1 /*
   2  * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.dnd;
  27 
  28 import java.awt.Component;
  29 import java.awt.Point;
  30 
  31 import java.awt.datatransfer.DataFlavor;
  32 import java.awt.datatransfer.Transferable;
  33 import java.awt.datatransfer.UnsupportedFlavorException;
  34 
  35 import java.awt.dnd.DnDConstants;
  36 
  37 import java.awt.dnd.DropTarget;
  38 import java.awt.dnd.DropTargetContext;
  39 import java.awt.dnd.DropTargetListener;
  40 import java.awt.dnd.DropTargetEvent;
  41 import java.awt.dnd.DropTargetDragEvent;
  42 import java.awt.dnd.DropTargetDropEvent;
  43 import java.awt.dnd.InvalidDnDOperationException;
  44 
  45 import java.awt.dnd.peer.DropTargetContextPeer;
  46 
  47 import java.util.HashSet;
  48 import java.util.Map;
  49 import java.util.Arrays;
  50 
  51 import sun.awt.AWTAccessor;
  52 import sun.awt.AWTAccessor.DropTargetContextAccessor;
  53 import sun.util.logging.PlatformLogger;
  54 
  55 import java.io.IOException;
  56 import java.io.InputStream;
  57 
  58 import sun.awt.AppContext;
  59 import sun.awt.AWTPermissions;
  60 import sun.awt.SunToolkit;
  61 import sun.awt.datatransfer.DataTransferer;
  62 import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
  63 
  64 /**
  65  * <p>
  66  * The SunDropTargetContextPeer class is the generic class responsible for handling
  67  * the interaction between a windowing systems DnD system and Java.
  68  * </p>
  69  *
  70  * @since 1.3.1
  71  *
  72  */
  73 
  74 public abstract class SunDropTargetContextPeer implements DropTargetContextPeer, Transferable {
  75 
  76     /*
  77      * A boolean constant that requires the peer to wait until the
  78      * SunDropTargetEvent is processed and return the status back
  79      * to the native code.
  80      */
  81     public static final boolean DISPATCH_SYNC = true;
  82     private   DropTarget              currentDT;
  83     private   DropTargetContext       currentDTC;
  84     private   long[]                  currentT;
  85     private   int                     currentA;   // target actions
  86     private   int                     currentSA;  // source actions
  87     private   int                     currentDA;  // current drop action
  88     private   int                     previousDA;
  89 
  90     private   long                    nativeDragContext;
  91 
  92     private   Transferable            local;
  93 
  94     private boolean                   dragRejected = false;
  95 
  96     protected int                     dropStatus   = STATUS_NONE;
  97     protected boolean                 dropComplete = false;
  98 
  99     // The flag is used to monitor whether the drop action is
 100     // handled by a user. That allows to distinct during
 101     // which operation getTransferData() method is invoked.
 102     boolean                           dropInProcess = false;
 103 
 104     /*
 105      * global lock
 106      */
 107 
 108     protected static final Object _globalLock = new Object();
 109 
 110     private static final PlatformLogger dndLog = PlatformLogger.getLogger("sun.awt.dnd.SunDropTargetContextPeer");
 111 
 112     /*
 113      * a primitive mechanism for advertising intra-JVM Transferables
 114      */
 115 
 116     protected static Transferable         currentJVMLocalSourceTransferable = null;
 117 
 118     public static void setCurrentJVMLocalSourceTransferable(Transferable t) throws InvalidDnDOperationException {
 119         synchronized(_globalLock) {
 120             if (t != null && currentJVMLocalSourceTransferable != null) {
 121                     throw new InvalidDnDOperationException();
 122             } else {
 123                 currentJVMLocalSourceTransferable = t;
 124             }
 125         }
 126     }
 127 
 128     /**
 129      * obtain the transferable iff the operation is in the same VM
 130      */
 131 
 132     private static Transferable getJVMLocalSourceTransferable() {
 133         return currentJVMLocalSourceTransferable;
 134     }
 135 
 136     /*
 137      * constants used by dropAccept() or dropReject()
 138      */
 139 
 140     protected final static int STATUS_NONE   =  0; // none pending
 141     protected final static int STATUS_WAIT   =  1; // drop pending
 142     protected final static int STATUS_ACCEPT =  2;
 143     protected final static int STATUS_REJECT = -1;
 144 
 145     /**
 146      * create the peer
 147      */
 148 
 149     public SunDropTargetContextPeer() {
 150         super();
 151     }
 152 
 153     /**
 154      * @return the DropTarget associated with this peer
 155      */
 156 
 157     public DropTarget getDropTarget() { return currentDT; }
 158 
 159     /**
 160      * @param actions set the current actions
 161      */
 162 
 163     public synchronized void setTargetActions(int actions) {
 164         currentA = actions &
 165             (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK);
 166     }
 167 
 168     /**
 169      * @return the current target actions
 170      */
 171 
 172     public int getTargetActions() {
 173         return currentA;
 174     }
 175 
 176     /**
 177      * get the Transferable associated with the drop
 178      */
 179 
 180     public Transferable getTransferable() {
 181         return this;
 182     }
 183 
 184     /**
 185      * @return current DataFlavors available
 186      */
 187     // NOTE: This method may be called by privileged threads.
 188     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
 189 
 190     public DataFlavor[] getTransferDataFlavors() {
 191         final Transferable    localTransferable = local;
 192 
 193         if (localTransferable != null) {
 194             return localTransferable.getTransferDataFlavors();
 195         } else {
 196             return DataTransferer.getInstance().getFlavorsForFormatsAsArray
 197                     (currentT, DataTransferer.adaptFlavorMap
 198                             (currentDT.getFlavorMap()));
 199         }
 200     }
 201 
 202     /**
 203      * @return if the flavor is supported
 204      */
 205 
 206     public boolean isDataFlavorSupported(DataFlavor df) {
 207         Transferable localTransferable = local;
 208 
 209         if (localTransferable != null) {
 210             return localTransferable.isDataFlavorSupported(df);
 211         } else {
 212             return DataTransferer.getInstance().getFlavorsForFormats
 213                 (currentT, DataTransferer.adaptFlavorMap
 214                     (currentDT.getFlavorMap())).
 215                 containsKey(df);
 216         }
 217     }
 218 
 219     /**
 220      * @return the data
 221      */
 222 
 223     public Object getTransferData(DataFlavor df)
 224       throws UnsupportedFlavorException, IOException,
 225         InvalidDnDOperationException
 226     {
 227 
 228         SecurityManager sm = System.getSecurityManager();
 229         try {
 230             if (!dropInProcess && sm != null) {
 231                 sm.checkPermission(AWTPermissions.ACCESS_CLIPBOARD_PERMISSION);
 232             }
 233         } catch (Exception e) {
 234             Thread currentThread = Thread.currentThread();
 235             currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, e);
 236             return null;
 237         }
 238 
 239         Long lFormat = null;
 240         Transferable localTransferable = local;
 241 
 242         if (localTransferable != null) {
 243             return localTransferable.getTransferData(df);
 244         }
 245 
 246         if (dropStatus != STATUS_ACCEPT || dropComplete) {
 247             throw new InvalidDnDOperationException("No drop current");
 248         }
 249 
 250         Map<DataFlavor, Long> flavorMap = DataTransferer.getInstance()
 251             .getFlavorsForFormats(currentT, DataTransferer.adaptFlavorMap
 252                 (currentDT.getFlavorMap()));
 253 
 254         lFormat = flavorMap.get(df);
 255         if (lFormat == null) {
 256             throw new UnsupportedFlavorException(df);
 257         }
 258 
 259         if (df.isRepresentationClassRemote() &&
 260             currentDA != DnDConstants.ACTION_LINK) {
 261             throw new InvalidDnDOperationException("only ACTION_LINK is permissable for transfer of java.rmi.Remote objects");
 262         }
 263 
 264         final long format = lFormat.longValue();
 265 
 266         Object ret = getNativeData(format);
 267 
 268         if (ret instanceof byte[]) {
 269             try {
 270                 return DataTransferer.getInstance().
 271                     translateBytes((byte[])ret, df, format, this);
 272             } catch (IOException e) {
 273                 throw new InvalidDnDOperationException(e.getMessage());
 274             }
 275         } else if (ret instanceof InputStream) {
 276             try {
 277                 return DataTransferer.getInstance().
 278                     translateStream((InputStream)ret, df, format, this);
 279             } catch (IOException e) {
 280                 throw new InvalidDnDOperationException(e.getMessage());
 281             }
 282         } else {
 283             throw new IOException("no native data was transfered");
 284         }
 285     }
 286 
 287     protected abstract Object getNativeData(long format)
 288       throws IOException;
 289 
 290     /**
 291      * @return if the transfer is a local one
 292      */
 293     public boolean isTransferableJVMLocal() {
 294         return local != null || getJVMLocalSourceTransferable() != null;
 295     }
 296 
 297     private int handleEnterMessage(final Component component,
 298                                    final int x, final int y,
 299                                    final int dropAction,
 300                                    final int actions, final long[] formats,
 301                                    final long nativeCtxt) {
 302         return postDropTargetEvent(component, x, y, dropAction, actions,
 303                                    formats, nativeCtxt,
 304                                    SunDropTargetEvent.MOUSE_ENTERED,
 305                                    SunDropTargetContextPeer.DISPATCH_SYNC);
 306     }
 307 
 308     /**
 309      * actual processing on EventQueue Thread
 310      */
 311 
 312     protected void processEnterMessage(SunDropTargetEvent event) {
 313         Component  c    = (Component)event.getSource();
 314         DropTarget dt   = c.getDropTarget();
 315         Point      hots = event.getPoint();
 316 
 317         local = getJVMLocalSourceTransferable();
 318         DropTargetContextAccessor acc =
 319                 AWTAccessor.getDropTargetContextAccessor();
 320         if (currentDTC != null) { // some wreckage from last time
 321             acc.reset(currentDTC);
 322             currentDTC = null;
 323         }
 324 
 325         if (c.isShowing() && dt != null && dt.isActive()) {
 326             currentDT  = dt;
 327             currentDTC = currentDT.getDropTargetContext();
 328 
 329             acc.setDropTargetContextPeer(currentDTC, this);
 330 
 331             currentA   = dt.getDefaultActions();
 332 
 333             try {
 334                 ((DropTargetListener)dt).dragEnter(new DropTargetDragEvent(currentDTC,
 335                                                                            hots,
 336                                                                            currentDA,
 337                                                                            currentSA));
 338             } catch (Exception e) {
 339                 e.printStackTrace();
 340                 currentDA = DnDConstants.ACTION_NONE;
 341             }
 342         } else {
 343             currentDT  = null;
 344             currentDTC = null;
 345             currentDA   = DnDConstants.ACTION_NONE;
 346             currentSA   = DnDConstants.ACTION_NONE;
 347             currentA   = DnDConstants.ACTION_NONE;
 348         }
 349 
 350     }
 351 
 352     /**
 353      * upcall to handle exit messages
 354      */
 355 
 356     private void handleExitMessage(final Component component,
 357                                    final long nativeCtxt) {
 358         /*
 359          * Even though the return value is irrelevant for this event, it is
 360          * dispatched synchronously to fix 4393148 properly.
 361          */
 362         postDropTargetEvent(component, 0, 0, DnDConstants.ACTION_NONE,
 363                             DnDConstants.ACTION_NONE, null, nativeCtxt,
 364                             SunDropTargetEvent.MOUSE_EXITED,
 365                             SunDropTargetContextPeer.DISPATCH_SYNC);
 366     }
 367 
 368     /**
 369      *
 370      */
 371 
 372     protected void processExitMessage(SunDropTargetEvent event) {
 373         Component         c   = (Component)event.getSource();
 374         DropTarget        dt  = c.getDropTarget();
 375         DropTargetContext dtc = null;
 376         DropTargetContextAccessor acc =
 377                 AWTAccessor.getDropTargetContextAccessor();
 378 
 379         if (dt == null) {
 380             currentDT = null;
 381             currentT  = null;
 382 
 383             if (currentDTC != null) {
 384                 acc.reset(currentDTC);
 385             }
 386 
 387             currentDTC = null;
 388 
 389             return;
 390         }
 391 
 392         if (dt != currentDT) {
 393 
 394             if (currentDTC != null) {
 395                 acc.reset(currentDTC);
 396             }
 397 
 398             currentDT  = dt;
 399             currentDTC = dt.getDropTargetContext();
 400 
 401             acc.setDropTargetContextPeer(currentDTC, this);
 402         }
 403 
 404         dtc = currentDTC;
 405 
 406         if (dt.isActive()) try {
 407             ((DropTargetListener)dt).dragExit(new DropTargetEvent(dtc));
 408         } catch (Exception e) {
 409             e.printStackTrace();
 410         } finally {
 411             currentA  = DnDConstants.ACTION_NONE;
 412             currentSA = DnDConstants.ACTION_NONE;
 413             currentDA = DnDConstants.ACTION_NONE;
 414             currentDT = null;
 415             currentT  = null;
 416 
 417             acc.reset(currentDTC);
 418             currentDTC = null;
 419 
 420             local = null;
 421 
 422             dragRejected = false;
 423         }
 424     }
 425 
 426     private int handleMotionMessage(final Component component,
 427                                     final int x, final int y,
 428                                     final int dropAction,
 429                                     final int actions, final long[] formats,
 430                                     final long nativeCtxt) {
 431         return postDropTargetEvent(component, x, y, dropAction, actions,
 432                                    formats, nativeCtxt,
 433                                    SunDropTargetEvent.MOUSE_DRAGGED,
 434                                    SunDropTargetContextPeer.DISPATCH_SYNC);
 435     }
 436 
 437     /**
 438      *
 439      */
 440 
 441     protected void processMotionMessage(SunDropTargetEvent event,
 442                                       boolean operationChanged) {
 443         Component         c    = (Component)event.getSource();
 444         Point             hots = event.getPoint();
 445         int               id   = event.getID();
 446         DropTarget        dt   = c.getDropTarget();
 447         DropTargetContext dtc  = null;
 448         DropTargetContextAccessor acc =
 449                 AWTAccessor.getDropTargetContextAccessor();
 450 
 451         if (c.isShowing() && (dt != null) && dt.isActive()) {
 452             if (currentDT != dt) {
 453                 if (currentDTC != null) {
 454                     acc.reset(currentDTC);
 455                 }
 456 
 457                 currentDT  = dt;
 458                 currentDTC = null;
 459             }
 460 
 461             dtc = currentDT.getDropTargetContext();
 462             if (dtc != currentDTC) {
 463                 if (currentDTC != null) {
 464                     acc.reset(currentDTC);
 465                 }
 466 
 467                 currentDTC = dtc;
 468                 acc.setDropTargetContextPeer(currentDTC, this);
 469             }
 470 
 471             currentA = currentDT.getDefaultActions();
 472 
 473             try {
 474                 DropTargetDragEvent dtde = new DropTargetDragEvent(dtc,
 475                                                                    hots,
 476                                                                    currentDA,
 477                                                                    currentSA);
 478                 DropTargetListener dtl = (DropTargetListener)dt;
 479                 if (operationChanged) {
 480                     dtl.dropActionChanged(dtde);
 481                 } else {
 482                     dtl.dragOver(dtde);
 483                 }
 484 
 485                 if (dragRejected) {
 486                     currentDA = DnDConstants.ACTION_NONE;
 487                 }
 488             } catch (Exception e) {
 489                 e.printStackTrace();
 490                 currentDA = DnDConstants.ACTION_NONE;
 491             }
 492         } else {
 493             currentDA = DnDConstants.ACTION_NONE;
 494         }
 495     }
 496 
 497     /**
 498      * upcall to handle the Drop message
 499      */
 500 
 501     private void handleDropMessage(final Component component,
 502                                    final int x, final int y,
 503                                    final int dropAction, final int actions,
 504                                    final long[] formats,
 505                                    final long nativeCtxt) {
 506         postDropTargetEvent(component, x, y, dropAction, actions,
 507                             formats, nativeCtxt,
 508                             SunDropTargetEvent.MOUSE_DROPPED,
 509                             !SunDropTargetContextPeer.DISPATCH_SYNC);
 510     }
 511 
 512     /**
 513      *
 514      */
 515 
 516     protected void processDropMessage(SunDropTargetEvent event) {
 517         Component  c    = (Component)event.getSource();
 518         Point      hots = event.getPoint();
 519         DropTarget dt   = c.getDropTarget();
 520 
 521         dropStatus   = STATUS_WAIT; // drop pending ACK
 522         dropComplete = false;
 523 
 524         if (c.isShowing() && dt != null && dt.isActive()) {
 525             DropTargetContext dtc = dt.getDropTargetContext();
 526 
 527             currentDT = dt;
 528             DropTargetContextAccessor acc =
 529                     AWTAccessor.getDropTargetContextAccessor();
 530 
 531             if (currentDTC != null) {
 532                 acc.reset(currentDTC);
 533             }
 534 
 535             currentDTC = dtc;
 536             acc.setDropTargetContextPeer(currentDTC, this);
 537             currentA = dt.getDefaultActions();
 538 
 539             synchronized(_globalLock) {
 540                 if ((local = getJVMLocalSourceTransferable()) != null)
 541                     setCurrentJVMLocalSourceTransferable(null);
 542             }
 543 
 544             dropInProcess = true;
 545 
 546             try {
 547                 ((DropTargetListener)dt).drop(new DropTargetDropEvent(dtc,
 548                                                                       hots,
 549                                                                       currentDA,
 550                                                                       currentSA,
 551                                                                       local != null));
 552             } finally {
 553                 if (dropStatus == STATUS_WAIT) {
 554                     rejectDrop();
 555                 } else if (dropComplete == false) {
 556                     dropComplete(false);
 557                 }
 558                 dropInProcess = false;
 559             }
 560         } else {
 561             rejectDrop();
 562         }
 563     }
 564 
 565     protected int postDropTargetEvent(final Component component,
 566                                       final int x, final int y,
 567                                       final int dropAction,
 568                                       final int actions,
 569                                       final long[] formats,
 570                                       final long nativeCtxt,
 571                                       final int eventID,
 572                                       final boolean dispatchType) {
 573         AppContext appContext = SunToolkit.targetToAppContext(component);
 574 
 575         EventDispatcher dispatcher =
 576             new EventDispatcher(this, dropAction, actions, formats, nativeCtxt,
 577                                 dispatchType);
 578 
 579         SunDropTargetEvent event =
 580             new SunDropTargetEvent(component, eventID, x, y, dispatcher);
 581 
 582         if (dispatchType == SunDropTargetContextPeer.DISPATCH_SYNC) {
 583             DataTransferer.getInstance().getToolkitThreadBlockedHandler().lock();
 584         }
 585 
 586         // schedule callback
 587         SunToolkit.postEvent(appContext, event);
 588 
 589         eventPosted(event);
 590 
 591         if (dispatchType == SunDropTargetContextPeer.DISPATCH_SYNC) {
 592             while (!dispatcher.isDone()) {
 593                 DataTransferer.getInstance().getToolkitThreadBlockedHandler().enter();
 594             }
 595 
 596             DataTransferer.getInstance().getToolkitThreadBlockedHandler().unlock();
 597 
 598             // return target's response
 599             return dispatcher.getReturnValue();
 600         } else {
 601             return 0;
 602         }
 603     }
 604 
 605     /**
 606      * acceptDrag
 607      */
 608 
 609     public synchronized void acceptDrag(int dragOperation) {
 610         if (currentDT == null) {
 611             throw new InvalidDnDOperationException("No Drag pending");
 612         }
 613         currentDA = mapOperation(dragOperation);
 614         if (currentDA != DnDConstants.ACTION_NONE) {
 615             dragRejected = false;
 616         }
 617     }
 618 
 619     /**
 620      * rejectDrag
 621      */
 622 
 623     public synchronized void rejectDrag() {
 624         if (currentDT == null) {
 625             throw new InvalidDnDOperationException("No Drag pending");
 626         }
 627         currentDA = DnDConstants.ACTION_NONE;
 628         dragRejected = true;
 629     }
 630 
 631     /**
 632      * acceptDrop
 633      */
 634 
 635     public synchronized void acceptDrop(int dropOperation) {
 636         if (dropOperation == DnDConstants.ACTION_NONE)
 637             throw new IllegalArgumentException("invalid acceptDrop() action");
 638 
 639         if (dropStatus == STATUS_WAIT || dropStatus == STATUS_ACCEPT) {
 640             currentDA = currentA = mapOperation(dropOperation & currentSA);
 641 
 642             dropStatus   = STATUS_ACCEPT;
 643             dropComplete = false;
 644         } else {
 645             throw new InvalidDnDOperationException("invalid acceptDrop()");
 646         }
 647     }
 648 
 649     /**
 650      * reject Drop
 651      */
 652 
 653     public synchronized void rejectDrop() {
 654         if (dropStatus != STATUS_WAIT) {
 655             throw new InvalidDnDOperationException("invalid rejectDrop()");
 656         }
 657         dropStatus = STATUS_REJECT;
 658         /*
 659          * Fix for 4285634.
 660          * The target rejected the drop means that it doesn't perform any
 661          * drop action. This change is to make Solaris behavior consistent
 662          * with Win32.
 663          */
 664         currentDA = DnDConstants.ACTION_NONE;
 665         dropComplete(false);
 666     }
 667 
 668     /**
 669      * mapOperation
 670      */
 671 
 672     private int mapOperation(int operation) {
 673         int[] operations = {
 674                 DnDConstants.ACTION_MOVE,
 675                 DnDConstants.ACTION_COPY,
 676                 DnDConstants.ACTION_LINK,
 677         };
 678         int   ret = DnDConstants.ACTION_NONE;
 679 
 680         for (int i = 0; i < operations.length; i++) {
 681             if ((operation & operations[i]) == operations[i]) {
 682                     ret = operations[i];
 683                     break;
 684             }
 685         }
 686 
 687         return ret;
 688     }
 689 
 690     /**
 691      * signal drop complete
 692      */
 693 
 694     public synchronized void dropComplete(boolean success) {
 695         if (dropStatus == STATUS_NONE) {
 696             throw new InvalidDnDOperationException("No Drop pending");
 697         }
 698 
 699         if (currentDTC != null) {
 700             AWTAccessor.getDropTargetContextAccessor().reset(currentDTC);
 701         }
 702 
 703         currentDT  = null;
 704         currentDTC = null;
 705         currentT   = null;
 706         currentA   = DnDConstants.ACTION_NONE;
 707 
 708         synchronized(_globalLock) {
 709             currentJVMLocalSourceTransferable = null;
 710         }
 711 
 712         dropStatus   = STATUS_NONE;
 713         dropComplete = true;
 714 
 715         try {
 716             doDropDone(success, currentDA, local != null);
 717         } finally {
 718             currentDA = DnDConstants.ACTION_NONE;
 719             // The native context is invalid after the drop is done.
 720             // Clear the reference to prohibit access.
 721             nativeDragContext = 0;
 722         }
 723     }
 724 
 725     protected abstract void doDropDone(boolean success,
 726                                        int dropAction, boolean isLocal);
 727 
 728     protected synchronized long getNativeDragContext() {
 729         return nativeDragContext;
 730     }
 731 
 732     protected void eventPosted(SunDropTargetEvent e) {}
 733 
 734     protected void eventProcessed(SunDropTargetEvent e, int returnValue,
 735                                   boolean dispatcherDone) {}
 736 
 737     protected static class EventDispatcher {
 738 
 739         private final SunDropTargetContextPeer peer;
 740 
 741         // context fields
 742         private final int dropAction;
 743         private final int actions;
 744         private final long[] formats;
 745         private long nativeCtxt;
 746         private final boolean dispatchType;
 747         private boolean dispatcherDone = false;
 748 
 749         // dispatcher state fields
 750         private int returnValue = 0;
 751         // set of events to be dispatched by this dispatcher
 752         private final HashSet<SunDropTargetEvent> eventSet = new HashSet<>(3);
 753 
 754         static final ToolkitThreadBlockedHandler handler =
 755             DataTransferer.getInstance().getToolkitThreadBlockedHandler();
 756 
 757         EventDispatcher(SunDropTargetContextPeer peer,
 758                         int dropAction,
 759                         int actions,
 760                         long[] formats,
 761                         long nativeCtxt,
 762                         boolean dispatchType) {
 763 
 764             this.peer         = peer;
 765             this.nativeCtxt   = nativeCtxt;
 766             this.dropAction   = dropAction;
 767             this.actions      = actions;
 768             this.formats =
 769                      (null == formats) ? null : Arrays.copyOf(formats, formats.length);
 770             this.dispatchType = dispatchType;
 771         }
 772 
 773         void dispatchEvent(SunDropTargetEvent e) {
 774             int id = e.getID();
 775 
 776             switch (id) {
 777             case SunDropTargetEvent.MOUSE_ENTERED:
 778                 dispatchEnterEvent(e);
 779                 break;
 780             case SunDropTargetEvent.MOUSE_DRAGGED:
 781                 dispatchMotionEvent(e);
 782                 break;
 783             case SunDropTargetEvent.MOUSE_EXITED:
 784                 dispatchExitEvent(e);
 785                 break;
 786             case SunDropTargetEvent.MOUSE_DROPPED:
 787                 dispatchDropEvent(e);
 788                 break;
 789             default:
 790                 throw new InvalidDnDOperationException();
 791             }
 792         }
 793 
 794         private void dispatchEnterEvent(SunDropTargetEvent e) {
 795             synchronized (peer) {
 796 
 797                 // store the drop action here to track operation changes
 798                 peer.previousDA = dropAction;
 799 
 800                 // setup peer context
 801                 peer.nativeDragContext = nativeCtxt;
 802                 peer.currentT          = formats;
 803                 peer.currentSA         = actions;
 804                 peer.currentDA         = dropAction;
 805                 // To allow data retrieval.
 806                 peer.dropStatus        = STATUS_ACCEPT;
 807                 peer.dropComplete      = false;
 808 
 809                 try {
 810                     peer.processEnterMessage(e);
 811                 } finally {
 812                     peer.dropStatus        = STATUS_NONE;
 813                 }
 814 
 815                 setReturnValue(peer.currentDA);
 816             }
 817         }
 818 
 819         private void dispatchMotionEvent(SunDropTargetEvent e) {
 820             synchronized (peer) {
 821 
 822                 boolean operationChanged = peer.previousDA != dropAction;
 823                 peer.previousDA = dropAction;
 824 
 825                 // setup peer context
 826                 peer.nativeDragContext = nativeCtxt;
 827                 peer.currentT          = formats;
 828                 peer.currentSA         = actions;
 829                 peer.currentDA         = dropAction;
 830                 // To allow data retrieval.
 831                 peer.dropStatus        = STATUS_ACCEPT;
 832                 peer.dropComplete      = false;
 833 
 834                 try {
 835                     peer.processMotionMessage(e, operationChanged);
 836                 } finally {
 837                     peer.dropStatus        = STATUS_NONE;
 838                 }
 839 
 840                 setReturnValue(peer.currentDA);
 841             }
 842         }
 843 
 844         private void dispatchExitEvent(SunDropTargetEvent e) {
 845             synchronized (peer) {
 846 
 847                 // setup peer context
 848                 peer.nativeDragContext = nativeCtxt;
 849 
 850                 peer.processExitMessage(e);
 851             }
 852         }
 853 
 854         private void dispatchDropEvent(SunDropTargetEvent e) {
 855             synchronized (peer) {
 856 
 857                 // setup peer context
 858                 peer.nativeDragContext = nativeCtxt;
 859                 peer.currentT          = formats;
 860                 peer.currentSA         = actions;
 861                 peer.currentDA         = dropAction;
 862 
 863                 peer.processDropMessage(e);
 864             }
 865         }
 866 
 867         void setReturnValue(int ret) {
 868             returnValue = ret;
 869         }
 870 
 871         int getReturnValue() {
 872             return returnValue;
 873         }
 874 
 875         boolean isDone() {
 876             return eventSet.isEmpty();
 877         }
 878 
 879         void registerEvent(SunDropTargetEvent e) {
 880             handler.lock();
 881             if (!eventSet.add(e) && dndLog.isLoggable(PlatformLogger.Level.FINE)) {
 882                 dndLog.fine("Event is already registered: " + e);
 883             }
 884             handler.unlock();
 885         }
 886 
 887         void unregisterEvent(SunDropTargetEvent e) {
 888             handler.lock();
 889             try {
 890                 if (!eventSet.remove(e)) {
 891                     // This event has already been unregistered.
 892                     return;
 893                 }
 894                 if (eventSet.isEmpty()) {
 895                     if (!dispatcherDone && dispatchType == DISPATCH_SYNC) {
 896                         handler.exit();
 897                     }
 898                     dispatcherDone = true;
 899                 }
 900             } finally {
 901                 handler.unlock();
 902             }
 903 
 904             try {
 905                 peer.eventProcessed(e, returnValue, dispatcherDone);
 906             } finally {
 907                 /*
 908                  * Clear the reference to the native context if all copies of
 909                  * the original event are processed.
 910                  */
 911                 if (dispatcherDone && !peer.dropInProcess) {
 912                     nativeCtxt = 0;
 913                     // Fix for 6342381
 914                     peer.nativeDragContext = 0;
 915 
 916                 }
 917             }
 918         }
 919 
 920         public void unregisterAllEvents() {
 921             Object[] events = null;
 922             handler.lock();
 923             try {
 924                 events = eventSet.toArray();
 925             } finally {
 926                 handler.unlock();
 927             }
 928 
 929             if (events != null) {
 930                 for (int i = 0; i < events.length; i++) {
 931                     unregisterEvent((SunDropTargetEvent)events[i]);
 932                 }
 933             }
 934         }
 935     }
 936 }