package com.jogamp.nativewindow.awt; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import com.jogamp.common.util.RunnableTask; import jogamp.nativewindow.jawt.JAWTUtil; /** * Instance of this class holds information about a {@link ThreadGroup} associated {@link sun.awt.AppContext}. * <p> * Non intrusive workaround for Bug 983 and Bug 1004, see {@link #getCachedThreadGroup()}. * </p> */ public class AppContextInfo { private static final boolean DEBUG; private static final Method getAppContextMethod; private static final Object mainThreadAppContextLock = new Object(); private volatile WeakReference<Object> mainThreadAppContextWR = null; private volatile WeakReference<ThreadGroup> mainThreadGroupWR = null; static { DEBUG = JAWTUtil.DEBUG; final Method[] _getAppContextMethod = { null }; AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { final Class<?> appContextClass = Class.forName("sun.awt.AppContext"); _getAppContextMethod[0] = appContextClass.getMethod("getAppContext"); } catch(final Throwable ex) { System.err.println("Bug 1004: Caught @ static: "+ex.getMessage()); ex.printStackTrace(); } return null; } } ); getAppContextMethod = _getAppContextMethod[0]; } public AppContextInfo(final String info) { update(info); } /** * Returns <code>true</code> if this instance has valid {@link sun.awt.AppContext} information, * i.e. {@link #getCachedThreadGroup()} returns not <code>null</code>. */ public final boolean isValid() { return null != getCachedThreadGroup(); } /** * Returns the {@link ThreadGroup} belonging to the * last known {@link sun.awt.AppContext} as queried via {@link #update(String)}. * <p> * Returns <code>null</code> if no {@link sun.awt.AppContext} has been queried. * </p> * <p> * The returned {@link ThreadGroup} allows users to create a custom thread * belonging to it and hence mitigating Bug 983 and Bug 1004. * </p> * <p> * {@link #update(String)} should be called from a thread belonging to the * desired {@link sun.awt.AppContext}, i.e. early from within the special threaded application. * </p> * <p> * E.g. {@link JAWTWindow} issues {@link #update(String)} in it's constructor. * </p> */ public final ThreadGroup getCachedThreadGroup() { final WeakReference<ThreadGroup> tgRef = mainThreadGroupWR; return null != tgRef ? tgRef.get() : null; } /** * Invokes <code>runnable</code> on a {@link Thread} belonging to the {@link sun.awt.AppContext} {@link ThreadGroup}, * see {@link #getCachedThreadGroup()}. * <p> * {@link #update(String)} is issued first, which returns <code>true</code> * if the current thread belongs to an AppContext {@link ThreadGroup}. * In this case the <code>runnable</code> is invoked on the current thread, * otherwise a new {@link Thread} will be started. * </p> * <p> * If a new {@link Thread} is required, the AppContext {@link ThreadGroup} is being used * if {@link #isValid() available}, otherwise the default system {@link ThreadGroup}. * </p> * * @param waitUntilDone if <code>true</code>, waits until <code>runnable</code> execution is completed, otherwise returns immediately. * @param runnable the {@link Runnable} to be executed. If <code>waitUntilDone</code> is <code>true</code>, * the runnable <b>must exist</b>, i.e. not loop forever. * @param threadBaseName the base name for the new thread if required. * The resulting thread name will have either '-OnAppContextTG' or '-OnSystemTG' appended * @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. */ public RunnableTask invokeOnAppContextThread(final boolean waitUntilDone, final Runnable runnable, final String threadBaseName) { final RunnableTask rt; if( update("invoke") ) { rt = RunnableTask.invokeOnCurrentThread(runnable); if( DEBUG ) { System.err.println("Bug 1004: Invoke.0 on current AppContext: "+rt); } } else { final ThreadGroup tg = getCachedThreadGroup(); final String tName = threadBaseName + ( null != tg ? "-OnAppContextTG" : "-OnSystemTG" ); rt = RunnableTask.invokeOnNewThread(tg, tName, waitUntilDone, runnable); if( DEBUG ) { final int tgHash = null != tg ? tg.hashCode() : 0; System.err.println("Bug 1004: Invoke.1 on new AppContext: "+rt+", tg "+tg+" "+toHexString(tgHash)); } } return rt; } /** * Update {@link sun.awt.AppContext} information for the current ThreadGroup if uninitialized or {@link sun.awt.AppContext} changed. * <p> * See {@link #getCachedThreadGroup()} for usage. * </p> * @param info informal string for logging purposes * @return <code>true</code> if the current ThreadGroup is mapped to an {@link sun.awt.AppContext} and the information is good, otherwise false. */ public final boolean update(final String info) { if ( null != getAppContextMethod ) { // Test whether the current thread's ThreadGroup is mapped to an AppContext. final Object thisThreadAppContext = fetchAppContext(); final boolean tgMapped = null != thisThreadAppContext; final Thread thread = Thread.currentThread(); final ThreadGroup threadGroup = thread.getThreadGroup(); final Object mainThreadAppContext; { final WeakReference<Object> _mainThreadAppContextWR = mainThreadAppContextWR; mainThreadAppContext = null != _mainThreadAppContextWR ? _mainThreadAppContextWR.get() : null; } if( tgMapped ) { // null != thisThreadAppContext // Update info is possible if( null == mainThreadAppContext || mainThreadAppContext != thisThreadAppContext ) { // GC'ed or 1st fetch ! final int mainThreadAppContextHash = null != mainThreadAppContext ? mainThreadAppContext.hashCode() : 0; final int thisThreadAppContextHash; synchronized(mainThreadAppContextLock) { mainThreadGroupWR = new WeakReference<ThreadGroup>(threadGroup); mainThreadAppContextWR = new WeakReference<Object>(thisThreadAppContext); thisThreadAppContextHash = thisThreadAppContext.hashCode(); } if( DEBUG ) { System.err.println("Bug 1004[TGMapped "+tgMapped+"]: Init AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+ ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+ " -> appCtx [ main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash)+ " -> this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash) + " ] "); } } else { // old info is OK if( DEBUG ) { final int mainThreadAppContextHash = mainThreadAppContext.hashCode(); final int thisThreadAppContextHash = thisThreadAppContext.hashCode(); System.err.println("Bug 1004[TGMapped "+tgMapped+"]: OK AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+ ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+ " : appCtx [ this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash)+ " , main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash) + " ] "); } } return true; } else { if( DEBUG ) { final int mainThreadAppContextHash = null != mainThreadAppContext ? mainThreadAppContext.hashCode() : 0; final int thisThreadAppContextHash = null != thisThreadAppContext ? thisThreadAppContext.hashCode() : 0; System.err.println("Bug 1004[TGMapped "+tgMapped+"]: No AppContext @ "+info+" on thread "+thread.getName()+" "+toHexString(thread.hashCode())+ ": tg "+threadGroup.getName()+" "+toHexString(threadGroup.hashCode())+ " -> appCtx [ this "+thisThreadAppContext+" "+toHexString(thisThreadAppContextHash)+ " -> main "+mainThreadAppContext+" "+toHexString(mainThreadAppContextHash) + " ] "); } } } return false; } private static Object fetchAppContext() { try { return getAppContextMethod.invoke(null); } catch(final Exception ex) { System.err.println("Bug 1004: Caught: "+ex.getMessage()); ex.printStackTrace(); return null; } } private static String toHexString(final int i) { return "0x"+Integer.toHexString(i); } }