import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

import javax.swing.*;

/**
 * If dragGestureRecognized() takes a while to complete and if user performs a drag quickly,
 * an exception is thrown from DropTargetListener.dragEnter when it calls
 * DropTargetDragEvent.getTransferable().
 * <p>
 * This class introduces a delay in dragGestureRecognized() to cause the exception.
 * <p>
 * Start this application and quickly drag the cyan square from one panel to another,
 * you'll see an exception in the console.
 * <p>
 * For more information see https://bugs.openjdk.java.net/browse/JDK-8024061
 */
public class JDK8024061 {
    private static final DataFlavor DropObjectFlavor;
    private static final int DELAY = 1000;

    static {
        DataFlavor flavor = null;
        try {
            flavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        DropObjectFlavor = flavor;
    }

    JDK8024061() {
        final JFrame frame = new JFrame("D'n'D - JDK-8024061");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        Dimension d = new Dimension(200, 200);

        final DnDPanel panel1 = new DnDPanel(Color.yellow);
        final DnDPanel panel2 = new DnDPanel(Color.pink);

        panel1.setPreferredSize(d);
        panel2.setPreferredSize(d);

        Container content = frame.getContentPane();
        content.setLayout(new GridLayout(1, 2, 5, 5));
        content.add(panel1);
        content.add(panel2);

        frame.pack();

        DropObject drop = new DropObject();
        drop.place(panel1, new Point(75, 75));

        frame.setVisible(true);
    }

    public static void main(String[] args) throws AWTException, InvocationTargetException, InterruptedException {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                new JDK8024061();
            }
        });
    }

    class DropObject implements Transferable {
        DnDPanel panel;
        Color color = Color.CYAN;
        int width = 50;
        int height = 50;
        int x;
        int y;

        void draw(Graphics2D g) {
            Color savedColor = g.getColor();
            g.setColor(color);
            g.fillRect(x, y, width, height);
            g.setColor(Color.lightGray);
            g.drawRect(x, y, width, height);
            g.setColor(savedColor);
        }

        boolean contains(int x, int y) {
            return (x > this.x && x < this.x + width)
                    && (y > this.y && y < this.y + height);
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{DropObjectFlavor};
        }

        void place(DnDPanel panel, Point location) {
            if (panel != this.panel) {
                x = location.x;
                y = location.y;
                if (this.panel != null) {
                    this.panel.setDropObject(null);
                    this.panel.repaint();
                }
                this.panel = panel;
                this.panel.setDropObject(this);
                this.panel.repaint();
            }
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return DropObjectFlavor.equals(flavor);
        }

        @Override
        public Object getTransferData(DataFlavor flavor)
                throws UnsupportedFlavorException, IOException {
            if (isDataFlavorSupported(flavor)) {
                return this;
            } else {
                throw new UnsupportedFlavorException(flavor);
            }
        }
    }

    class DnDPanel extends JPanel {
        DropObject dropObject;
        final DragSource dragSource;
        final DropTarget dropTarget;
        final Color color;
        final DragGestureListener dgListener;
        final DragSourceListener dsListener;
        final DropTargetListener dtListener;

        DnDPanel(Color color) {
            this.color = color;
            this.dragSource = DragSource.getDefaultDragSource();
            dgListener = new DragGestureListener() {
                @Override
                public void dragGestureRecognized(DragGestureEvent dge) {
                    Point location = dge.getDragOrigin();
                    if (dropObject != null && dropObject.contains(location.x, location.y)) {
                        dragSource.startDrag(dge, DragSource.DefaultCopyNoDrop, dropObject, dsListener);
                        try {
                            Thread.sleep(DELAY);
                        } catch (InterruptedException e) {
                        }
                    }
                }
            };

            dsListener = new DragSourceListener() {
                @Override
                public void dragEnter(DragSourceDragEvent dsde) {
                }

                @Override
                public void dragOver(DragSourceDragEvent dsde) {
                }

                @Override
                public void dropActionChanged(DragSourceDragEvent dsde) {
                }

                @Override
                public void dragExit(DragSourceEvent dse) {
                }

                @Override
                public void dragDropEnd(DragSourceDropEvent dsde) {
                }
            };

            dtListener = new DropTargetListener() {
                @Override
                public void dragEnter(DropTargetDragEvent dtde) {
                    if (dropObject != null) {
                        dtde.rejectDrag();
                        return;
                    }
                    dtde.acceptDrag(DnDConstants.ACTION_MOVE);
                    try {
                        Transferable t = dtde.getTransferable();
                        Object data = t.getTransferData(DropObjectFlavor);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void dragOver(DropTargetDragEvent dtde) {
                    if (dropObject != null) {
                        dtde.rejectDrag();
                        return;
                    }
                    dtde.acceptDrag(DnDConstants.ACTION_MOVE);
                }

                @Override
                public void dropActionChanged(DropTargetDragEvent dtde) {
                }

                @Override
                public void dragExit(DropTargetEvent dte) {
                }

                @Override
                public void drop(DropTargetDropEvent dtde) {
                    if (dropObject != null) {
                        dtde.rejectDrop();
                        return;
                    }
                    try {
                        dtde.acceptDrop(DnDConstants.ACTION_MOVE);
                        Transferable t = dtde.getTransferable();
                        DropObject dropObject = (DropObject) t.getTransferData(DropObjectFlavor);
                        Point location = dtde.getLocation();
                        dropObject.place(DnDPanel.this, location);
                        dtde.dropComplete(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            };

            dragSource.createDefaultDragGestureRecognizer(this,
                    DnDConstants.ACTION_MOVE, dgListener);

            dropTarget = new DropTarget(this, DnDConstants.ACTION_MOVE, dtListener, true);

        }

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Color savedColor = g.getColor();
            g.setColor(color);
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(savedColor);
            if (dropObject != null) {
                dropObject.draw((Graphics2D) g);
            }
        }

        void setDropObject(DropObject dropObject) {
            this.dropObject = dropObject;
        }

    }
}
