1 /*
   2  * Copyright (c) 2000, 2010, 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 java.awt;
  27 
  28 import java.util.LinkedList;
  29 import sun.awt.AppContext;
  30 import sun.awt.SunToolkit;
  31 
  32 /**
  33  * A mechanism for ensuring that a series of AWTEvents are executed in a
  34  * precise order, even across multiple AppContexts. The nested events will be
  35  * dispatched in the order in which their wrapping SequencedEvents were
  36  * constructed. The only exception to this rule is if the peer of the target of
  37  * the nested event was destroyed (with a call to Component.removeNotify)
  38  * before the wrapping SequencedEvent was able to be dispatched. In this case,
  39  * the nested event is never dispatched.
  40  *
  41  * @author David Mendenhall
  42  */
  43 class SequencedEvent extends AWTEvent implements ActiveEvent {
  44     /*
  45      * serialVersionUID
  46      */
  47     private static final long serialVersionUID = 547742659238625067L;
  48 
  49     private static final int ID =
  50         java.awt.event.FocusEvent.FOCUS_LAST + 1;
  51     private static final LinkedList list = new LinkedList();
  52 
  53     private final AWTEvent nested;
  54     private AppContext appContext;
  55     private boolean disposed;
  56 
  57     /**
  58      * Constructs a new SequencedEvent which will dispatch the specified
  59      * nested event.
  60      *
  61      * @param nested the AWTEvent which this SequencedEvent's dispatch()
  62      *        method will dispatch
  63      */
  64     public SequencedEvent(AWTEvent nested) {
  65         super(nested.getSource(), ID);
  66         this.nested = nested;
  67         // All AWTEvents that are wrapped in SequencedEvents are (at
  68         // least currently) implicitly generated by the system
  69         SunToolkit.setSystemGenerated(nested);
  70         synchronized (SequencedEvent.class) {
  71             list.add(this);
  72         }
  73     }
  74 
  75     /**
  76      * Dispatches the nested event after all previous nested events have been
  77      * dispatched or disposed. If this method is invoked before all previous nested events
  78      * have been dispatched, then this method blocks until such a point is
  79      * reached.
  80      * While waiting disposes nested events to disposed AppContext
  81      *
  82      * NOTE: Locking protocol.  Since dispose() can get EventQueue lock,
  83      * dispatch() shall never call dispose() while holding the lock on the list,
  84      * as EventQueue lock is held during dispatching.  The locks should be acquired
  85      * in the same order.
  86      */
  87     public final void dispatch() {
  88         try {
  89             appContext = AppContext.getAppContext();
  90 
  91             if (getFirst() != this) {
  92                 if (EventQueue.isDispatchThread()) {
  93                     EventDispatchThread edt = (EventDispatchThread)
  94                         Thread.currentThread();
  95                     edt.pumpEvents(SentEvent.ID, new Conditional() {
  96                         public boolean evaluate() {
  97                             return !SequencedEvent.this.isFirstOrDisposed();
  98                         }
  99                     });
 100                 } else {
 101                     while(!isFirstOrDisposed()) {
 102                         synchronized (SequencedEvent.class) {
 103                             try {
 104                                 SequencedEvent.class.wait(1000);
 105                             } catch (InterruptedException e) {
 106                                 break;
 107                             }
 108                         }
 109                     }
 110                 }
 111             }
 112 
 113             if (!disposed) {
 114                 KeyboardFocusManager.getCurrentKeyboardFocusManager().
 115                     setCurrentSequencedEvent(this);
 116                 Toolkit.getEventQueue().dispatchEvent(nested);
 117             }
 118         } finally {
 119             dispose();
 120         }
 121     }
 122 
 123     /**
 124      * true only if event exists and nested source appContext is disposed.
 125      */
 126     private final static boolean isOwnerAppContextDisposed(SequencedEvent se) {
 127         if (se != null) {
 128             Object target = se.nested.getSource();
 129             if (target instanceof Component) {
 130                 return ((Component)target).appContext.isDisposed();
 131             }
 132         }
 133         return false;
 134     }
 135 
 136     /**
 137      * Sequenced events are dispatched in order, so we cannot dispatch
 138      * until we are the first sequenced event in the queue (i.e. it's our
 139      * turn).  But while we wait for our turn to dispatch, the event
 140      * could have been disposed for a number of reasons.
 141      */
 142     public final boolean isFirstOrDisposed() {
 143         if (disposed) {
 144             return true;
 145         }
 146         // getFirstWithContext can dispose this
 147         return this == getFirstWithContext() || disposed;
 148     }
 149 
 150     private final synchronized static SequencedEvent getFirst() {
 151         return (SequencedEvent)list.getFirst();
 152     }
 153 
 154     /* Disposes all events from disposed AppContext
 155      * return first valid event
 156      */
 157     private final static SequencedEvent getFirstWithContext() {
 158         SequencedEvent first = getFirst();
 159         while(isOwnerAppContextDisposed(first)) {
 160             first.dispose();
 161             first = getFirst();
 162         }
 163         return first;
 164     }
 165 
 166     /**
 167      * Disposes of this instance. This method is invoked once the nested event
 168      * has been dispatched and handled, or when the peer of the target of the
 169      * nested event has been disposed with a call to Component.removeNotify.
 170      *
 171      * NOTE: Locking protocol.  Since SunToolkit.postEvent can get EventQueue lock,
 172      * it shall never be called while holding the lock on the list,
 173      * as EventQueue lock is held during dispatching and dispatch() will get
 174      * lock on the list. The locks should be acquired in the same order.
 175      */
 176     final void dispose() {
 177       synchronized (SequencedEvent.class) {
 178             if (disposed) {
 179                 return;
 180             }
 181             if (KeyboardFocusManager.getCurrentKeyboardFocusManager().
 182                     getCurrentSequencedEvent() == this) {
 183                 KeyboardFocusManager.getCurrentKeyboardFocusManager().
 184                     setCurrentSequencedEvent(null);
 185             }
 186             disposed = true;
 187         }
 188         // Wake myself up
 189         if (appContext != null) {
 190             SunToolkit.postEvent(appContext, new SentEvent());
 191         }
 192 
 193         SequencedEvent next = null;
 194 
 195         synchronized (SequencedEvent.class) {
 196           SequencedEvent.class.notifyAll();
 197 
 198           if (list.getFirst() == this) {
 199               list.removeFirst();
 200 
 201               if (!list.isEmpty()) {
 202                     next = (SequencedEvent)list.getFirst();
 203               }
 204           } else {
 205               list.remove(this);
 206           }
 207       }
 208         // Wake up waiting threads
 209         if (next != null && next.appContext != null) {
 210             SunToolkit.postEvent(next.appContext, new SentEvent());
 211         }
 212     }
 213 }