On Sat, 20 Jun 2026 17:54:20 GMT, Michael Strauß <[email protected]> wrote:

>> 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(n...

My thoughts are similar to @mstr2's.

First, I'm not confident the implementation of `DataFormat` is compatible with 
its use. The class is used in conjunction with `Clipboard`/`Dragboard` as a 
marker for the type of data it contains.
Because `DataFormat` is just a thin wrapper of a `Set<String>` (where the 
strings are identifiers/mime types), and the content is populate through a 
`Map<DataFormat, Object>`, it essentially serves as an intermediary between the 
ids and the value. That is, the clipboard is essentially `ids -> value`.
Likewise, the content is retrieved by `DataFormats`, not by the ids they 
contain. For example,

var df = new DataFormat("text/a", "text/b");
var cc = new ClipboardContent();
cc.put(df, "value");
clipboard.setContent(cc);
// ... and then
clipboard.getContent(df); // "value"

There is no direct way to ask the clipboard specifically for a `"text/a"` 
despite having matching content:

var df2 = new DataFormat("text/a");
clipboard.get(df2); // null

This does not strike me as a correct behavior to begin with. The receiving 
application is interested in identifiers, not aggregates of identifiers: 
`getContent(String id)`. If the receiving application is interested in 
`"text/plain"`, but the publishing application wanted to provide more 
information about the text and used `new DataFormat("text/plain", "text/rtf")`, 
the content will not be found. As Michael said, a manual lookup for an id in 
all data formats is possible.

What a publishing application can do in this case is use 1 `DataFormat` per id: 
put both `new DataFormat("text/plain")` and `new DataFormat("text/rtf")` in the 
clipboard content with the same value. Indeed, the docs of `Clipboard` say
> The `Clipboard` operates on the concept of having a single conceptual item on 
> the clipboard at any one time -- though it may be placed on the clipboard in 
> different formats.

and give the example

content.putString("Some text");
content.putHtml("<b>Some</b> text");

so that there are 2 `DataFormat`s, not a single one aggregating the plain and 
html ids. But then what purpose does `DataFormat` serve?

As for the multiplicity problem - the clipboard could contain more than one 
value per identifier:

var df1 = new DataFormat("text/a", "text/b");
var df2 = new DataFormat("text/a", "text/c");
var cc = new ClipboardContent();
cc.put(df1, "value1");
cc.put(df2, "value2");
clipboard.setContent(cc);

and what should an application interested in `"text/a"` find, `"value1"` or 
`"value2"`? Perhaps this is why there can't exist more than 1 association of an 
identifier with a `DataFormat` and the global registry is needed (although 
`WeakReferenceQueue` seems like a poor choice for it).

My conclusion is that the content should actually be represented as 
`Map<String, Object>` where the string is the identifier. This eliminates the 
problem of multiplicity by virtue of the `Map` contract: the user can then 
decide to solve multiplicity either by "last entry wins" via standard `put` or 
by "first entry wins" via `putIfAbsent`.
The threading issue can be eliminated by using a `ConcurrentMap`, but it 
shouldn't be about concurrently registering an identifier with a `DataFormat`, 
but rather about populating the clipboard concurrently. The synchronization 
should be done at a different level, in my opinion.

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

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

Reply via email to