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