JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
AppContextInfo.java
Go to the documentation of this file.
1package com.jogamp.nativewindow.awt;
2
3import java.lang.ref.WeakReference;
4import java.lang.reflect.Method;
5import java.security.PrivilegedAction;
6
7import com.jogamp.common.ExceptionUtils;
8import com.jogamp.common.util.RunnableTask;
9import com.jogamp.common.util.SecurityUtil;
10import com.jogamp.common.util.UnsafeUtil;
11
12import jogamp.nativewindow.jawt.JAWTUtil;
13
14/**
15 * Instance of this class holds information about a {@link ThreadGroup} associated {@link sun.awt.AppContext}.
16 * <p>
17 * Non intrusive workaround for Bug 983 and Bug 1004, see {@link #getCachedThreadGroup()}.
18 * </p>
19 */
20public class AppContextInfo {
21 private static final boolean DEBUG;
22
23 private static final Method getAppContextMethod;
24 private static final Object mainThreadAppContextLock = new Object();
25 private volatile WeakReference<Object> mainThreadAppContextWR = null;
26 private volatile WeakReference<ThreadGroup> mainThreadGroupWR = null;
27
28 static {
29 DEBUG = JAWTUtil.DEBUG;
30 final Method[] _getAppContextMethod = { null };
31 SecurityUtil.doPrivileged(new PrivilegedAction<Object>() {
32 @Override
33 public Object run() {
34 return UnsafeUtil.doWithoutIllegalAccessLogger(new PrivilegedAction<Object>() {
35 @Override
36 public Object run() {
37 try {
38 final Class<?> appContextClass = Class.forName("sun.awt.AppContext");
39 _getAppContextMethod[0] = appContextClass.getMethod("getAppContext");
40 _getAppContextMethod[0].setAccessible(true);
41 } catch(final Throwable ex) {
42 ExceptionUtils.dumpThrowable("AppContextInfo(Bug 1004)", ex);
43 }
44 return null;
45 }}); }});
46 getAppContextMethod = _getAppContextMethod[0];
47 }
48
49 public AppContextInfo(final String info) {
50 update(info);
51 }
52
53 /**
54 * Returns <code>true</code> if this instance has valid {@link sun.awt.AppContext} information,
55 * i.e. {@link #getCachedThreadGroup()} returns not <code>null</code>.
56 */
57 public final boolean isValid() {
58 return null != getCachedThreadGroup();
59 }
60
61 /**
62 * Returns the {@link ThreadGroup} belonging to the
63 * last known {@link sun.awt.AppContext} as queried via {@link #update(String)}.
64 * <p>
65 * Returns <code>null</code> if no {@link sun.awt.AppContext} has been queried.
66 * </p>
67 * <p>
68 * The returned {@link ThreadGroup} allows users to create a custom thread
69 * belonging to it and hence mitigating Bug 983 and Bug 1004.
70 * </p>
71 * <p>
72 * {@link #update(String)} should be called from a thread belonging to the
73 * desired {@link sun.awt.AppContext}, i.e. early from within the special threaded application.
74 * </p>
75 * <p>
76 * E.g. {@link JAWTWindow} issues {@link #update(String)} in it's constructor.
77 * </p>
78 */
79 public final ThreadGroup getCachedThreadGroup() {
80 final WeakReference<ThreadGroup> tgRef = mainThreadGroupWR;
81 return null != tgRef ? tgRef.get() : null;
82 }
83
84 /**
85 * Invokes <code>runnable</code> on a {@link Thread} belonging to the {@link sun.awt.AppContext} {@link ThreadGroup},
86 * see {@link #getCachedThreadGroup()}.
87 * <p>
88 * {@link #update(String)} is issued first, which returns <code>true</code>
89 * if the current thread belongs to an AppContext {@link ThreadGroup}.
90 * In this case the <code>runnable</code> is invoked on the current thread,
91 * otherwise a new {@link Thread} will be started.
92 * </p>
93 * <p>
94 * If a new {@link Thread} is required, the AppContext {@link ThreadGroup} is being used
95 * if {@link #isValid() available}, otherwise the default system {@link ThreadGroup}.
96 * </p>
97 *
98 * @param waitUntilDone if <code>true</code>, waits until <code>runnable</code> execution is completed, otherwise returns immediately.
99 * @param runnable the {@link Runnable} to be executed. If <code>waitUntilDone</code> is <code>true</code>,
100 * the runnable <b>must exist</b>, i.e. not loop forever.
101 * @param threadBaseName the base name for the new thread if required.
102 * The resulting thread name will have either '-OnAppContextTG' or '-OnSystemTG' appended
103 * @return the {@link Thread} used to invoke the <code>runnable</code>, which may be the current {@link Thread} or a newly created one, see above.
104 */
105 public RunnableTask invokeOnAppContextThread(final boolean waitUntilDone, final Runnable runnable, final String threadBaseName) {
106 final RunnableTask rt;
107 if( update("invoke") ) {
108 rt = RunnableTask.invokeOnCurrentThread(runnable);
109 if( DEBUG ) {
110 System.err.println("Bug 1004: Invoke.0 on current AppContext: "+rt);
111 }
112 } else {
113 final ThreadGroup tg = getCachedThreadGroup();
114 final String tName = threadBaseName + ( null != tg ? "-OnAppContextTG" : "-OnSystemTG" );
115 rt = RunnableTask.invokeOnNewThread(tg, tName, waitUntilDone, runnable);
116 if( DEBUG ) {
117 final int tgHash = null != tg ? tg.hashCode() : 0;
118 System.err.println("Bug 1004: Invoke.1 on new AppContext: "+rt+", tg "+tg+" "+toHexString(tgHash));
119 }
120 }
121 return rt;
122 }
123
124 /**
125 * Update {@link sun.awt.AppContext} information for the current ThreadGroup if uninitialized or {@link sun.awt.AppContext} changed.
126 * <p>
127 * See {@link #getCachedThreadGroup()} for usage.
128 * </p>
129 * @param info informal string for logging purposes
130 * @return <code>true</code> if the current ThreadGroup is mapped to an {@link sun.awt.AppContext} and the information is good, otherwise false.
131 */
132 public final boolean update(final String info) {
133 if ( null != getAppContextMethod ) {
134 // Test whether the current thread's ThreadGroup is mapped to an AppContext.
135 final Object thisThreadAppContext = fetchAppContext();
136 final boolean tgMapped = null != thisThreadAppContext;
137
138 final Thread thread = Thread.currentThread();
139 final ThreadGroup threadGroup = thread.getThreadGroup();
140 final Object mainThreadAppContext;
141 {
142 final WeakReference<Object> _mainThreadAppContextWR = mainThreadAppContextWR;
143 mainThreadAppContext = null != _mainThreadAppContextWR ? _mainThreadAppContextWR.get() : null;
144 }
145
146 if( tgMapped ) { // null != thisThreadAppContext
147 // Update info is possible
148 if( null == mainThreadAppContext ||
149 mainThreadAppContext != thisThreadAppContext ) {
150 // GC'ed or 1st fetch !
151 final int mainThreadAppContextHash = null != mainThreadAppContext ? mainThreadAppContext.hashCode() : 0;
152 final int thisThreadAppContextHash;
153 synchronized(mainThreadAppContextLock) {
154 mainThreadGroupWR = new WeakReference<ThreadGroup>(threadGroup);
155 mainThreadAppContextWR = new WeakReference<Object>(thisThreadAppContext);
156 thisThreadAppContextHash = thisThreadAppContext.hashCode();
157 }
158 if( DEBUG ) {
159 System.err.println("Bug 1004[TGMapped "+tgMapped+"]: Init AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+
160 ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+
161 " -> appCtx [ main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash)+
162 " -> this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash) + " ] ");
163 }
164 } else {
165 // old info is OK
166 if( DEBUG ) {
167 final int mainThreadAppContextHash = mainThreadAppContext.hashCode();
168 final int thisThreadAppContextHash = thisThreadAppContext.hashCode();
169 System.err.println("Bug 1004[TGMapped "+tgMapped+"]: OK AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+
170 ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+
171 " : appCtx [ this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash)+
172 " , main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash) + " ] ");
173 }
174 }
175 return true;
176 } else {
177 if( DEBUG ) {
178 final int mainThreadAppContextHash = null != mainThreadAppContext ? mainThreadAppContext.hashCode() : 0;
179 final int thisThreadAppContextHash = null != thisThreadAppContext ? thisThreadAppContext.hashCode() : 0;
180 System.err.println("Bug 1004[TGMapped "+tgMapped+"]: No AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+
181 ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+
182 " -> appCtx [ this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash)+
183 " -> main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash) + " ] ");
184 }
185 }
186 }
187 return false;
188 }
189 private static Object fetchAppContext() {
190 try {
191 return getAppContextMethod.invoke(null);
192 } catch(final Exception ex) {
193 ExceptionUtils.dumpThrowable("AppContextInfo(Bug 1004)", ex);
194 return null;
195 }
196 }
197
198 private static String toHexString(final int i) {
199 return "0x"+Integer.toHexString(i);
200 }
201
202}
Instance of this class holds information about a ThreadGroup associated sun.awt.AppContext.
final boolean isValid()
Returns true if this instance has valid sun.awt.AppContext information, i.e.
final boolean update(final String info)
Update sun.awt.AppContext information for the current ThreadGroup if uninitialized or sun....
final ThreadGroup getCachedThreadGroup()
Returns the ThreadGroup belonging to the last known sun.awt.AppContext as queried via update(String).
RunnableTask invokeOnAppContextThread(final boolean waitUntilDone, final Runnable runnable, final String threadBaseName)
Invokes runnable on a Thread belonging to the sun.awt.AppContext ThreadGroup, see getCachedThreadGrou...