diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 74880fa728c..2906cc8da19 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -261,6 +261,10 @@ java/awt/Clipboard/PasteNullToTextComponentsTest.java 8234140 macosx-all,windows java/awt/Clipboard/NoOwnerNoTargetsTest.java 8234140 macosx-all java/awt/Clipboard/LostOwnershipChainTest/SystemClipboard2ProcTest.java 8234140 macosx-all java/awt/Clipboard/HTMLTransferTest/HTMLTransferTest.java 8017454 macosx-all +java/awt/Clipboard/ClipboardSecurity.java 8054809 macosx-all +java/awt/Clipboard/GetAltContentsTest/SystemClipboardTest.java 8234140 macosx-all +java/awt/Clipboard/ImageTransferTest.java 8030710 generic-all +java/awt/Clipboard/NoDataConversionFailureTest.java 8234140 macosx-all java/awt/Frame/MiscUndecorated/RepaintTest.java 8266244 macosx-aarch64 java/awt/Modal/FileDialog/FileDialogAppModal1Test.java 7186009 macosx-all java/awt/Modal/FileDialog/FileDialogAppModal2Test.java 7186009 macosx-all diff --git a/test/jdk/java/awt/Clipboard/ClipboardSecurity.java b/test/jdk/java/awt/Clipboard/ClipboardSecurity.java new file mode 100644 index 00000000000..d33cc4fca42 --- /dev/null +++ b/test/jdk/java/awt/Clipboard/ClipboardSecurity.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2000, 2025, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 4274061 + * @summary Tests that Transferable.getTransferData() and + * SelectionOwner.lostOwnership is not called on Toolkit thread. + * @key headful + * @library /test/lib + * @run main ClipboardSecurity + */ + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class ClipboardSecurity { + static Clipboard clip = null; + public static final CountDownLatch latch = new CountDownLatch(1); + public static volatile boolean hasError = false; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + ClipboardSecurity clipboardSecurity = new ClipboardSecurity(); + clipboardSecurity.start(); + return; + } + + try { + clip = Toolkit.getDefaultToolkit().getSystemClipboard(); + if ( clip == null ) { + throw (new RuntimeException("Clipboard is null")); + } + Transferable data = clip.getContents(null); + if ( data == null ) { + throw (new RuntimeException("Data is null")); + } + System.out.println("Clipboard contents: " + data); + // First check - getTransferData + try { + String contData = + (String) data.getTransferData(DataFlavor.stringFlavor); + } catch (UnsupportedFlavorException | IOException exc) { + throw(new RuntimeException("Couldn't get transfer data - " + + exc.getMessage())); + } + // Second check - lostOwnership + MyClass clipData = new MyClass("clipbard test data"); + clip.setContents(clipData, clipData); + System.out.println("exit 0"); + System.exit(0); + } catch (RuntimeException exc) { + System.err.println(exc.getMessage()); + System.out.println("exit 2"); + System.exit(2); + } + } + + public void start() throws Exception { + clip = Toolkit.getDefaultToolkit().getSystemClipboard(); + if (clip == null) { + throw (new RuntimeException("Clipboard is null")); + } + MyClass clipData = new MyClass("clipboard test data"); + clip.setContents(clipData, clipData); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + ClipboardSecurity.class.getName(), + "child" + ); + + Process process = ProcessTools.startProcess("Child", pb); + OutputAnalyzer outputAnalyzer = new OutputAnalyzer(process); + + if (!process.waitFor(15, TimeUnit.SECONDS)) { + process.destroyForcibly(); + throw new TimeoutException("Timed out waiting for Child"); + } + System.out.println("WAIT COMPLETE"); + + outputAnalyzer.shouldHaveExitValue(0); + + if (!latch.await(10, TimeUnit.SECONDS)) { + throw new RuntimeException("timed out"); + } + + if (hasError) { + throw new RuntimeException("Detected call on Toolkit thread"); + } + + System.out.println("Passed."); + } +} + +class MyClass extends StringSelection implements ClipboardOwner { + MyClass(String title) { + super(title); + } + + private void checkIsCorrectThread(String reason) { + System.out.println("Checking " + reason + " for thread " + + Thread.currentThread().getName()); + String name = Thread.currentThread().getName(); + if (name.equals("AWT-Windows") || name.equals("AWT-Motif")) { + ClipboardSecurity.hasError = true; + System.err.println(reason + " is called on Toolkit thread!"); + } + } + + public void lostOwnership(Clipboard clip, Transferable cont) { + checkIsCorrectThread("lostOwnership"); + ClipboardSecurity.latch.countDown(); + System.out.println("lost ownership on " + + Thread.currentThread().getName() + " thread"); + } + + public Object getTransferData(DataFlavor flav) + throws UnsupportedFlavorException, IOException { + System.out.println("getTransferData on " + + Thread.currentThread().getName() + " thread"); + checkIsCorrectThread("getTransferData"); + return super.getTransferData(flav); + } +} diff --git a/test/jdk/java/awt/Clipboard/GetAltContentsTest/SystemClipboardTest.java b/test/jdk/java/awt/Clipboard/GetAltContentsTest/SystemClipboardTest.java new file mode 100644 index 00000000000..6aaa431fb3b --- /dev/null +++ b/test/jdk/java/awt/Clipboard/GetAltContentsTest/SystemClipboardTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2003, 2025, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 4287795 4790833 + * @summary tests new Clipboard methods: getAvailableDataFlavors, + * isDataFlavorAvailable, getData + * @key headful + * @library /test/lib + * @run main SystemClipboardTest + */ + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + + +public class SystemClipboardTest { + + private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + + private static final String contentsText = "contents text"; + + public void start() throws Exception { + Util.setClipboardContents(clipboard, new StringSelection(contentsText), new ClipboardOwner() { + public void lostOwnership(Clipboard clpbrd, Transferable cntnts) { + check(); // clipboard data retrieved from the system clipboard + Util.setClipboardContents(clipboard, new StringSelection(contentsText), null); + } + }); + + check(); // JVM-local clipboard data + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + SystemClipboardTest.class.getName(), + "child" + ); + + Process process = ProcessTools.startProcess("Child", pb); + OutputAnalyzer outputAnalyzer = new OutputAnalyzer(process); + + if (!process.waitFor(15, TimeUnit.SECONDS)) { + process.destroyForcibly(); + throw new TimeoutException("Timed out waiting for Child"); + } + + outputAnalyzer.shouldHaveExitValue(0); + } + + private void check() { + boolean failed = false; + + Transferable contents = Util.getClipboardContents(clipboard, null); + Set flavorsT = new HashSet<>(Arrays.asList(contents.getTransferDataFlavors())); + Set flavorsA = new HashSet<>(Arrays.asList(Util.getClipboardAvailableDataFlavors(clipboard))); + System.err.println("getAvailableDataFlavors(): " + flavorsA); + if (!flavorsA.equals(flavorsT)) { + failed = true; + System.err.println("FAILURE: getAvailableDataFlavors() returns incorrect " + + "DataFlavors: " + flavorsA + "\nwhile getContents()." + + "getTransferDataFlavors() return: " + flavorsT); + } + + if (!Util.isClipboardDataFlavorAvailable(clipboard, DataFlavor.stringFlavor)) { + failed = true; + System.err.println("FAILURE: isDataFlavorAvailable(DataFlavor.stringFlavor) " + + "returns false"); + } + + Object data = null; + try { + data = Util.getClipboardData(clipboard, DataFlavor.stringFlavor); + } catch (UnsupportedFlavorException exc) { + failed = true; + exc.printStackTrace(); + } catch (IOException exc) { + failed = true; + exc.printStackTrace(); + } + System.err.println("getData(): " + data); + if (!contentsText.equals(data)) { + failed = true; + System.err.println("FAILURE: getData() returns: " + data + + ", that is not equal to: \"" + contentsText + "\""); + + } + + if (failed) { + throw new RuntimeException("test failed, for details see output above"); + } + } + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + SystemClipboardTest systemClipboardTest = new SystemClipboardTest(); + systemClipboardTest.start(); + return; + } + + System.err.println("child VM: setting clipboard contents"); + + CountDownLatch latch = new CountDownLatch(1); + Util.setClipboardContents(clipboard, new StringSelection(contentsText), + (clpbrd, cntnts) -> { + System.err.println("child VM: success"); + latch.countDown(); + }); + + if (!latch.await(15, TimeUnit.SECONDS)) { + throw new RuntimeException("child VM failed"); + } + } +} + +class Util { + public static void setClipboardContents(Clipboard cb, + Transferable contents, + ClipboardOwner owner) { + while (true) { + try { + cb.setContents(contents, owner); + return; + } catch (IllegalStateException ise) { + ise.printStackTrace(); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + + public static Transferable getClipboardContents(Clipboard cb, + Object requestor) { + while (true) { + try { + return cb.getContents(requestor); + } catch (IllegalStateException ise) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + + public static Object getClipboardData(Clipboard cb, DataFlavor flavor) + throws IOException, UnsupportedFlavorException { + while (true) { + try { + return cb.getData(flavor); + } catch (IllegalStateException ise) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + + public static DataFlavor[] getClipboardAvailableDataFlavors(Clipboard cb) { + while (true) { + try { + return cb.getAvailableDataFlavors(); + } catch (IllegalStateException ise) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + + public static boolean isClipboardDataFlavorAvailable(Clipboard cb, + DataFlavor flavor) { + while (true) { + try { + return cb.isDataFlavorAvailable(flavor); + } catch (IllegalStateException ise) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } +} diff --git a/test/jdk/java/awt/Clipboard/ImageTransferTest.java b/test/jdk/java/awt/Clipboard/ImageTransferTest.java new file mode 100644 index 00000000000..0c407152009 --- /dev/null +++ b/test/jdk/java/awt/Clipboard/ImageTransferTest.java @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2002, 2025, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 4397404 4720930 + * @summary tests that images of all supported native image formats + * are transferred properly + * @key headful + * @library /test/lib + * @run main ImageTransferTest + */ + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.image.BufferedImage; +import java.awt.image.MemoryImageSource; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class ImageTransferTest { + public static final int CODE_NOT_RETURNED = 100; + public static final int CODE_CONSUMER_TEST_FAILED = 101; + public static final int CODE_FAILURE = 102; + + private TImageProducer imPr; + private int returnCode = CODE_NOT_RETURNED; + + public static void main(String[] args) throws Exception { + ImageTransferTest imageTransferTest = new ImageTransferTest(); + imageTransferTest.init(); + imageTransferTest.start(); + } + + public void init() { + imPr = new TImageProducer(); + imPr.begin(); + } + + public void start() throws Exception { + String formats = ""; + + String iniMsg = "Testing all native image formats from \n" + + "SystemFlavorMap.getNativesForFlavor(DataFlavor.imageFlavor) \n"; + + for (int i = 0; i < imPr.formats.length; i++) { + formats += (imPr.formats[i] + " "); + } + System.out.println(iniMsg + formats); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + TImageConsumer.class.getName(), formats + ); + + Process process = ProcessTools.startProcess("Child", pb); + OutputAnalyzer outputAnalyzer = new OutputAnalyzer(process); + + if (!process.waitFor(15, TimeUnit.SECONDS)) { + process.destroyForcibly(); + returnCode = CODE_NOT_RETURNED; + } else { + returnCode = outputAnalyzer.getExitValue(); + } + + switch (returnCode) { + case CODE_NOT_RETURNED: + throw new RuntimeException("Child VM: failed to start"); + case CODE_FAILURE: + throw new RuntimeException("Child VM: abnormal termination"); + case CODE_CONSUMER_TEST_FAILED: + throw new RuntimeException("test failed: images in some " + + "native formats are not transferred properly: " + + "see output of child VM"); + default: + boolean failed = false; + String passedFormats = ""; + String failedFormats = ""; + + for (int i = 0; i < imPr.passedArray.length; i++) { + if (imPr.passedArray[i]) passedFormats += imPr.formats[i] + " "; + else { + failed = true; + failedFormats += imPr.formats[i] + " "; + } + } + if (failed) { + throw new RuntimeException("test failed: images in following " + + "native formats are not transferred properly: " + + failedFormats); + } else { + System.err.println("images in following native formats are " + + "transferred properly: " + passedFormats); + } + } + } +} + +abstract class ImageTransferer implements ClipboardOwner { + + static final String S_PASSED = "Y"; + static final String S_FAILED = "N"; + static final String S_BEGIN = "B"; + static final String S_BEGIN_ANSWER = "BA"; + static final String S_END = "E"; + + Image image; + + Clipboard clipboard; + + String[] formats; + int fi; // next format index + + + ImageTransferer() { + clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + image = createImage(); + } + + abstract void notifyTransferSuccess(boolean status); + + private static Image createImage() { + int w = 100; + int h = 100; + int[] pix = new int[w * h]; + + int index = 0; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int red = 127; + int green = 127; + int blue = y > h / 2 ? 127 : 0; + int alpha = 255; + if (x < w / 4 && y < h / 4) { + alpha = 0; + red = 0; + } + pix[index++] = (alpha << 24) | (red << 16) | (green << 8) | blue; + } + } + + return Toolkit.getDefaultToolkit(). + createImage(new MemoryImageSource(w, h, pix, 0, w)); + } + + static String[] retrieveFormatsToTest() { + SystemFlavorMap sfm = (SystemFlavorMap)SystemFlavorMap.getDefaultFlavorMap(); + java.util.List ln = sfm.getNativesForFlavor(DataFlavor.imageFlavor); + + String osName = System.getProperty("os.name").toLowerCase(); + String sMETAFILEPICT = "METAFILEPICT"; + if (osName.indexOf("win") >= 0 && !ln.contains(sMETAFILEPICT)) { + // for test failing on JDK without this fix + ln.add(sMETAFILEPICT); + } + return (String[])ln.toArray(new String[ln.size()]); + } + + static void leaveFormat(String format) { + SystemFlavorMap sfm = (SystemFlavorMap)SystemFlavorMap.getDefaultFlavorMap(); + sfm.setFlavorsForNative(format, + new DataFlavor[] { DataFlavor.imageFlavor }); + sfm.setNativesForFlavor(DataFlavor.imageFlavor, + new String[] { format }); + } + + boolean areImagesIdentical(Image im1, Image im2) { + if (formats[fi].equals("JFIF") || formats[fi].equals("image/jpeg") || + formats[fi].equals("GIF") || formats[fi].equals("image/gif")) { + // JFIF and GIF are lossy formats + return true; + } + int[] ib1 = getImageData(im1); + int[] ib2 = getImageData(im2); + + if (ib1.length != ib2.length) { + return false; + } + + if (formats[fi].equals("PNG") || + formats[fi].equals("image/png") || + formats[fi].equals("image/x-png")) { + // check alpha as well + for (int i = 0; i < ib1.length; i++) { + if (ib1[i] != ib2[i]) { + System.err.println("different pixels: " + + Integer.toHexString(ib1[i]) + " " + + Integer.toHexString(ib2[i])); + return false; + } + } + } else { + for (int i = 0; i < ib1.length; i++) { + if ((ib1[i] & 0x00FFFFFF) != (ib2[i] & 0x00FFFFFF)) { + System.err.println("different pixels: " + + Integer.toHexString(ib1[i]) + " " + + Integer.toHexString(ib2[i])); + return false; + } + } + } + return true; + } + + private static int[] getImageData(Image image) { + int width = image.getWidth(null); + int height = image.getHeight(null); + BufferedImage bimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = bimage.createGraphics(); + try { + g2d.drawImage(image, 0, 0, width, height, null); + } finally { + g2d.dispose(); + } + return bimage.getRGB(0, 0, width, height, null, 0, width); + } + + static void setClipboardContents(Clipboard cb, + Transferable contents, + ClipboardOwner owner) { + synchronized (cb) { + boolean set = false; + while (!set) { + try { + cb.setContents(contents, owner); + set = true; + } catch (IllegalStateException ise) { + try { Thread.sleep(100); } + catch (InterruptedException e) { e.printStackTrace(); } + } + } + } + } + + static Transferable getClipboardContents(Clipboard cb, + Object requestor) { + synchronized (cb) { + while (true) { + try { + Transferable t = cb.getContents(requestor); + return t; + } catch (IllegalStateException ise) { + try { Thread.sleep(100); } + catch (InterruptedException e) { e.printStackTrace(); } + } + } + } + } +} + +class TImageProducer extends ImageTransferer { + + boolean[] passedArray; + + private boolean isFirstCallOfLostOwnership = true; + + TImageProducer() { + formats = retrieveFormatsToTest(); + passedArray = new boolean[formats.length]; + } + + void begin() { + setClipboardContents(clipboard, new StringSelection(S_BEGIN), this); + } + + public void lostOwnership(Clipboard cb, Transferable contents) { + System.err.println("PRODUCER: lost clipboard ownership"); + + Transferable t = getClipboardContents(cb, null); + + if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + String msg = null; + // for test going on if t.getTransferData() will throw an exception + if (isFirstCallOfLostOwnership) { + isFirstCallOfLostOwnership = false; + msg = S_BEGIN_ANSWER; + } else { + msg = S_PASSED; + } + + try { + msg = (String)t.getTransferData(DataFlavor.stringFlavor); + System.err.println("received message: " + msg); + } catch (Exception e) { + System.err.println("Can't getTransferData-message: " + e); + } + + if (msg.equals(S_PASSED)) { + notifyTransferSuccess(true); + } else if (msg.equals(S_FAILED)) { + notifyTransferSuccess(false); + } else if (!msg.equals(S_BEGIN_ANSWER)) { + throw new RuntimeException("wrong message in " + + "TImageProducer.lostOwnership(): " + msg + + " (possibly due to bug 4683804)"); + } + } else { + throw new RuntimeException("DataFlavor.stringFlavor is not " + + "supported by transferable in " + + "TImageProducer.lostOwnership()"); + } + + if (fi < formats.length) { + System.err.println("testing native image format " + formats[fi] + + "..."); + leaveFormat(formats[fi]); + setClipboardContents(cb, new ImageSelection(image), this); + } else { + setClipboardContents(cb, new StringSelection(S_END), null); + } + } + + void notifyTransferSuccess(boolean status) { + passedArray[fi] = status; + fi++; + } +} + +class TImageConsumer extends ImageTransferer { + + private static final Object LOCK = new Object(); + + private static boolean failed; + + public void lostOwnership(Clipboard cb, Transferable contents) { + System.err.println("CONSUMER: lost clipboard ownership"); + + Transferable t = getClipboardContents(cb, null); + + if (t.isDataFlavorSupported(DataFlavor.imageFlavor)) { + Image im = null; //? image; + try { + im = (Image) t.getTransferData(DataFlavor.imageFlavor); + } catch (Exception e) { + System.err.println("Can't getTransferData-image: " + e); + notifyTransferSuccess(false); + } + + if (im == null) { + System.err.println("getTransferData returned null"); + notifyTransferSuccess(false); + } else if (areImagesIdentical(image, im)) { + notifyTransferSuccess(true); + } else { + System.err.println("transferred image is different from " + + "initial image"); + notifyTransferSuccess(false); + } + } else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + // all image formats have been processed + try { + String msg = (String) t.getTransferData(DataFlavor.stringFlavor); + System.err.println("received message: " + msg); + } catch (Exception e) { + System.err.println("Can't getTransferData-message: " + e); + } + synchronized (LOCK) { + LOCK.notifyAll(); + } + } else { + System.err.println("imageFlavor is not supported by transferable"); + notifyTransferSuccess(false); + } + } + + void notifyTransferSuccess(boolean status) { + if (status) { + System.err.println("format passed: " + formats[fi]); + setClipboardContents(clipboard, new StringSelection(S_PASSED), this); + } else { + failed = true; + System.err.println("format failed: " + formats[fi]); + setClipboardContents(clipboard, new StringSelection(S_FAILED), this); + } + + if (fi < formats.length - 1) { + leaveFormat(formats[++fi]); + } + } + + public static void main(String[] args) { + try { + TImageConsumer ic = new TImageConsumer(); + + ic.formats = args; + + leaveFormat(ic.formats[0]); + synchronized (LOCK) { + ic.setClipboardContents(ic.clipboard, + new StringSelection(S_BEGIN_ANSWER), ic); + LOCK.wait(); + } + if (failed) System.exit(ImageTransferTest.CODE_CONSUMER_TEST_FAILED); + } catch (Throwable e) { + e.printStackTrace(); + System.exit(ImageTransferTest.CODE_FAILURE); + } + } +} + +/** + * A Transferable which implements the capability required + * to transfer an Image. + * + * This Transferable properly supports + * DataFlavor.imageFlavor. + * and all equivalent flavors. + * No other DataFlavors are supported. + * + * @see java.awt.datatransfer.DataFlavor.imageFlavor + */ +class ImageSelection implements Transferable { + + private static final int IMAGE = 0; + + private static final DataFlavor[] flavors = { DataFlavor.imageFlavor }; + + private Image data; + + /** + * Creates a Transferable capable of transferring + * the specified String. + */ + public ImageSelection(Image data) { + this.data = data; + } + + /** + * Returns an array of flavors in which this Transferable + * can provide the data. DataFlavor.stringFlavor + * is properly supported. + * Support for DataFlavor.plainTextFlavor is + * deprecated. + * + * @return an array of length one, whose element is DataFlavor. + * imageFlavor + */ + public DataFlavor[] getTransferDataFlavors() { + // returning flavors itself would allow client code to modify + // our internal behavior + return (DataFlavor[])flavors.clone(); + } + + /** + * Returns whether the requested flavor is supported by this + * Transferable. + * + * @param flavor the requested flavor for the data + * @return true if flavor is equal to + * DataFlavor.imageFlavor; + * false if flavor + * is not one of the above flavors + * @throws NullPointerException if flavor is null + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + for (int i = 0; i < flavors.length; i++) { + if (flavor.equals(flavors[i])) { + return true; + } + } + return false; + } + + /** + * Returns the Transferable's data in the requested + * DataFlavor if possible. If the desired flavor is + * DataFlavor.imageFlavor, or an equivalent flavor, + * the Image representing the selection is + * returned. + * + * @param flavor the requested flavor for the data + * @return the data in the requested flavor, as outlined above + * @throws UnsupportedFlavorException if the requested data flavor is + * not equivalent to DataFlavor.imageFlavor + * @throws IOException if an IOException occurs while retrieving the data. + * By default, ImageSelection never throws + * this exception, but a subclass may. + * @throws NullPointerException if flavor is null + */ + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, java.io.IOException + { + if (flavor.equals(flavors[IMAGE])) { + return (Object)data; + } else { + throw new UnsupportedFlavorException(flavor); + } + } +} diff --git a/test/jdk/java/awt/Clipboard/NoDataConversionFailureTest.java b/test/jdk/java/awt/Clipboard/NoDataConversionFailureTest.java new file mode 100644 index 00000000000..9ad108a5da2 --- /dev/null +++ b/test/jdk/java/awt/Clipboard/NoDataConversionFailureTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2002, 2025, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 4558797 + * @summary Tests that there is no data conversion failure when two applications + * exchange data via system clipboard + * @key headful + * @library /test/lib + * @run main NoDataConversionFailureTest + */ + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class NoDataConversionFailureTest { + + public static void main(String[] args) throws Exception { + SystemClipboardOwner.run(); + + if (SystemClipboardOwner.failed) { + throw new RuntimeException("test failed: can not get transfer data"); + } else { + System.err.println("test passed"); + } + } +} + +class SystemClipboardOwner implements ClipboardOwner { + static volatile boolean failed; + + private static final Object LOCK = new Object(); + + private static final int CHAIN_LENGTH = 15; + private final static Clipboard clipboard = + Toolkit.getDefaultToolkit().getSystemClipboard(); + + private int m; + private final int id; + + public SystemClipboardOwner(int m) { this.m = m; id = m; } + + public void lostOwnership(Clipboard cb, Transferable contents) { + System.err.println(id + " lost clipboard ownership"); + + Transferable t = getClipboardContents(cb, null); + // for test passing if t.getTransferData() will throw an exception + String msg = String.valueOf(m + 1); + try { + msg = (String) t.getTransferData(DataFlavor.stringFlavor); + } catch (IOException e) { + failed = true; + System.err.println(id + " can't getTransferData: " + e); + } catch (Exception e) { + System.err.println(id + " can't getTransferData: " + e); + } + + System.err.println(id + " Clipboard.getContents(): " + msg); + if (!msg.equals(String.valueOf(m + 1))) { + System.err.println("Clipboard.getContents() returned incorrect contents!"); + } + + m += 2; + if (m <= CHAIN_LENGTH) { + System.err.println(id + " Clipboard.setContents(): " + m); + setClipboardContents(cb, new StringSelection(m + ""), this); + } + if (m >= CHAIN_LENGTH) { + synchronized (LOCK) { + LOCK.notifyAll(); + } + } + } + + public static void run() throws Exception { + SystemClipboardOwner cbo1 = new SystemClipboardOwner(0); + System.err.println(cbo1.m + " Clipboard.setContents(): " + cbo1.m); + setClipboardContents(clipboard, new StringSelection(cbo1.m + ""), + cbo1); + + ProcessBuilder pb = ProcessTools + .createTestJavaProcessBuilder(SystemClipboardOwner.class.getName()); + + Process process = ProcessTools.startProcess("Child", pb); + OutputAnalyzer outputAnalyzer = new OutputAnalyzer(process); + + if (!process.waitFor(15, TimeUnit.SECONDS)) { + process.destroyForcibly(); + throw new TimeoutException("Timed out waiting for Child"); + } + + if (cbo1.m < CHAIN_LENGTH) { + System.err.println("chain of calls of lostOwnership() broken!"); + } + + outputAnalyzer.shouldHaveExitValue(0); + } + + public static void main(String[] args) throws InterruptedException { + SystemClipboardOwner cbo2 = new SystemClipboardOwner(1); + System.err.println(cbo2.m + " Clipboard.setContents(): " + cbo2.m); + + synchronized (LOCK) { + setClipboardContents(clipboard, new StringSelection(cbo2.m + ""), + cbo2); + LOCK.wait(); + } + } + + + private static void setClipboardContents(Clipboard cb, + Transferable contents, + ClipboardOwner owner) { + synchronized (cb) { + boolean set = false; + while (!set) { + try { + cb.setContents(contents, owner); + set = true; + } catch (IllegalStateException ise) { + try { Thread.sleep(100); } + catch (InterruptedException e) { e.printStackTrace(); } + } + } + } + } + + private static Transferable getClipboardContents(Clipboard cb, + Object requestor) { + synchronized (cb) { + while (true) { + try { + Transferable t = cb.getContents(requestor); + return t; + } catch (IllegalStateException ise) { + try { Thread.sleep(100); } + catch (InterruptedException e) { e.printStackTrace(); } + } + } + } + } +}