aboutsummaryrefslogtreecommitdiffstats
path: root/src/nativewindow/classes/com/jogamp/nativewindow/awt/AppContextInfo.java
blob: e5dcfa1c04911e42872e0ec4314eeee25b39cb35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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);
  }

}