On Mon, 8 Jun 2026 14:57:53 GMT, Andy Goryachev <[email protected]> wrote:

>> ### Summary
>> 
>> This PR makes deprecates the `DataFormat` constructor (for removal):
>> 
>> public DataFormat(@NamedArg("ids") String... ids)
>> 
>> 
>> and replaces it with
>> 
>> public static DataFormat of(String ... ids)
>> 
>> 
>> ### Problem
>> 
>> There seems to be several issues with DataFormat API and implementation 
>> discovered during a Clipboard-related code review:
>> 
>> 1. `static DataFormat::lookupMimeType(String)` is not thread safe: while 
>> iterating over previously registered entries in the `DATA_FORMAT_LIST` 
>> another thread might create a new instance (DataFormat L227)
>> 
>> 2. `public DataFormat(String...)` constructor might throw an 
>> `IllegalArgumentException` if one of the given mime types is already 
>> assigned to another `DataFormat`. The origin of this requirement is unclear, 
>> but one possible issue I can see is if the application has two libraries 
>> that both attempt to create a `DataFormat` for let's say `"text/css"`. Then, 
>> depending on the timing or the exact code path, an exception will be thrown 
>> for which the library(-ies) might not be prepared. The constructor is also 
>> not thread safe.
>> 
>> 3. To avoid a situation mentioned in bullet 2, a developer would is 
>> typically call `lookupMimeType()` to obtain an already registered instance, 
>> followed by a constructor call if such an instance has not been found. An 
>> example of such code can be seen in webkit/UIClientImpl:299 - but even then, 
>> despite that two-step process being synchronized, the code might still fail 
>> if *some other* library or the application attempts to create a new instance 
>> of DataFormat, since the constructor itself is not synchronized.
>> 
>> 4. `DataFormat(new String[] { null })` is allowed but makes no sense!
>> 
>> Why do we need to have the registry of previously created instances? 
>> Unclear. My theory is that the DataFormat allows to have multiple mime-types 
>> (ids) - example being `DataFormat.FILES = new 
>> DataFormat("application/x-java-file-list", "java.file-list");` - and the 
>> registry was added to prevent creation of a `DataFormat` with just one id 
>> for some reason.
>> 
>> What should be done?
>> - find out why we need this registry in the first place i.e. what could 
>> happen if we have multiple DataFormat instances with overlapping ids.
>> - if the registry is needed add a new factory method, something like 
>> `DataFormat::of(String ...)` which is properly synchronized. This method 
>> will be called by the constructor to retain the backward compatibility.
>> - deprecate (possibly for removal) `DataFormat::l...
>
> Andy Goryachev has updated the pull request with a new target base due to a 
> merge or a rebase. The incremental webrev excludes the unrelated changes 
> brought in by the merge/rebase. The pull request contains nine additional 
> commits since the last revision:
> 
>  - Merge branch 'master' into 8373452.data.format
>  - Merge branch 'master' into 8373452.data.format
>  - 2026
>  - Merge branch 'master' into 8373452.data.format
>  - Merge branch 'master' into 8373452.data.format
>  - junit
>  - whitespace
>  - javadoc
>  - data format

I've been staring at this code, scratching my head and wondering what is going 
on.

First of all, the global `DataFormat` registry does indeed expose some real 
problems. However, replacing the constructor with an interned 
`DataFormat.of(String...)` does not address the underlying design problem. It 
seems to me that the main question here is not whether `DataFormat` should be 
created with a constructor or a factory method, the main question is what 
`DataFormat` is supposed to mean.

The proposed `DataFormat.of(...)` still requires a global ownership model for 
identifiers. That model is ambiguous for overlapping formats:

DataFormat a = DataFormat.of("text/foo");
DataFormat b = DataFormat.of("text/foo", "text/bar");


There is no universally correct interned result here:
* returning `a` loses the "text/bar" information
* returning `b` changes what earlier callers meant by "text/foo"
* throwing keeps the current failure mode
* mutating `a` to include "text/bar" would break immutability and hash-based 
collections

So the factory does not actually solve the semantic problem. It also does not 
remove the need to support existing public API, as both `DataFormat(String...)` 
and `DataFormat.lookupMimeType(String)` are already public.

The current implementation relies too much on identity and registry lookup. For 
example, `QuantumClipboard` maps native MIME strings back through 
`DataFormat.lookupMimeType(...)`, which makes clipboard behavior depend on 
unrelated application initialization order.

I think we should treat `DataFormat` primarily as an immutable value 
descriptor: `new DataFormat("text/foo", "text/bar")` means that this format can 
be represented by any of the specified identifiers.

Then we define another operation, let's call it `boolean 
DataFormat.matches(String)`. This method returns `true` if any of the 
identifiers is equal to the given string. This is the question that clipboard 
code _actually_ needs to answer. (Note that this operation is not strictly 
required, as the identifiers are already exposed with 
`DataFormat.getIdentifiers()`, so anyone can answer the question that a 
`matches` method would answer.)

`Clipboard.getContentTypes()` should report what the clipboard _actually_ 
advertises, not an arbitrary alias group that an application may have 
registered globally. If the native clipboard reports only `text/foo` then 
`getContentTypes()` can reasonably return `Set.of(new DataFormat("text/foo"))`.

But this must still work:

clipboard.hasContent(new DataFormat("text/foo", "text/bar")) // true
clipboard.getContent(new DataFormat("text/foo", "text/bar")) // reads text/foo

These methods must be implemented in terms of `matches`. The clipboard 
implementation should be changed as follows:
* `getContentTypes()` discovers the actual identifiers on the clipboard (for 
example "text/foo")
* `hasContent(format)` and `getContent(format)` match a user-provided 
`DataFormat`, which may be an alias group for one of the identifiers on the 
clipboard (for example ["text/foo", "text/bar"])
* `QuantumClipboard` should stop using `lookupMimeType()` to decide whether 
content exists
* `QuantumClipboard` should stop depending on identity comparisons for built-in 
formats like image, URL, files, RTF, etc.

This would fix the synchronization issue without relying on global interning.

-------------

PR Comment: https://git.openjdk.org/jfx/pull/2006#issuecomment-4759398192

Reply via email to