diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nativewindow/classes/com/jogamp/nativewindow/awt/AppContextInfo.java | 199 | ||||
-rw-r--r-- | src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java | 26 |
2 files changed, 221 insertions, 4 deletions
diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/awt/AppContextInfo.java b/src/nativewindow/classes/com/jogamp/nativewindow/awt/AppContextInfo.java new file mode 100644 index 000000000..54762bb77 --- /dev/null +++ b/src/nativewindow/classes/com/jogamp/nativewindow/awt/AppContextInfo.java @@ -0,0 +1,199 @@ +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(Throwable ex) { + System.err.println("Bug 1004: Catched @ 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 Thread invokeOnAppContextThread(final boolean waitUntilDone, final Runnable runnable, final String threadBaseName) { + final Thread t; + if( update("invoke") ) { + t = Thread.currentThread(); + if( DEBUG ) { + System.err.println("Bug 1004: Invoke.0 on current AppContext thread: "+t+" "+toHexString(t.hashCode())); + } + runnable.run(); + } else { + final ThreadGroup tg = getCachedThreadGroup(); + final String tName = threadBaseName + ( null != tg ? "-OnAppContextTG" : "-OnSystemTG" ); + t = RunnableTask.invokeOnNewThread(tg, waitUntilDone, runnable, tName); + if( DEBUG ) { + final int tgHash = null != tg ? tg.hashCode() : 0; + System.err.println("Bug 1004: Invoke.1 on new AppContext thread: "+t+" "+toHexString(t.hashCode())+", tg "+tg+" "+toHexString(tgHash)); + } + } + return t; + } + + /** + * 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 = null != mainThreadAppContext ? mainThreadAppContext.hashCode() : 0; + final int thisThreadAppContextHash = null != thisThreadAppContext ? thisThreadAppContext.hashCode() : 0; + 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(Exception ex) { + System.err.println("Bug 1004: Catched: "+ex.getMessage()); + ex.printStackTrace(); + return null; + } + } + + private static String toHexString(final int i) { + return "0x"+Integer.toHexString(i); + } + +} diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java b/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java index bb7b44795..e2a4ad4bd 100644 --- a/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java +++ b/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java @@ -84,6 +84,7 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, // lifetime: forever protected final Component component; + private final AppContextInfo appContextInfo; private final AWTGraphicsConfiguration config; // control access due to delegation private final SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper(); private final RecursiveLock surfaceLock = LockFactory.createRecursiveLock(); @@ -114,6 +115,7 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, if(! ( config instanceof AWTGraphicsConfiguration ) ) { throw new NativeWindowException("Error: AbstractGraphicsConfiguration is not an AWTGraphicsConfiguration: "+config); } + appContextInfo = new AppContextInfo("<init>"); this.component = (Component)comp; this.config = (AWTGraphicsConfiguration) config; this.jawtComponentListener = new JAWTComponentListener(); @@ -121,7 +123,7 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, this.isApplet = false; this.offscreenSurfaceLayer = 0; } - private static String id(Object obj) { return "0x" + ( null!=obj ? Integer.toHexString(obj.hashCode()) : "nil" ); } + private static String id(Object obj) { return ( null!=obj ? toHexString(obj.hashCode()) : "nil" ); } private String jawtStr() { return "JAWTWindow["+id(JAWTWindow.this)+"]"; } private class JAWTComponentListener implements ComponentListener, HierarchyListener { @@ -332,8 +334,20 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, } attachSurfaceLayerImpl(layerHandle); offscreenSurfaceLayer = layerHandle; - component.repaint(); + appContextInfo.invokeOnAppContextThread(false /* waitUntilDone */, repaintTask, "Repaint"); } + private final Runnable repaintTask = new Runnable() { + @Override + public void run() { + final Component c = component; + if( DEBUG ) { + System.err.println("Bug 1004: RepaintTask on "+Thread.currentThread()+": Has Comp "+(null != c)); + } + if( null != c ) { + c.repaint(); + } + } }; + protected void attachSurfaceLayerImpl(final long layerHandle) { throw new UnsupportedOperationException("offscreen layer not supported"); } @@ -744,7 +758,7 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, append(Platform.JAVA_VERSION_NUMBER). append(" update ").append(Platform.JAVA_VERSION_UPDATE).append(")").append(Platform.getNewline()); if(null != jawt) { - sb.append("JAWT version: 0x").append(Integer.toHexString(jawt.getCachedVersion())). + sb.append("JAWT version: ").append(toHexString(jawt.getCachedVersion())). append(", CA_LAYER: ").append(JAWTUtil.isJAWTUsingOffscreenLayer(jawt)). append(", isLayeredSurface ").append(isOffscreenLayerSurfaceEnabled()).append(", bounds ").append(bounds).append(", insets ").append(insets); } else { @@ -775,7 +789,11 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface, return sb.toString(); } - protected final String toHexString(long l) { + protected static final String toHexString(final long l) { return "0x"+Long.toHexString(l); } + protected static final String toHexString(final int i) { + return "0x"+Integer.toHexString(i); + } + } |