diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/sun/awt/dnd/SunDropTargetContextPeer.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,903 @@ +/* + * Copyright 2000-2007 Sun Microsystems, Inc. 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. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.awt.dnd; + +import java.awt.Component; +import java.awt.Point; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; + +import java.awt.dnd.DnDConstants; + +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetContext; +import java.awt.dnd.DropTargetListener; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.InvalidDnDOperationException; + +import java.awt.dnd.peer.DropTargetContextPeer; + +import java.util.HashSet; +import java.util.Map; +import java.util.Arrays; + +import java.util.logging.*; + +import java.io.IOException; +import java.io.InputStream; + +import sun.awt.AppContext; +import sun.awt.SunToolkit; +import sun.awt.datatransfer.DataTransferer; +import sun.awt.datatransfer.ToolkitThreadBlockedHandler; + +/** + *

+ * The SunDropTargetContextPeer class is the generic class responsible for handling + * the interaction between a windowing systems DnD system and Java. + *

+ * + * @since JDK1.3.1 + * + */ + +public abstract class SunDropTargetContextPeer implements DropTargetContextPeer, Transferable { + + /* + * A boolean constant that requires the peer to wait until the + * SunDropTargetEvent is processed and return the status back + * to the native code. + */ + public static final boolean DISPATCH_SYNC = true; + private DropTarget currentDT; + private DropTargetContext currentDTC; + private long[] currentT; + private int currentA; // target actions + private int currentSA; // source actions + private int currentDA; // current drop action + private int previousDA; + + private long nativeDragContext; + + private Transferable local; + + private boolean dragRejected = false; + + protected int dropStatus = STATUS_NONE; + protected boolean dropComplete = false; + + /* + * global lock + */ + + protected static final Object _globalLock = new Object(); + + private static final Logger dndLog = Logger.getLogger("sun.awt.dnd.SunDropTargetContextPeer"); + + /* + * a primitive mechanism for advertising intra-JVM Transferables + */ + + protected static Transferable currentJVMLocalSourceTransferable = null; + + public static void setCurrentJVMLocalSourceTransferable(Transferable t) throws InvalidDnDOperationException { + synchronized(_globalLock) { + if (t != null && currentJVMLocalSourceTransferable != null) { + throw new InvalidDnDOperationException(); + } else { + currentJVMLocalSourceTransferable = t; + } + } + } + + /** + * obtain the transferable iff the operation is in the same VM + */ + + private static Transferable getJVMLocalSourceTransferable() { + return currentJVMLocalSourceTransferable; + } + + /* + * constants used by dropAccept() or dropReject() + */ + + protected final static int STATUS_NONE = 0; // none pending + protected final static int STATUS_WAIT = 1; // drop pending + protected final static int STATUS_ACCEPT = 2; + protected final static int STATUS_REJECT = -1; + + /** + * create the peer + */ + + public SunDropTargetContextPeer() { + super(); + } + + /** + * @return the DropTarget associated with this peer + */ + + public DropTarget getDropTarget() { return currentDT; } + + /** + * @param actions set the current actions + */ + + public synchronized void setTargetActions(int actions) { + currentA = actions & + (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); + } + + /** + * @return the current target actions + */ + + public int getTargetActions() { + return currentA; + } + + /** + * get the Transferable associated with the drop + */ + + public Transferable getTransferable() { + return this; + } + + /** + * @return current DataFlavors available + */ + // NOTE: This method may be called by privileged threads. + // DO NOT INVOKE CLIENT CODE ON THIS THREAD! + + public DataFlavor[] getTransferDataFlavors() { + final Transferable localTransferable = local; + + if (localTransferable != null) { + return localTransferable.getTransferDataFlavors(); + } else { + return DataTransferer.getInstance().getFlavorsForFormatsAsArray + (currentT, DataTransferer.adaptFlavorMap + (currentDT.getFlavorMap())); + } + } + + /** + * @return if the flavor is supported + */ + + public boolean isDataFlavorSupported(DataFlavor df) { + Transferable localTransferable = local; + + if (localTransferable != null) { + return localTransferable.isDataFlavorSupported(df); + } else { + return DataTransferer.getInstance().getFlavorsForFormats + (currentT, DataTransferer.adaptFlavorMap + (currentDT.getFlavorMap())). + containsKey(df); + } + } + + /** + * @return the data + */ + + public Object getTransferData(DataFlavor df) + throws UnsupportedFlavorException, IOException, + InvalidDnDOperationException + { + Long lFormat = null; + Transferable localTransferable = local; + + if (localTransferable != null) { + return localTransferable.getTransferData(df); + } + + if (dropStatus != STATUS_ACCEPT || dropComplete) { + throw new InvalidDnDOperationException("No drop current"); + } + + Map flavorMap = DataTransferer.getInstance().getFlavorsForFormats + (currentT, DataTransferer.adaptFlavorMap + (currentDT.getFlavorMap())); + + lFormat = (Long)flavorMap.get(df); + if (lFormat == null) { + throw new UnsupportedFlavorException(df); + } + + if (df.isRepresentationClassRemote() && + currentDA != DnDConstants.ACTION_LINK) { + throw new InvalidDnDOperationException("only ACTION_LINK is permissable for transfer of java.rmi.Remote objects"); + } + + final long format = lFormat.longValue(); + Object ret = getNativeData(format); + + if (ret instanceof byte[]) { + try { + return DataTransferer.getInstance(). + translateBytes((byte[])ret, df, format, this); + } catch (IOException e) { + throw new InvalidDnDOperationException(e.getMessage()); + } + } else if (ret instanceof InputStream) { + try { + return DataTransferer.getInstance(). + translateStream((InputStream)ret, df, format, this); + } catch (IOException e) { + throw new InvalidDnDOperationException(e.getMessage()); + } + } else { + throw new IOException("no native data was transfered"); + } + } + + protected abstract Object getNativeData(long format) + throws IOException; + + /** + * @return if the transfer is a local one + */ + public boolean isTransferableJVMLocal() { + return local != null || getJVMLocalSourceTransferable() != null; + } + + private int handleEnterMessage(final Component component, + final int x, final int y, + final int dropAction, + final int actions, final long[] formats, + final long nativeCtxt) { + return postDropTargetEvent(component, x, y, dropAction, actions, + formats, nativeCtxt, + SunDropTargetEvent.MOUSE_ENTERED, + SunDropTargetContextPeer.DISPATCH_SYNC); + } + + /** + * actual processing on EventQueue Thread + */ + + protected void processEnterMessage(SunDropTargetEvent event) { + Component c = (Component)event.getSource(); + DropTarget dt = c.getDropTarget(); + Point hots = event.getPoint(); + + local = getJVMLocalSourceTransferable(); + + if (currentDTC != null) { // some wreckage from last time + currentDTC.removeNotify(); + currentDTC = null; + } + + if (c.isShowing() && dt != null && dt.isActive()) { + currentDT = dt; + currentDTC = currentDT.getDropTargetContext(); + + currentDTC.addNotify(this); + + currentA = dt.getDefaultActions(); + + try { + ((DropTargetListener)dt).dragEnter(new DropTargetDragEvent(currentDTC, + hots, + currentDA, + currentSA)); + } catch (Exception e) { + e.printStackTrace(); + currentDA = DnDConstants.ACTION_NONE; + } + } else { + currentDT = null; + currentDTC = null; + currentDA = DnDConstants.ACTION_NONE; + currentSA = DnDConstants.ACTION_NONE; + currentA = DnDConstants.ACTION_NONE; + } + + } + + /** + * upcall to handle exit messages + */ + + private void handleExitMessage(final Component component, + final long nativeCtxt) { + /* + * Even though the return value is irrelevant for this event, it is + * dispatched synchronously to fix 4393148 properly. + */ + postDropTargetEvent(component, 0, 0, DnDConstants.ACTION_NONE, + DnDConstants.ACTION_NONE, null, nativeCtxt, + SunDropTargetEvent.MOUSE_EXITED, + SunDropTargetContextPeer.DISPATCH_SYNC); + } + + /** + * + */ + + protected void processExitMessage(SunDropTargetEvent event) { + Component c = (Component)event.getSource(); + DropTarget dt = c.getDropTarget(); + DropTargetContext dtc = null; + + if (dt == null) { + currentDT = null; + currentT = null; + + if (currentDTC != null) { + currentDTC.removeNotify(); + } + + currentDTC = null; + + return; + } + + if (dt != currentDT) { + + if (currentDTC != null) { + currentDTC.removeNotify(); + } + + currentDT = dt; + currentDTC = dt.getDropTargetContext(); + + currentDTC.addNotify(this); + } + + dtc = currentDTC; + + if (dt.isActive()) try { + ((DropTargetListener)dt).dragExit(new DropTargetEvent(dtc)); + } catch (Exception e) { + e.printStackTrace(); + } finally { + currentA = DnDConstants.ACTION_NONE; + currentSA = DnDConstants.ACTION_NONE; + currentDA = DnDConstants.ACTION_NONE; + currentDT = null; + currentT = null; + + currentDTC.removeNotify(); + currentDTC = null; + + local = null; + + dragRejected = false; + } + } + + private int handleMotionMessage(final Component component, + final int x, final int y, + final int dropAction, + final int actions, final long[] formats, + final long nativeCtxt) { + return postDropTargetEvent(component, x, y, dropAction, actions, + formats, nativeCtxt, + SunDropTargetEvent.MOUSE_DRAGGED, + SunDropTargetContextPeer.DISPATCH_SYNC); + } + + /** + * + */ + + protected void processMotionMessage(SunDropTargetEvent event, + boolean operationChanged) { + Component c = (Component)event.getSource(); + Point hots = event.getPoint(); + int id = event.getID(); + DropTarget dt = c.getDropTarget(); + DropTargetContext dtc = null; + + if (c.isShowing() && (dt != null) && dt.isActive()) { + if (currentDT != dt) { + if (currentDTC != null) { + currentDTC.removeNotify(); + } + + currentDT = dt; + currentDTC = null; + } + + dtc = currentDT.getDropTargetContext(); + if (dtc != currentDTC) { + if (currentDTC != null) { + currentDTC.removeNotify(); + } + + currentDTC = dtc; + currentDTC.addNotify(this); + } + + currentA = currentDT.getDefaultActions(); + + try { + DropTargetDragEvent dtde = new DropTargetDragEvent(dtc, + hots, + currentDA, + currentSA); + DropTargetListener dtl = (DropTargetListener)dt; + if (operationChanged) { + dtl.dropActionChanged(dtde); + } else { + dtl.dragOver(dtde); + } + + if (dragRejected) { + currentDA = DnDConstants.ACTION_NONE; + } + } catch (Exception e) { + e.printStackTrace(); + currentDA = DnDConstants.ACTION_NONE; + } + } else { + currentDA = DnDConstants.ACTION_NONE; + } + } + + /** + * upcall to handle the Drop message + */ + + private void handleDropMessage(final Component component, + final int x, final int y, + final int dropAction, final int actions, + final long[] formats, + final long nativeCtxt) { + postDropTargetEvent(component, x, y, dropAction, actions, + formats, nativeCtxt, + SunDropTargetEvent.MOUSE_DROPPED, + !SunDropTargetContextPeer.DISPATCH_SYNC); + } + + /** + * + */ + + protected void processDropMessage(SunDropTargetEvent event) { + Component c = (Component)event.getSource(); + Point hots = event.getPoint(); + DropTarget dt = c.getDropTarget(); + + dropStatus = STATUS_WAIT; // drop pending ACK + dropComplete = false; + + if (c.isShowing() && dt != null && dt.isActive()) { + DropTargetContext dtc = dt.getDropTargetContext(); + + currentDT = dt; + + if (currentDTC != null) { + currentDTC.removeNotify(); + } + + currentDTC = dtc; + currentDTC.addNotify(this); + currentA = dt.getDefaultActions(); + + synchronized(_globalLock) { + if ((local = getJVMLocalSourceTransferable()) != null) + setCurrentJVMLocalSourceTransferable(null); + } + + try { + ((DropTargetListener)dt).drop(new DropTargetDropEvent(dtc, + hots, + currentDA, + currentSA, + local != null)); + } finally { + if (dropStatus == STATUS_WAIT) { + rejectDrop(); + } else if (dropComplete == false) { + dropComplete(false); + } + } + } else { + rejectDrop(); + } + } + + protected int postDropTargetEvent(final Component component, + final int x, final int y, + final int dropAction, + final int actions, + final long[] formats, + final long nativeCtxt, + final int eventID, + final boolean dispatchType) { + AppContext appContext = SunToolkit.targetToAppContext(component); + + EventDispatcher dispatcher = + new EventDispatcher(this, dropAction, actions, formats, nativeCtxt, + dispatchType); + + SunDropTargetEvent event = + new SunDropTargetEvent(component, eventID, x, y, dispatcher); + + if (dispatchType == SunDropTargetContextPeer.DISPATCH_SYNC) { + DataTransferer.getInstance().getToolkitThreadBlockedHandler().lock(); + } + + // schedule callback + SunToolkit.postEvent(appContext, event); + + eventPosted(event); + + if (dispatchType == SunDropTargetContextPeer.DISPATCH_SYNC) { + while (!dispatcher.isDone()) { + DataTransferer.getInstance().getToolkitThreadBlockedHandler().enter(); + } + + DataTransferer.getInstance().getToolkitThreadBlockedHandler().unlock(); + + // return target's response + return dispatcher.getReturnValue(); + } else { + return 0; + } + } + + /** + * acceptDrag + */ + + public synchronized void acceptDrag(int dragOperation) { + if (currentDT == null) { + throw new InvalidDnDOperationException("No Drag pending"); + } + currentDA = mapOperation(dragOperation); + if (currentDA != DnDConstants.ACTION_NONE) { + dragRejected = false; + } + } + + /** + * rejectDrag + */ + + public synchronized void rejectDrag() { + if (currentDT == null) { + throw new InvalidDnDOperationException("No Drag pending"); + } + currentDA = DnDConstants.ACTION_NONE; + dragRejected = true; + } + + /** + * acceptDrop + */ + + public synchronized void acceptDrop(int dropOperation) { + if (dropOperation == DnDConstants.ACTION_NONE) + throw new IllegalArgumentException("invalid acceptDrop() action"); + + if (dropStatus != STATUS_WAIT) { + throw new InvalidDnDOperationException("invalid acceptDrop()"); + } + + currentDA = currentA = mapOperation(dropOperation & currentSA); + + dropStatus = STATUS_ACCEPT; + dropComplete = false; + } + + /** + * reject Drop + */ + + public synchronized void rejectDrop() { + if (dropStatus != STATUS_WAIT) { + throw new InvalidDnDOperationException("invalid rejectDrop()"); + } + dropStatus = STATUS_REJECT; + /* + * Fix for 4285634. + * The target rejected the drop means that it doesn't perform any + * drop action. This change is to make Solaris behavior consistent + * with Win32. + */ + currentDA = DnDConstants.ACTION_NONE; + dropComplete(false); + } + + /** + * mapOperation + */ + + private int mapOperation(int operation) { + int[] operations = { + DnDConstants.ACTION_MOVE, + DnDConstants.ACTION_COPY, + DnDConstants.ACTION_LINK, + }; + int ret = DnDConstants.ACTION_NONE; + + for (int i = 0; i < operations.length; i++) { + if ((operation & operations[i]) == operations[i]) { + ret = operations[i]; + break; + } + } + + return ret; + } + + /** + * signal drop complete + */ + + public synchronized void dropComplete(boolean success) { + if (dropStatus == STATUS_NONE) { + throw new InvalidDnDOperationException("No Drop pending"); + } + + if (currentDTC != null) currentDTC.removeNotify(); + + currentDT = null; + currentDTC = null; + currentT = null; + currentA = DnDConstants.ACTION_NONE; + + synchronized(_globalLock) { + currentJVMLocalSourceTransferable = null; + } + + dropStatus = STATUS_NONE; + dropComplete = true; + + try { + doDropDone(success, currentDA, local != null); + } finally { + currentDA = DnDConstants.ACTION_NONE; + // The native context is invalid after the drop is done. + // Clear the reference to prohibit access. + nativeDragContext = 0; + } + } + + protected abstract void doDropDone(boolean success, + int dropAction, boolean isLocal); + + protected synchronized long getNativeDragContext() { + return nativeDragContext; + } + + protected void eventPosted(SunDropTargetEvent e) {} + + protected void eventProcessed(SunDropTargetEvent e, int returnValue, + boolean dispatcherDone) {} + + protected static class EventDispatcher { + + private final SunDropTargetContextPeer peer; + + // context fields + private final int dropAction; + private final int actions; + private final long[] formats; + private long nativeCtxt; + private final boolean dispatchType; + private boolean dispatcherDone = false; + + // dispatcher state fields + private int returnValue = 0; + // set of events to be dispatched by this dispatcher + private final HashSet eventSet = new HashSet(3); + + static final ToolkitThreadBlockedHandler handler = + DataTransferer.getInstance().getToolkitThreadBlockedHandler(); + + EventDispatcher(SunDropTargetContextPeer peer, + int dropAction, + int actions, + long[] formats, + long nativeCtxt, + boolean dispatchType) { + + this.peer = peer; + this.nativeCtxt = nativeCtxt; + this.dropAction = dropAction; + this.actions = actions; + this.formats = + (null == formats) ? null : Arrays.copyOf(formats, formats.length); + this.dispatchType = dispatchType; + } + + void dispatchEvent(SunDropTargetEvent e) { + int id = e.getID(); + + switch (id) { + case SunDropTargetEvent.MOUSE_ENTERED: + dispatchEnterEvent(e); + break; + case SunDropTargetEvent.MOUSE_DRAGGED: + dispatchMotionEvent(e); + break; + case SunDropTargetEvent.MOUSE_EXITED: + dispatchExitEvent(e); + break; + case SunDropTargetEvent.MOUSE_DROPPED: + dispatchDropEvent(e); + break; + default: + throw new InvalidDnDOperationException(); + } + } + + private void dispatchEnterEvent(SunDropTargetEvent e) { + synchronized (peer) { + + // store the drop action here to track operation changes + peer.previousDA = dropAction; + + // setup peer context + peer.nativeDragContext = nativeCtxt; + peer.currentT = formats; + peer.currentSA = actions; + peer.currentDA = dropAction; + // To allow data retrieval. + peer.dropStatus = STATUS_ACCEPT; + peer.dropComplete = false; + + try { + peer.processEnterMessage(e); + } finally { + peer.dropStatus = STATUS_NONE; + } + + setReturnValue(peer.currentDA); + } + } + + private void dispatchMotionEvent(SunDropTargetEvent e) { + synchronized (peer) { + + boolean operationChanged = peer.previousDA != dropAction; + peer.previousDA = dropAction; + + // setup peer context + peer.nativeDragContext = nativeCtxt; + peer.currentT = formats; + peer.currentSA = actions; + peer.currentDA = dropAction; + // To allow data retrieval. + peer.dropStatus = STATUS_ACCEPT; + peer.dropComplete = false; + + try { + peer.processMotionMessage(e, operationChanged); + } finally { + peer.dropStatus = STATUS_NONE; + } + + setReturnValue(peer.currentDA); + } + } + + private void dispatchExitEvent(SunDropTargetEvent e) { + synchronized (peer) { + + // setup peer context + peer.nativeDragContext = nativeCtxt; + + peer.processExitMessage(e); + } + } + + private void dispatchDropEvent(SunDropTargetEvent e) { + synchronized (peer) { + + // setup peer context + peer.nativeDragContext = nativeCtxt; + peer.currentT = formats; + peer.currentSA = actions; + peer.currentDA = dropAction; + + peer.processDropMessage(e); + } + } + + void setReturnValue(int ret) { + returnValue = ret; + } + + int getReturnValue() { + return returnValue; + } + + boolean isDone() { + return eventSet.isEmpty(); + } + + void registerEvent(SunDropTargetEvent e) { + handler.lock(); + if (!eventSet.add(e) && dndLog.isLoggable(Level.FINE)) { + dndLog.log(Level.FINE, "Event is already registered: " + e); + } + handler.unlock(); + } + + void unregisterEvent(SunDropTargetEvent e) { + handler.lock(); + try { + if (!eventSet.remove(e)) { + // This event has already been unregistered. + return; + } + if (eventSet.isEmpty()) { + if (!dispatcherDone && dispatchType == DISPATCH_SYNC) { + handler.exit(); + } + dispatcherDone = true; + } + } finally { + handler.unlock(); + } + + try { + peer.eventProcessed(e, returnValue, dispatcherDone); + } finally { + /* + * Clear the reference to the native context if all copies of + * the original event are processed. + */ + if (dispatcherDone) { + nativeCtxt = 0; + // Fix for 6342381 + peer.nativeDragContext = 0; + + } + } + } + + public void unregisterAllEvents() { + Object[] events = null; + handler.lock(); + try { + events = eventSet.toArray(); + } finally { + handler.unlock(); + } + + if (events != null) { + for (int i = 0; i < events.length; i++) { + unregisterEvent((SunDropTargetEvent)events[i]); + } + } + } + } +}