Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/awt/AppContext.java
41152 views
1
/*
2
* Copyright (c) 1998, 2021, 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;
27
28
import java.awt.EventQueue;
29
import java.awt.Window;
30
import java.awt.SystemTray;
31
import java.awt.TrayIcon;
32
import java.awt.Toolkit;
33
import java.awt.GraphicsEnvironment;
34
import java.awt.event.InvocationEvent;
35
import java.security.AccessController;
36
import java.security.PrivilegedAction;
37
import java.util.Collections;
38
import java.util.HashMap;
39
import java.util.IdentityHashMap;
40
import java.util.Map;
41
import java.util.Set;
42
import java.util.HashSet;
43
import java.beans.PropertyChangeSupport;
44
import java.beans.PropertyChangeListener;
45
import java.lang.ref.SoftReference;
46
47
import jdk.internal.access.JavaAWTAccess;
48
import jdk.internal.access.SharedSecrets;
49
import sun.util.logging.PlatformLogger;
50
import java.util.concurrent.locks.Condition;
51
import java.util.concurrent.locks.Lock;
52
import java.util.concurrent.locks.ReentrantLock;
53
import java.util.concurrent.atomic.AtomicInteger;
54
import java.util.function.Supplier;
55
56
/**
57
* The AppContext is a table referenced by ThreadGroup which stores
58
* application service instances. (If you are not writing an application
59
* service, or don't know what one is, please do not use this class.)
60
* The AppContext allows applet access to what would otherwise be
61
* potentially dangerous services, such as the ability to peek at
62
* EventQueues or change the look-and-feel of a Swing application.<p>
63
*
64
* Most application services use a singleton object to provide their
65
* services, either as a default (such as getSystemEventQueue or
66
* getDefaultToolkit) or as static methods with class data (System).
67
* The AppContext works with the former method by extending the concept
68
* of "default" to be ThreadGroup-specific. Application services
69
* lookup their singleton in the AppContext.<p>
70
*
71
* For example, here we have a Foo service, with its pre-AppContext
72
* code:<p>
73
* <pre>{@code
74
* public class Foo {
75
* private static Foo defaultFoo = new Foo();
76
*
77
* public static Foo getDefaultFoo() {
78
* return defaultFoo;
79
* }
80
*
81
* ... Foo service methods
82
* }
83
* }</pre><p>
84
*
85
* The problem with the above is that the Foo service is global in scope,
86
* so that applets and other untrusted code can execute methods on the
87
* single, shared Foo instance. The Foo service therefore either needs
88
* to block its use by untrusted code using a SecurityManager test, or
89
* restrict its capabilities so that it doesn't matter if untrusted code
90
* executes it.<p>
91
*
92
* Here's the Foo class written to use the AppContext:<p>
93
* <pre>{@code
94
* public class Foo {
95
* public static Foo getDefaultFoo() {
96
* Foo foo = (Foo)AppContext.getAppContext().get(Foo.class);
97
* if (foo == null) {
98
* foo = new Foo();
99
* getAppContext().put(Foo.class, foo);
100
* }
101
* return foo;
102
* }
103
*
104
* ... Foo service methods
105
* }
106
* }</pre><p>
107
*
108
* Since a separate AppContext can exist for each ThreadGroup, trusted
109
* and untrusted code have access to different Foo instances. This allows
110
* untrusted code access to "system-wide" services -- the service remains
111
* within the AppContext "sandbox". For example, say a malicious applet
112
* wants to peek all of the key events on the EventQueue to listen for
113
* passwords; if separate EventQueues are used for each ThreadGroup
114
* using AppContexts, the only key events that applet will be able to
115
* listen to are its own. A more reasonable applet request would be to
116
* change the Swing default look-and-feel; with that default stored in
117
* an AppContext, the applet's look-and-feel will change without
118
* disrupting other applets or potentially the browser itself.<p>
119
*
120
* Because the AppContext is a facility for safely extending application
121
* service support to applets, none of its methods may be blocked by a
122
* a SecurityManager check in a valid Java implementation. Applets may
123
* therefore safely invoke any of its methods without worry of being
124
* blocked.
125
*
126
* @author Thomas Ball
127
* @author Fred Ecks
128
*/
129
public final class AppContext {
130
private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext");
131
132
/* Since the contents of an AppContext are unique to each Java
133
* session, this class should never be serialized. */
134
135
/*
136
* The key to put()/get() the Java EventQueue into/from the AppContext.
137
*/
138
public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue");
139
140
/*
141
* The keys to store EventQueue push/pop lock and condition.
142
*/
143
public static final Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock");
144
public static final Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition");
145
146
/* A map of AppContexts, referenced by ThreadGroup.
147
*/
148
private static final Map<ThreadGroup, AppContext> threadGroup2appContext =
149
Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, AppContext>());
150
151
/**
152
* Returns a set containing all {@code AppContext}s.
153
*/
154
public static Set<AppContext> getAppContexts() {
155
synchronized (threadGroup2appContext) {
156
return new HashSet<AppContext>(threadGroup2appContext.values());
157
}
158
}
159
160
/* The main "system" AppContext, used by everything not otherwise
161
contained in another AppContext. It is implicitly created for
162
standalone apps only (i.e. not applets)
163
*/
164
private static volatile AppContext mainAppContext = null;
165
166
private static class GetAppContextLock {};
167
private static final Object getAppContextLock = new GetAppContextLock();
168
169
/*
170
* The hash map associated with this AppContext. A private delegate
171
* is used instead of subclassing HashMap so as to avoid all of
172
* HashMap's potentially risky methods, such as clear(), elements(),
173
* putAll(), etc.
174
*/
175
private final Map<Object, Object> table = new HashMap<>();
176
177
private final ThreadGroup threadGroup;
178
179
/**
180
* If any {@code PropertyChangeListeners} have been registered,
181
* the {@code changeSupport} field describes them.
182
*
183
* @see #addPropertyChangeListener
184
* @see #removePropertyChangeListener
185
* @see PropertyChangeSupport#firePropertyChange
186
*/
187
private PropertyChangeSupport changeSupport = null;
188
189
public static final String DISPOSED_PROPERTY_NAME = "disposed";
190
public static final String GUI_DISPOSED = "guidisposed";
191
192
private enum State {
193
VALID,
194
BEING_DISPOSED,
195
DISPOSED
196
};
197
198
private volatile State state = State.VALID;
199
200
public boolean isDisposed() {
201
return state == State.DISPOSED;
202
}
203
204
/*
205
* The total number of AppContexts, system-wide. This number is
206
* incremented at the beginning of the constructor, and decremented
207
* at the end of dispose(). getAppContext() checks to see if this
208
* number is 1. If so, it returns the sole AppContext without
209
* checking Thread.currentThread().
210
*/
211
private static final AtomicInteger numAppContexts = new AtomicInteger();
212
213
214
/*
215
* The context ClassLoader that was used to create this AppContext.
216
*/
217
private final ClassLoader contextClassLoader;
218
219
/**
220
* Constructor for AppContext. This method is <i>not</i> public,
221
* nor should it ever be used as such. The proper way to construct
222
* an AppContext is through the use of SunToolkit.createNewAppContext.
223
* A ThreadGroup is created for the new AppContext, a Thread is
224
* created within that ThreadGroup, and that Thread calls
225
* SunToolkit.createNewAppContext before calling anything else.
226
* That creates both the new AppContext and its EventQueue.
227
*
228
* @param threadGroup The ThreadGroup for the new AppContext
229
* @see sun.awt.SunToolkit
230
* @since 1.2
231
*/
232
@SuppressWarnings("removal")
233
AppContext(ThreadGroup threadGroup) {
234
numAppContexts.incrementAndGet();
235
236
this.threadGroup = threadGroup;
237
threadGroup2appContext.put(threadGroup, this);
238
239
this.contextClassLoader =
240
AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
241
public ClassLoader run() {
242
return Thread.currentThread().getContextClassLoader();
243
}
244
});
245
246
// Initialize push/pop lock and its condition to be used by all the
247
// EventQueues within this AppContext
248
Lock eventQueuePushPopLock = new ReentrantLock();
249
put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock);
250
Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition();
251
put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond);
252
}
253
254
private static final ThreadLocal<AppContext> threadAppContext =
255
new ThreadLocal<AppContext>();
256
257
@SuppressWarnings("removal")
258
private static void initMainAppContext() {
259
// On the main Thread, we get the ThreadGroup, make a corresponding
260
// AppContext, and instantiate the Java EventQueue. This way, legacy
261
// code is unaffected by the move to multiple AppContext ability.
262
AccessController.doPrivileged(new PrivilegedAction<Void>() {
263
public Void run() {
264
ThreadGroup currentThreadGroup =
265
Thread.currentThread().getThreadGroup();
266
ThreadGroup parentThreadGroup = currentThreadGroup.getParent();
267
while (parentThreadGroup != null) {
268
// Find the root ThreadGroup to construct our main AppContext
269
currentThreadGroup = parentThreadGroup;
270
parentThreadGroup = currentThreadGroup.getParent();
271
}
272
273
mainAppContext = SunToolkit.createNewAppContext(currentThreadGroup);
274
return null;
275
}
276
});
277
}
278
279
/**
280
* Returns the appropriate AppContext for the caller,
281
* as determined by its ThreadGroup.
282
*
283
* @return the AppContext for the caller.
284
* @see java.lang.ThreadGroup
285
* @since 1.2
286
*/
287
@SuppressWarnings("removal")
288
public static AppContext getAppContext() {
289
// we are standalone app, return the main app context
290
if (numAppContexts.get() == 1 && mainAppContext != null) {
291
return mainAppContext;
292
}
293
294
AppContext appContext = threadAppContext.get();
295
296
if (null == appContext) {
297
appContext = AccessController.doPrivileged(new PrivilegedAction<AppContext>()
298
{
299
public AppContext run() {
300
// Get the current ThreadGroup, and look for it and its
301
// parents in the hash from ThreadGroup to AppContext --
302
// it should be found, because we use createNewContext()
303
// when new AppContext objects are created.
304
ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
305
ThreadGroup threadGroup = currentThreadGroup;
306
307
// Special case: we implicitly create the main app context
308
// if no contexts have been created yet. This covers standalone apps
309
// and excludes applets because by the time applet starts
310
// a number of contexts have already been created by the plugin.
311
synchronized (getAppContextLock) {
312
if (numAppContexts.get() == 0) {
313
if (System.getProperty("javaplugin.version") == null &&
314
System.getProperty("javawebstart.version") == null) {
315
initMainAppContext();
316
} else if (System.getProperty("javafx.version") != null &&
317
threadGroup.getParent() != null) {
318
// Swing inside JavaFX case
319
SunToolkit.createNewAppContext();
320
}
321
}
322
}
323
324
AppContext context = threadGroup2appContext.get(threadGroup);
325
while (context == null) {
326
threadGroup = threadGroup.getParent();
327
if (threadGroup == null) {
328
// We've got up to the root thread group and did not find an AppContext
329
// Try to get it from the security manager
330
SecurityManager securityManager = System.getSecurityManager();
331
if (securityManager != null) {
332
ThreadGroup smThreadGroup = securityManager.getThreadGroup();
333
if (smThreadGroup != null) {
334
/*
335
* If we get this far then it's likely that
336
* the ThreadGroup does not actually belong
337
* to the applet, so do not cache it.
338
*/
339
return threadGroup2appContext.get(smThreadGroup);
340
}
341
}
342
return null;
343
}
344
context = threadGroup2appContext.get(threadGroup);
345
}
346
347
// In case we did anything in the above while loop, we add
348
// all the intermediate ThreadGroups to threadGroup2appContext
349
// so we won't spin again.
350
for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) {
351
threadGroup2appContext.put(tg, context);
352
}
353
354
// Now we're done, so we cache the latest key/value pair.
355
threadAppContext.set(context);
356
357
return context;
358
}
359
});
360
}
361
362
return appContext;
363
}
364
365
/**
366
* Returns true if the specified AppContext is the main AppContext.
367
*
368
* @param ctx the context to compare with the main context
369
* @return true if the specified AppContext is the main AppContext.
370
* @since 1.8
371
*/
372
public static boolean isMainContext(AppContext ctx) {
373
return (ctx != null && ctx == mainAppContext);
374
}
375
376
private long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout
377
// for disposal of all Frames
378
// (we wait for this time twice,
379
// once for dispose(), and once
380
// to clear the EventQueue).
381
382
private long THREAD_INTERRUPT_TIMEOUT = 1000;
383
// Default to 1-second timeout for all
384
// interrupted Threads to exit, and another
385
// 1 second for all stopped Threads to die.
386
387
/**
388
* Disposes of this AppContext, all of its top-level Frames, and
389
* all Threads and ThreadGroups contained within it.
390
*
391
* This method must be called from a Thread which is not contained
392
* within this AppContext.
393
*
394
* @exception IllegalThreadStateException if the current thread is
395
* contained within this AppContext
396
* @since 1.2
397
*/
398
@SuppressWarnings({"deprecation", "removal"})
399
public void dispose() throws IllegalThreadStateException {
400
// Check to be sure that the current Thread isn't in this AppContext
401
if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) {
402
throw new IllegalThreadStateException(
403
"Current Thread is contained within AppContext to be disposed."
404
);
405
}
406
407
synchronized(this) {
408
if (this.state != State.VALID) {
409
return; // If already disposed or being disposed, bail.
410
}
411
412
this.state = State.BEING_DISPOSED;
413
}
414
415
final PropertyChangeSupport changeSupport = this.changeSupport;
416
if (changeSupport != null) {
417
changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true);
418
}
419
420
// First, we post an InvocationEvent to be run on the
421
// EventDispatchThread which disposes of all top-level Frames and TrayIcons
422
423
final Object notificationLock = new Object();
424
425
Runnable runnable = new Runnable() {
426
public void run() {
427
Window[] windowsToDispose = Window.getOwnerlessWindows();
428
for (Window w : windowsToDispose) {
429
try {
430
w.dispose();
431
} catch (Throwable t) {
432
log.finer("exception occurred while disposing app context", t);
433
}
434
}
435
AccessController.doPrivileged(new PrivilegedAction<Void>() {
436
public Void run() {
437
if (!GraphicsEnvironment.isHeadless() && SystemTray.isSupported())
438
{
439
SystemTray systemTray = SystemTray.getSystemTray();
440
TrayIcon[] trayIconsToDispose = systemTray.getTrayIcons();
441
for (TrayIcon ti : trayIconsToDispose) {
442
systemTray.remove(ti);
443
}
444
}
445
return null;
446
}
447
});
448
// Alert PropertyChangeListeners that the GUI has been disposed.
449
if (changeSupport != null) {
450
changeSupport.firePropertyChange(GUI_DISPOSED, false, true);
451
}
452
synchronized(notificationLock) {
453
notificationLock.notifyAll(); // Notify caller that we're done
454
}
455
}
456
};
457
synchronized(notificationLock) {
458
SunToolkit.postEvent(this,
459
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
460
try {
461
notificationLock.wait(DISPOSAL_TIMEOUT);
462
} catch (InterruptedException e) { }
463
}
464
465
// Next, we post another InvocationEvent to the end of the
466
// EventQueue. When it's executed, we know we've executed all
467
// events in the queue.
468
469
runnable = new Runnable() { public void run() {
470
synchronized(notificationLock) {
471
notificationLock.notifyAll(); // Notify caller that we're done
472
}
473
} };
474
synchronized(notificationLock) {
475
SunToolkit.postEvent(this,
476
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
477
try {
478
notificationLock.wait(DISPOSAL_TIMEOUT);
479
} catch (InterruptedException e) { }
480
}
481
482
// We are done with posting events, so change the state to disposed
483
synchronized(this) {
484
this.state = State.DISPOSED;
485
}
486
487
// Next, we interrupt all Threads in the ThreadGroup
488
this.threadGroup.interrupt();
489
// Note, the EventDispatchThread we've interrupted may dump an
490
// InterruptedException to the console here. This needs to be
491
// fixed in the EventDispatchThread, not here.
492
493
// Next, we sleep 10ms at a time, waiting for all of the active
494
// Threads in the ThreadGroup to exit.
495
496
long startTime = System.currentTimeMillis();
497
long endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
498
while ((this.threadGroup.activeCount() > 0) &&
499
(System.currentTimeMillis() < endTime)) {
500
try {
501
Thread.sleep(10);
502
} catch (InterruptedException e) { }
503
}
504
505
// Then, we stop any remaining Threads
506
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
507
threadGroup.stop();
508
return null;
509
});
510
511
// Next, we sleep 10ms at a time, waiting for all of the active
512
// Threads in the ThreadGroup to die.
513
514
startTime = System.currentTimeMillis();
515
endTime = startTime + THREAD_INTERRUPT_TIMEOUT;
516
while ((this.threadGroup.activeCount() > 0) &&
517
(System.currentTimeMillis() < endTime)) {
518
try {
519
Thread.sleep(10);
520
} catch (InterruptedException e) { }
521
}
522
523
// Next, we remove this and all subThreadGroups from threadGroup2appContext
524
int numSubGroups = this.threadGroup.activeGroupCount();
525
if (numSubGroups > 0) {
526
ThreadGroup [] subGroups = new ThreadGroup[numSubGroups];
527
numSubGroups = this.threadGroup.enumerate(subGroups);
528
for (int subGroup = 0; subGroup < numSubGroups; subGroup++) {
529
threadGroup2appContext.remove(subGroups[subGroup]);
530
}
531
}
532
threadGroup2appContext.remove(this.threadGroup);
533
534
threadAppContext.set(null);
535
536
// Finally, we destroy the ThreadGroup entirely.
537
try {
538
this.threadGroup.destroy();
539
} catch (IllegalThreadStateException e) {
540
// Fired if not all the Threads died, ignore it and proceed
541
}
542
543
synchronized (table) {
544
this.table.clear(); // Clear out the Hashtable to ease garbage collection
545
}
546
547
numAppContexts.decrementAndGet();
548
549
mostRecentKeyValue = null;
550
}
551
552
static final class PostShutdownEventRunnable implements Runnable {
553
private final AppContext appContext;
554
555
PostShutdownEventRunnable(AppContext ac) {
556
appContext = ac;
557
}
558
559
public void run() {
560
final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY);
561
if (eq != null) {
562
eq.postEvent(AWTAutoShutdown.getShutdownEvent());
563
}
564
}
565
}
566
567
static final class CreateThreadAction implements PrivilegedAction<Thread> {
568
private final AppContext appContext;
569
private final Runnable runnable;
570
571
CreateThreadAction(AppContext ac, Runnable r) {
572
appContext = ac;
573
runnable = r;
574
}
575
576
public Thread run() {
577
Thread t = new Thread(appContext.getThreadGroup(),
578
runnable, "AppContext Disposer", 0, false);
579
t.setContextClassLoader(appContext.getContextClassLoader());
580
t.setPriority(Thread.NORM_PRIORITY + 1);
581
t.setDaemon(true);
582
return t;
583
}
584
}
585
586
static void stopEventDispatchThreads() {
587
for (AppContext appContext: getAppContexts()) {
588
if (appContext.isDisposed()) {
589
continue;
590
}
591
Runnable r = new PostShutdownEventRunnable(appContext);
592
// For security reasons EventQueue.postEvent should only be called
593
// on a thread that belongs to the corresponding thread group.
594
if (appContext != AppContext.getAppContext()) {
595
// Create a thread that belongs to the thread group associated
596
// with the AppContext and invokes EventQueue.postEvent.
597
PrivilegedAction<Thread> action = new CreateThreadAction(appContext, r);
598
@SuppressWarnings("removal")
599
Thread thread = AccessController.doPrivileged(action);
600
thread.start();
601
} else {
602
r.run();
603
}
604
}
605
}
606
607
private MostRecentKeyValue mostRecentKeyValue = null;
608
private MostRecentKeyValue shadowMostRecentKeyValue = null;
609
610
/**
611
* Returns the value to which the specified key is mapped in this context.
612
*
613
* @param key a key in the AppContext.
614
* @return the value to which the key is mapped in this AppContext;
615
* {@code null} if the key is not mapped to any value.
616
* @see #put(Object, Object)
617
* @since 1.2
618
*/
619
public Object get(Object key) {
620
/*
621
* The most recent reference should be updated inside a synchronized
622
* block to avoid a race when put() and get() are executed in
623
* parallel on different threads.
624
*/
625
synchronized (table) {
626
// Note: this most recent key/value caching is thread-hot.
627
// A simple test using SwingSet found that 72% of lookups
628
// were matched using the most recent key/value. By instantiating
629
// a simple MostRecentKeyValue object on cache misses, the
630
// cache hits can be processed without synchronization.
631
632
MostRecentKeyValue recent = mostRecentKeyValue;
633
if ((recent != null) && (recent.key == key)) {
634
return recent.value;
635
}
636
637
Object value = table.get(key);
638
if(mostRecentKeyValue == null) {
639
mostRecentKeyValue = new MostRecentKeyValue(key, value);
640
shadowMostRecentKeyValue = new MostRecentKeyValue(key, value);
641
} else {
642
MostRecentKeyValue auxKeyValue = mostRecentKeyValue;
643
shadowMostRecentKeyValue.setPair(key, value);
644
mostRecentKeyValue = shadowMostRecentKeyValue;
645
shadowMostRecentKeyValue = auxKeyValue;
646
}
647
return value;
648
}
649
}
650
651
/**
652
* Maps the specified {@code key} to the specified
653
* {@code value} in this AppContext. Neither the key nor the
654
* value can be {@code null}.
655
* <p>
656
* The value can be retrieved by calling the {@code get} method
657
* with a key that is equal to the original key.
658
*
659
* @param key the AppContext key.
660
* @param value the value.
661
* @return the previous value of the specified key in this
662
* AppContext, or {@code null} if it did not have one.
663
* @exception NullPointerException if the key or value is
664
* {@code null}.
665
* @see #get(Object)
666
* @since 1.2
667
*/
668
public Object put(Object key, Object value) {
669
synchronized (table) {
670
MostRecentKeyValue recent = mostRecentKeyValue;
671
if ((recent != null) && (recent.key == key))
672
recent.value = value;
673
return table.put(key, value);
674
}
675
}
676
677
/**
678
* Removes the key (and its corresponding value) from this
679
* AppContext. This method does nothing if the key is not in the
680
* AppContext.
681
*
682
* @param key the key that needs to be removed.
683
* @return the value to which the key had been mapped in this AppContext,
684
* or {@code null} if the key did not have a mapping.
685
* @since 1.2
686
*/
687
public Object remove(Object key) {
688
synchronized (table) {
689
MostRecentKeyValue recent = mostRecentKeyValue;
690
if ((recent != null) && (recent.key == key))
691
recent.value = null;
692
return table.remove(key);
693
}
694
}
695
696
/**
697
* Returns the root ThreadGroup for all Threads contained within
698
* this AppContext.
699
* @since 1.2
700
*/
701
public ThreadGroup getThreadGroup() {
702
return threadGroup;
703
}
704
705
/**
706
* Returns the context ClassLoader that was used to create this
707
* AppContext.
708
*
709
* @see java.lang.Thread#getContextClassLoader
710
*/
711
public ClassLoader getContextClassLoader() {
712
return contextClassLoader;
713
}
714
715
/**
716
* Returns a string representation of this AppContext.
717
* @since 1.2
718
*/
719
@Override
720
public String toString() {
721
return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]";
722
}
723
724
/**
725
* Returns an array of all the property change listeners
726
* registered on this component.
727
*
728
* @return all of this component's {@code PropertyChangeListener}s
729
* or an empty array if no property change
730
* listeners are currently registered
731
*
732
* @see #addPropertyChangeListener
733
* @see #removePropertyChangeListener
734
* @see #getPropertyChangeListeners(java.lang.String)
735
* @see java.beans.PropertyChangeSupport#getPropertyChangeListeners
736
* @since 1.4
737
*/
738
public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
739
if (changeSupport == null) {
740
return new PropertyChangeListener[0];
741
}
742
return changeSupport.getPropertyChangeListeners();
743
}
744
745
/**
746
* Adds a PropertyChangeListener to the listener list for a specific
747
* property. The specified property may be one of the following:
748
* <ul>
749
* <li>if this AppContext is disposed ("disposed")</li>
750
* </ul>
751
* <ul>
752
* <li>if this AppContext's unowned Windows have been disposed
753
* ("guidisposed"). Code to cleanup after the GUI is disposed
754
* (such as LookAndFeel.uninitialize()) should execute in response to
755
* this property being fired. Notifications for the "guidisposed"
756
* property are sent on the event dispatch thread.</li>
757
* </ul>
758
* <p>
759
* If listener is null, no exception is thrown and no action is performed.
760
*
761
* @param propertyName one of the property names listed above
762
* @param listener the PropertyChangeListener to be added
763
*
764
* @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
765
* @see #getPropertyChangeListeners(java.lang.String)
766
* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
767
*/
768
public synchronized void addPropertyChangeListener(
769
String propertyName,
770
PropertyChangeListener listener) {
771
if (listener == null) {
772
return;
773
}
774
if (changeSupport == null) {
775
changeSupport = new PropertyChangeSupport(this);
776
}
777
changeSupport.addPropertyChangeListener(propertyName, listener);
778
}
779
780
/**
781
* Removes a PropertyChangeListener from the listener list for a specific
782
* property. This method should be used to remove PropertyChangeListeners
783
* that were registered for a specific bound property.
784
* <p>
785
* If listener is null, no exception is thrown and no action is performed.
786
*
787
* @param propertyName a valid property name
788
* @param listener the PropertyChangeListener to be removed
789
*
790
* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
791
* @see #getPropertyChangeListeners(java.lang.String)
792
* @see PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
793
*/
794
public synchronized void removePropertyChangeListener(
795
String propertyName,
796
PropertyChangeListener listener) {
797
if (listener == null || changeSupport == null) {
798
return;
799
}
800
changeSupport.removePropertyChangeListener(propertyName, listener);
801
}
802
803
/**
804
* Returns an array of all the listeners which have been associated
805
* with the named property.
806
*
807
* @return all of the {@code PropertyChangeListeners} associated with
808
* the named property or an empty array if no listeners have
809
* been added
810
*
811
* @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
812
* @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
813
* @see #getPropertyChangeListeners
814
* @since 1.4
815
*/
816
public synchronized PropertyChangeListener[] getPropertyChangeListeners(
817
String propertyName) {
818
if (changeSupport == null) {
819
return new PropertyChangeListener[0];
820
}
821
return changeSupport.getPropertyChangeListeners(propertyName);
822
}
823
824
// Set up JavaAWTAccess in SharedSecrets
825
static {
826
SharedSecrets.setJavaAWTAccess(new JavaAWTAccess() {
827
@SuppressWarnings("removal")
828
private boolean hasRootThreadGroup(final AppContext ecx) {
829
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
830
@Override
831
public Boolean run() {
832
return ecx.threadGroup.getParent() == null;
833
}
834
});
835
}
836
837
/**
838
* Returns the AppContext used for applet logging isolation, or null if
839
* the default global context can be used.
840
* If there's no applet, or if the caller is a stand alone application,
841
* or running in the main app context, returns null.
842
* Otherwise, returns the AppContext of the calling applet.
843
* @return null if the global default context can be used,
844
* an AppContext otherwise.
845
**/
846
public Object getAppletContext() {
847
// There's no AppContext: return null.
848
// No need to call getAppContext() if numAppContext == 0:
849
// it means that no AppContext has been created yet, and
850
// we don't want to trigger the creation of a main app
851
// context since we don't need it.
852
if (numAppContexts.get() == 0) return null;
853
854
AppContext ecx = null;
855
856
// Not sure we really need to re-check numAppContexts here.
857
// If all applets have gone away then we could have a
858
// numAppContexts coming back to 0. So we recheck
859
// it here because we don't want to trigger the
860
// creation of a main AppContext in that case.
861
// This is probably not 100% MT-safe but should reduce
862
// the window of opportunity in which that issue could
863
// happen.
864
if (numAppContexts.get() > 0) {
865
// Defaults to thread group caching.
866
// This is probably not required as we only really need
867
// isolation in a deployed applet environment, in which
868
// case ecx will not be null when we reach here
869
// However it helps emulate the deployed environment,
870
// in tests for instance.
871
ecx = ecx != null ? ecx : getAppContext();
872
}
873
874
// getAppletContext() may be called when initializing the main
875
// app context - in which case mainAppContext will still be
876
// null. To work around this issue we simply use
877
// AppContext.threadGroup.getParent() == null instead, since
878
// mainAppContext is the only AppContext which should have
879
// the root TG as its thread group.
880
// See: JDK-8023258
881
final boolean isMainAppContext = ecx == null
882
|| mainAppContext == ecx
883
|| mainAppContext == null && hasRootThreadGroup(ecx);
884
885
return isMainAppContext ? null : ecx;
886
}
887
888
});
889
}
890
891
public static <T> T getSoftReferenceValue(Object key,
892
Supplier<T> supplier) {
893
894
final AppContext appContext = AppContext.getAppContext();
895
@SuppressWarnings("unchecked")
896
SoftReference<T> ref = (SoftReference<T>) appContext.get(key);
897
if (ref != null) {
898
final T object = ref.get();
899
if (object != null) {
900
return object;
901
}
902
}
903
final T object = supplier.get();
904
ref = new SoftReference<>(object);
905
appContext.put(key, ref);
906
return object;
907
}
908
}
909
910
final class MostRecentKeyValue {
911
Object key;
912
Object value;
913
MostRecentKeyValue(Object k, Object v) {
914
key = k;
915
value = v;
916
}
917
void setPair(Object k, Object v) {
918
key = k;
919
value = v;
920
}
921
}
922
923