diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumClipboard.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumClipboard.java index 176888dc736..df8626b5064 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumClipboard.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumClipboard.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -245,10 +245,11 @@ public void flush() { systemAssistant.flush(); } - @Override public Object getContent(DataFormat dataFormat) { + @Override + public Object getContent(DataFormat dataFormat) { if (dataCache != null) { for (Pair pair : dataCache) { - if (pair.getKey() == dataFormat) { + if (pair.getKey().equals(dataFormat)) { return pair.getValue(); } } @@ -258,11 +259,11 @@ public void flush() { ClipboardAssistance assistant = (currentDragboard != null) ? currentDragboard : systemAssistant; - if (dataFormat == DataFormat.IMAGE) { + if (DataFormat.IMAGE.equals(dataFormat)) { return readImage(); - } else if (dataFormat == DataFormat.URL) { + } else if (DataFormat.URL.equals(dataFormat)) { return assistant.getData(Clipboard.URI_TYPE); - } else if (dataFormat == DataFormat.FILES) { + } else if (DataFormat.FILES.equals(dataFormat)) { Object data = assistant.getData(Clipboard.FILE_LIST_TYPE); if (data == null) return Collections.emptyList(); String[] paths = (String[]) data; @@ -438,10 +439,11 @@ private boolean placeImage(final Image image) { return set; } - @Override public boolean hasContent(DataFormat dataFormat) { + @Override + public boolean hasContent(DataFormat dataFormat) { if (dataCache != null) { for (Pair pair : dataCache) { - if (pair.getKey() == dataFormat) { + if (pair.getKey().equals(dataFormat)) { return true; } } @@ -456,17 +458,17 @@ private boolean placeImage(final Image image) { return false; } for (String t: stypes) { - if (dataFormat == DataFormat.IMAGE && + if (DataFormat.IMAGE.equals(dataFormat) && t.equalsIgnoreCase(Clipboard.RAW_IMAGE_TYPE)) { return true; - } else if (dataFormat == DataFormat.URL && + } else if (DataFormat.URL.equals(dataFormat) && t.equalsIgnoreCase(Clipboard.URI_TYPE)) { return true; - } else if (dataFormat == DataFormat.IMAGE && + } else if (DataFormat.IMAGE.equals(dataFormat) && t.equalsIgnoreCase(Clipboard.HTML_TYPE) && parseIMG(assistant.getData(Clipboard.HTML_TYPE)) != null) { return true; - } else if (dataFormat == DataFormat.FILES && + } else if (DataFormat.FILES.equals(dataFormat) && t.equalsIgnoreCase(Clipboard.FILE_LIST_TYPE)) { return true; } @@ -528,16 +530,16 @@ private boolean putContentToPeer(Pair... content) { // might expect the JPG bits directly, rather than the DIB / TIFF bits. // So what we do is, any IMAGE type DataFormat that comes in will be stored // in DIB / TIFF, while specific bits will also be stored (in the future). - if (dataFormat == DataFormat.IMAGE) { + if (DataFormat.IMAGE.equals(dataFormat)) { dataSet = placeImage(convertObjectToImage(data)); - } else if (dataFormat == DataFormat.URL) { + } else if (DataFormat.URL.equals(dataFormat)) { // TODO Weird, but this is how Glass wants it... systemAssistant.setData(Clipboard.URI_TYPE, data); dataSet = true; - } else if (dataFormat == DataFormat.RTF) { + } else if (DataFormat.RTF.equals(dataFormat)) { systemAssistant.setData(Clipboard.RTF_TYPE, data); dataSet = true; - } else if (dataFormat == DataFormat.FILES) { + } else if (DataFormat.FILES.equals(dataFormat)) { // Have to convert from List to String[] List list = (List)data; if (list.size() != 0) { @@ -551,8 +553,10 @@ private boolean putContentToPeer(Pair... content) { } } else { if (data instanceof Serializable) { - if ((dataFormat != DataFormat.PLAIN_TEXT && dataFormat != DataFormat.HTML) || - !(data instanceof String)) + if ( + (!(DataFormat.PLAIN_TEXT.equals(dataFormat)) && !(DataFormat.HTML.equals(dataFormat))) || + !(data instanceof String) + ) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/input/DataFormat.java b/modules/javafx.graphics/src/main/java/javafx/scene/input/DataFormat.java index d8107ca13f5..8f960e2c569 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/input/DataFormat.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/input/DataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,9 @@ package javafx.scene.input; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.Iterator; import java.util.Set; - -import com.sun.javafx.util.WeakReferenceQueue; import javafx.beans.NamedArg; /** @@ -41,14 +37,9 @@ */ public class DataFormat { - /** - * A static cache of all DataFormats created and currently in use. This is needed - * by the underlying implementation, such that, given a mime type, we can determine - * the associated DataFormat. The OS level is going to supply us with a mime type - * (or other string based key), and we need to be able to map this back to the FX - * DataFormat. - */ - private static final WeakReferenceQueue DATA_FORMAT_LIST = new WeakReferenceQueue<>(); + // A static registry of DataFormats for the purposes of checking against constructing DataFormats + // that contain mismatched mime types. + private static final HashMap registry = new HashMap<>(); /** * Represents a plain text string. @@ -101,7 +92,7 @@ public class DataFormat { * A set of identifiers, typically mime types, for this DataFormat. * In most cases this will be a single String. */ - private final Set identifier; + private final Set identifiers; /** * Create a new DataFormat, specifying the set of ids that are associated with @@ -126,25 +117,37 @@ public class DataFormat { * to drag data of this type from/to {@link javafx.embed.swing.JFXPanel}. *

* @param ids The set of ids used to represent this DataFormat on the clipboard. - * @throws IllegalArgumentException if one of the given mime types is already - * assigned to another DataFormat. + * @throws IllegalArgumentException if one of the given ids is already + * assigned to another DataFormat with a different set of ids + * @throws NullPointerException if any of the ids is null */ public DataFormat(@NamedArg("ids") String... ids) { - DATA_FORMAT_LIST.cleanup(); - if (ids != null) { - for (String id : ids) { - if (lookupMimeType(id) != null) { - throw new IllegalArgumentException("DataFormat '" + id + - "' already exists."); + if ((ids == null) || (ids.length == 0)) { + identifiers = Set.of(); + return; + } + + identifiers = Set.of(ids); + // check for mismatched formats + boolean isNew = true; + synchronized (registry) { + for (String id : identifiers) { + DataFormat f = registry.get(id); + if (f != null) { + if (!identifiers.equals(f.identifiers)) { + throw new IllegalArgumentException("DataFormat '" + id + "' already exists."); + } + isNew = false; } } - this.identifier = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(ids))); - } else { - this.identifier = Collections.emptySet(); - } - // Add to the statis data format list. - DATA_FORMAT_LIST.add(this); + // add to the registry if new + if (isNew) { + for (String id : ids) { + registry.put(id, this); + } + } + } } /** @@ -152,7 +155,7 @@ public DataFormat(@NamedArg("ids") String... ids) { * @return an unmodifiable set that is never null. */ public final Set getIdentifiers() { - return identifier; + return identifiers; } /** @@ -160,15 +163,15 @@ public final Set getIdentifiers() { * @return a string representation of this {@code DataFormat} object. */ @Override public String toString() { - if (identifier.isEmpty()) { + if (identifiers.isEmpty()) { return "[]"; - } else if (identifier.size() == 1) { + } else if (identifiers.size() == 1) { StringBuilder sb = new StringBuilder("["); - sb.append(identifier.iterator().next()); + sb.append(identifiers.iterator().next()); return (sb.append("]").toString()); } else { StringBuilder b = new StringBuilder("["); - Iterator itr = identifier.iterator(); + Iterator itr = identifiers.iterator(); while (itr.hasNext()) { b = b.append(itr.next()); if (itr.hasNext()) { @@ -187,7 +190,7 @@ public final Set getIdentifiers() { @Override public int hashCode() { int hash = 7; - for (String id : identifier) { + for (String id : identifiers) { hash = 31 * hash + id.hashCode(); } @@ -199,18 +202,12 @@ public final Set getIdentifiers() { * @param obj the reference object with which to compare. * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise. */ - @Override public boolean equals(Object obj) { - if (obj == null || ! (obj instanceof DataFormat)) { - return false; - } - - DataFormat otherDataFormat = (DataFormat) obj; - - if (identifier.equals(otherDataFormat.identifier)) { + @Override + public boolean equals(Object obj) { + if (obj == this) { return true; } - - return false; + return (obj instanceof DataFormat f) && identifiers.equals(f.identifiers); } /** @@ -224,13 +221,8 @@ public static DataFormat lookupMimeType(String mimeType) { return null; } - Iterator itr = DATA_FORMAT_LIST.iterator(); - while (itr.hasNext()) { - DataFormat dataFormat = (DataFormat) itr.next(); - if (dataFormat.getIdentifiers().contains(mimeType)) { - return dataFormat; - } + synchronized (registry) { + return registry.get(mimeType); } - return null; } } diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/input/DataFormatTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/input/DataFormatTest.java index 6d1fd0fc7f9..f600e814c58 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/input/DataFormatTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/input/DataFormatTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,18 @@ package test.javafx.scene.input; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; import javafx.scene.input.DataFormat; - +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; public class DataFormatTest { @@ -69,9 +69,9 @@ public void testMimeTypes(DataFormat format, String mime1, String mime2) { @ParameterizedTest @MethodSource("getParams") public void dataFormatsShouldBeFound(DataFormat format, String mime1, String mime2) { - assertSame(format, DataFormat.lookupMimeType(mime1)); + assertEquals(format, DataFormat.lookupMimeType(mime1)); if (mime2 != null) { - assertSame(format, DataFormat.lookupMimeType(mime2)); + assertEquals(format, DataFormat.lookupMimeType(mime2)); } } @@ -84,14 +84,30 @@ public void testToString(DataFormat format, String mime1, String mime2) { @ParameterizedTest @MethodSource("getParams") - public void shouldNotBePossibleToReuseMimeTypes(DataFormat format, String mime1, String mime2) { + public void shouldBePossibleToReuseEquivalentMimeTypes(DataFormat f, String mime1, String mime2) { + DataFormat f1 = new DataFormat(f.getIdentifiers().toArray(String[]::new)); + assertEquals(f, f1); + DataFormat f2 = new DataFormat(f.getIdentifiers().toArray(String[]::new)); + assertEquals(f, f2); + assertEquals(f1, f2); + } + + @Test + public void shouldNotBePossibleToRegisterMismatchedFormats() { + // using DataFormat.FILES + assertThrows(IllegalArgumentException.class, () -> { + new DataFormat("application/x-java-file-list"); + }); assertThrows(IllegalArgumentException.class, () -> { - DataFormat customEqual = new DataFormat(format.getIdentifiers().toArray( - new String[format.getIdentifiers().size()])); + new DataFormat("java.file-list"); + }); + // using custom + DataFormat f1 = new DataFormat("test/foo", "test/bar"); + assertThrows(IllegalArgumentException.class, () -> { + new DataFormat("test/foo"); }); } - @ParameterizedTest @MethodSource("getParams") public void testEqualsAndHashCode(DataFormat format, String mime1, String mime2) { @@ -101,4 +117,20 @@ public void testEqualsAndHashCode(DataFormat format, String mime1, String mime2) assertFalse(uniqueFormat.equals(format)); assertFalse(uniqueFormat.hashCode() == format.hashCode()); } + + @Test + public void noMoreNullMimeTypes() { + String mime = null; + assertThrows(NullPointerException.class, () -> { + new DataFormat(mime); + }); + } + + @Test + public void nullArrayIsAllowedForCompatibilityReasons() { + String[] mimes = null; + assertDoesNotThrow(() -> { + new DataFormat(mimes); + }); + } }