admin管理员组

文章数量:1317898

My java (Java 21) application has a part of gui that lets you drag an image found on Google in web-browser to the application, the data flavor of the url is logged, and if thee is no valid data flavor a message is also logged.

On Windows it works fine (tested with Firefox)

On MacOS it doesnt work (tested with Firefox and Safari) and nothing is even logged. Based on suggestion VGR i now log if there is an exception and also I log when first enter the drop method.

Retested on MacOS and it doesnt even log the Started Drop method so it seems that MacOS is preventing the drag drop before it even gets to my code.

Whereas On MacOS it does work if you drop an image from Finder instead

What is causing the difference?

This is the class doing the work, key method is drop()

    package com.jthink.songkong.ui.startdialog.editsongs;
    
    import com.jthink.songkong.analyse.general.ArtworkHelper;
    import com.jthink.songkong.analyse.musicbrainz.RemoteArtworkLookup;
    import com.jthink.songkong.db.ImageCache;
    import com.jthink.songkong.db.SongCache;
    import com.jthink.songkong.preferences.UserOption;
    import com.jthink.songkong.preferences.UserPreferences;
    import com.jthink.songkong.ui.MainWindow;
    import com.jthink.songkong.ui.startdialog.imagefilters.ImageFileFilter;
    import com.jthink.songkong.ui.startdialog.imagefilters.ImageFilterFactory;
    import com.jthink.songlayer.CoverImage;
    
    import javax.imageio.IIOImage;
    import javax.imageio.ImageIO;
    import javax.imageio.ImageWriteParam;
    import javax.imageio.ImageWriter;
    import javax.imageio.stream.MemoryCacheImageOutputStream;
    import javax.swing.*;
    import java.awt.*;
    import java.awt.datatransfer.DataFlavor;
    import java.awt.datatransfer.Transferable;
    import java.awt.dnd.DnDConstants;
    import java.awt.dnd.DropTarget;
    import java.awt.dnd.DropTargetDropEvent;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.URI;
    import java.URL;
    import java.nio.file.Files;
    import java.util.logging.Level;
    
    import static com.jthink.songkong.ui.startdialog.editsongs.ArtworkTab.ARTWORK_SIZE;
    import static com.jthink.songkong.ui.startdialog.editsongs.ImageSizeDefaults.MAX_IMAGE_SIZE;
    import static com.jthink.songkong.ui.startdialog.editsongs.ImageSizeDefaults.MIN_IMAGE_SIZE;
    
    public class ArtworkDropTarget extends DropTarget
    {
        private static CoverImage   coverImage;
        private JLabel              displayArtwork;
    
        public static DataFlavor uriListFlavor;         //This is used by Linux for loading files
        public static DataFlavor imageFlavor;           //This is used by Firefox, when drag single image file
        public static DataFlavor urlFlavour;            //This is used by Firefox, when drag single image file
        public static DataFlavor pictImageFlavor;       //This is used by OSX for iTunes
        public static DataFlavor imageIconDataFlavor;   //When dragging from existingArtwork
        static
        {
            try
            {
                uriListFlavor   = new DataFlavor("text/uri-list;class=java.lang.String");
                urlFlavour = new DataFlavor("application/x-java-url;class=java.URL");
                imageFlavor = new DataFlavor("image/x-java-image;class=java.awt.Image");
                pictImageFlavor = new DataFlavor("image/x-pict;class=java.io.InputStream");
                imageIconDataFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
                        ";class=\""+javax.swing.Icon.class.getName() + "\"");
            }
            catch (ClassNotFoundException cne)
            {
                throw new RuntimeException("unable to create uri-list flavor in drop target:" + cne.getMessage());
            }
        }
    
        public ArtworkDropTarget(Component c, JLabel displayArtwork)
                throws HeadlessException
        {
            super(c, DnDConstants.ACTION_COPY, null, true, null);
            this.displayArtwork= displayArtwork;
        }
    
        public static void setCoverImage(CoverImage coverImage)
        {
            ArtworkDropTarget.coverImage = coverImage;
        }
    
        /**
         * When used as Drop target
         *
         * @param dtde
         */
        public synchronized void drop(DropTargetDropEvent dtde)
        {
            MainWindow.logger.severe("Starting Drop");
            dtde.acceptDrop(DnDConstants.ACTION_COPY);
            Transferable trans = dtde.getTransferable();
            try
            {
                if (trans.isDataFlavorSupported(imageIconDataFlavor))
                {
                    MainWindow.logger.severe("Received ImageIcon:"+trans.getTransferData(imageIconDataFlavor));
                    ImageIconWithOriginalImage icon = (ImageIconWithOriginalImage)trans.getTransferData(imageIconDataFlavor);
                    setExistingArtwork((BufferedImage)icon.getOriginalImage());
                }
                else if (trans.isDataFlavorSupported(DataFlavor.javaFileListFlavor) && ImageFilterFactory.getImageFilter().accept(((java.util.List<File>) trans.getTransferData(DataFlavor.javaFileListFlavor)).get(0)))
                {
                    MainWindow.logger.severe("Received Files:"+trans.getTransferData(DataFlavor.javaFileListFlavor));
                    File file = ((java.util.List<File>) trans.getTransferData(DataFlavor.javaFileListFlavor)).get(0);
                    setExistingArtwork(file);
                }
                else if (trans.isDataFlavorSupported(imageFlavor) && trans.getTransferData(ArtworkDropTarget.imageFlavor) instanceof BufferedImage)
                {
                    MainWindow.logger.severe("Received Image:"+trans.getTransferData(ArtworkDropTarget.imageFlavor));
                    setExistingArtwork((BufferedImage)trans.getTransferData(ArtworkDropTarget.imageFlavor));
                }
                else if (trans.isDataFlavorSupported(urlFlavour))
                {
                    URI imageUri = ((URL) trans.getTransferData(ArtworkDropTarget.urlFlavour)).toURI();
    
                    MainWindow.logger.severe("Received ImageUrl:" + imageUri);
                    URI subUri = GoogleImageSearch.getImageUrlFromImageSearchUrl(imageUri);
                    if (GoogleImageSearch.isGoogleImageSearch(imageUri) && subUri!=null)
                    {
                        MainWindow.logger.severe("Received ImageSubUrl:" + subUri);
                        setExistingArtwork(subUri);
                    }
                    else
                    {
                        MainWindow.logger.severe("Received ImageUrl:" + imageUri);
                        setExistingArtwork(imageUri);
                    }
                }
                else
                {
                    MainWindow.logger.severe("Unknown DataFlavor for ImageUrl");
                    for(DataFlavor next:trans.getTransferDataFlavors())
                    {
                        MainWindow.logger.severe(next.toString());
                    }
                }
                dtde.dropComplete(true);
            }
            catch(Exception ex)
            {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
                dtde.dropComplete(false);
            }
        }
    
        /**
         * TODO do we have to convert to JPG, cant we keep as PNG
         * @param image
         */
        private void setExistingArtwork(BufferedImage image)
        {
            try
            {
                byte[] imageData = writeCompressedJpegImageToByteArray(image, 1.0f);
                coverImage = ImageCache.saveNewCoverImage(image, imageData, "internet", com.jthink.songlayer.CoverImage.createKeyFromData(imageData),
                        MIN_IMAGE_SIZE,
                        UserOption.ARTWORK_MAX_SIZE.getIntValue()
                );
                SongCache.saveImagesForReport(coverImage, null);
                createNewArtworkImage(image);
            }
            catch (IOException ex)
            {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
    
        /**
         * Write Compressed Jpeg, use minimum compression of 1f for best quality
         *
         * @param bi
         * @param compressionLevel
         * @return
         * @throws IOException
         */
        private static byte[] writeCompressedJpegImageToByteArray(BufferedImage bi, float compressionLevel) throws IOException
        {
            ImageWriter imageWriter = ImageIO.getImageWritersByFormatName( "jpg").next();
            ImageWriteParam writeParams = imageWriter.getDefaultWriteParam();
            writeParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            writeParams.setCompressionQuality(compressionLevel);
    
            final ByteArrayOutputStream output = new ByteArrayOutputStream();
            imageWriter.setOutput( new MemoryCacheImageOutputStream(output));
            IIOImage outputImage = new IIOImage(bi, null, null);
            imageWriter.write(null, outputImage, writeParams);
            imageWriter.dispose();
    
            output.flush();
            final byte[] imageData = output.toByteArray();
            return imageData;
        }
    
        /**
         *
         * @param imageUri
         */
        private void setExistingArtwork(URI imageUri)
        {
            byte[] imageData = RemoteArtworkLookup.getDirectImage(imageUri.toString());
            if (imageData != null)
            {
                BufferedImage image = ArtworkHelper.createBufferedImageFromRawData(imageData, imageUri.toString());
                coverImage = ImageCache.saveNewCoverImage(image, imageData, "internet", com.jthink.songlayer.CoverImage.createKeyFromData(imageData),
                        UserPreferences.getInstance().getMinImageSize(),
                        (Integer) UserOption.ARTWORK_MAX_SIZE.getUserPref().getValue());
                SongCache.saveImagesForReport(coverImage, null);
                createNewArtworkImage(image);
            }
            else
            {
                MainWindow.logger.severe("Unable To Decode Image:"+imageUri.toString());
            }
        }
    
        /**
         *
         * @param file
         */
        private void setExistingArtwork(File file)
        {
            try
            {
                ImageFileFilter iff = ImageFilterFactory.getImageFilter();
                if(iff.accept(file))
                {
                    byte[] imageData = Files.readAllBytes(file.toPath());
                    BufferedImage image = ArtworkHelper.createBufferedImageFromRawData(imageData, file.getName());
                    coverImage = ImageCache.saveNewCoverImage(image, imageData, file.getName(),
                            com.jthink.songlayer.CoverImage.createKeyFromData(imageData),
                            UserPreferences.getInstance().getMinImageSize(),
                            (Integer) UserOption.ARTWORK_MAX_SIZE.getUserPref().getValue());
                    SongCache.saveImagesForReport(coverImage, null);
                    createNewArtworkImage(image);
                }
            }
            catch(Exception ex)
            {
                MainWindow.logger.log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
    
        /**
         * Update new artwork label with version of this image
         *
         * @param image
         */
        private void createNewArtworkImage(BufferedImage image)
        {
            BufferedImage ret = new BufferedImage(ARTWORK_SIZE, ARTWORK_SIZE, BufferedImage.TYPE_INT_RGB);
            ret.getGraphics().drawImage(image, 0, 0, ARTWORK_SIZE, ARTWORK_SIZE, null);
            ImageIcon ii = new ImageIcon(ret);
            displayArtwork.setIcon(ii);
            displayArtwork.setText(image.getWidth() + " x " + image.getHeight());
            ArtworkFont.formatArtworkLabel(displayArtwork);
        }
    
        public static CoverImage getCoverImage()
        {
            return coverImage;
        }
    }

Update

Removed the synchonized keyword as suggested by VGR, now gets into drop but throws an Exception

    01/2025 12.52.09:GMT:ArtworkDropTarget:drop:SEVERE: Cannot invoke "String.length()" because "spec" is null
    java.awt.dnd.InvalidDnDOperationException: Cannot invoke "String.length()" because "spec" is null
        at java.desktop/sun.awt.dnd.SunDropTargetContextPeer.getTransferData(SunDropTargetContextPeer.java:274)
        at java.desktop/sun.awt.datatransfer.TransferableProxy.getTransferData(TransferableProxy.java:73)
        at java.desktop/java.awt.dnd.DropTargetContext$TransferableProxy.getTransferData(DropTargetContext.java:387)
        at com.jthink.songkong.ui.startdialog.editsongs.ArtworkDropTarget.drop(ArtworkDropTarget.java:109)
        at java.desktop/sun.awt.dnd.SunDropTargetContextPeer.processDropMessage(SunDropTargetContextPeer.java:548)
        at java.desktop/sun.lwawt.macosx.CDropTargetContextPeer.processDropMessage(CDropTargetContextPeer.java:129)
        at java.desktop/sun.awt.dnd.SunDropTargetContextPeer$EventDispatcher.dispatchDropEvent(SunDropTargetContextPeer.java:864)
        at java.desktop/sun.awt.dnd.SunDropTargetContextPeer$EventDispatcher.dispatchEvent(SunDropTargetContextPeer.java:788)
        at java.desktop/sun.awt.dnd.SunDropTargetEvent.dispatch(SunDropTargetEvent.java:48)
        at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4861)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
        at java.desktop/java.awt.LightweightDispatcher.processDropTargetEvent(Container.java:4649)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4511)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
        at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
        at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
        at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
        at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Possible related issue -

Update 2

As also suggsted by VGR removed multiple calls to getTransferData() so can only ever be called once per invocation of method, made no difference.

Update 3

Looked at the data flavors available and swapped round the if statement so it looks for

 uriListFlavor   = new DataFlavor("text/uri-list;class=java.lang.String");

before

urlFlavour = new DataFlavor("application/x-java-url;class=java.URL");

and then it works.

So it is my opnion that since before failed calling getTransferData() with

java.awt.dnd.InvalidDnDOperationException: Cannot invoke "String.length()" because "spec" is null
    at java.desktop/sun.awt.dnd.SunDropTargetContextPeer.getTransferData(SunDropTargetContextPeer.java:274)
    at java.desktop/sun.awt.datatransfer.TransferableProxy.getTransferData(TransferableProxy.java:73)
    at java.desktop/java.awt.dnd.DropTargetContext$TransferableProxy.getTransferData(DropTargetContext.java:387)

for a flavor it had reported it had and before I did any other processing that this is actually a bug in the code that implements Java for MacOS

本文标签: Dropping of image url from webbrowser on Java app not working for macOSStack Overflow