SWT is no longer using reflection + javasist

This commit is contained in:
nathan 2020-08-06 02:31:38 +02:00
parent cdf7ad030b
commit dd75fff9ed

View File

@ -19,36 +19,25 @@ import java.lang.reflect.Method;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import org.slf4j.LoggerFactory; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import dorkbox.jna.JnaClassUtils; import dorkbox.os.OS;
import dorkbox.util.OS;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
/** /**
* Utility methods for SWT. Some of the methods will be overwritten via Javassist so we don't require a hard dependency on SWT. * Utility methods for SWT. SWT is always available for compiling, so it is not necessary to use reflection to compile it.
* <p> * <p>
* SWT system tray types are GtkStatusIcon trays (so we don't want to use them) * SWT system tray types are GtkStatusIcon trays (so we don't want to use them)
*/ */
@SuppressWarnings("Convert2Lambda")
public public
class Swt { class Swt {
public final static boolean isLoaded; public final static boolean isLoaded;
public final static boolean isGtk3; public final static boolean isGtk3;
private static final int version; private static final int version;
private static final Object currentDisplay; private static final Display currentDisplay;
private static final Thread currentDisplayThread; private static final Thread currentDisplayThread;
// Methods are cached for performance
private static final Method syncExecMethod;
static { static {
// There is a silly amount of redirection, simply because we have to be able to access SWT, but only if it's in use. // There is a silly amount of redirection, simply because we have to be able to access SWT, but only if it's in use.
// Since this class is the place other code interacts with, we can use SWT stuff if necessary without loading/linking // Since this class is the place other code interacts with, we can use SWT stuff if necessary without loading/linking
@ -56,95 +45,14 @@ class Swt {
boolean isSwtLoadable_ = isLoadable(); boolean isSwtLoadable_ = isLoadable();
int _version = 0; version = SWT.getVersion();
if (isSwtLoadable_) {
try {
// _version = SWT.getVersion();
Class<?> clazz = Class.forName("org.eclipse.swt.SWT");
Method _getVersionMethod = clazz.getMethod("getVersion", int.class);
_version = (Integer) _getVersionMethod.invoke(null);
} catch (Exception ignored) {
}
}
version = _version;
isLoaded = isSwtLoadable_; isLoaded = isSwtLoadable_;
isGtk3 = isSwtLoadable_ && isGtk3(); isGtk3 = isSwtLoadable_ && isGtk3();
// we MUST save this on init, otherwise it is "null" when methods are run from the swing EDT. // we MUST save this on init, otherwise it is "null" when methods are run from the swing EDT.
// currentDisplay = org.eclipse.swt.widgets.Display.getCurrent();
// currentDisplay = org.eclipse.swt.widgets.Display.getCurrent(); currentDisplayThread = currentDisplay.getThread();
// currentDisplayThread = currentDisplay.getThread();
Object _currentDisplay = null;
Thread _currentDisplayThread = null;
Method _syncExecMethod = null;
if (isSwtLoadable_) {
try {
Class<?> clazz = Class.forName("org.eclipse.swt.widgets.Display");
Method getCurrentMethod = clazz.getMethod("getCurrent");
Method getThreadMethod = clazz.getMethod("getThread");
_syncExecMethod = clazz.getDeclaredMethod("syncExec", Runnable.class);
_currentDisplay = getCurrentMethod.invoke(null);
_currentDisplayThread = (Thread) getThreadMethod.invoke(_currentDisplay);
// re-write the part that is heavily SWT dependent, that cannot be done via reflection.
byte[] bytes;
String body;
CtMethod method;
CtField ctField;
ClassPool pool = ClassPool.getDefault();
CtClass swtOverriedClass = pool.get("dorkbox.util.Swt$SwtOverride");
// the abstractions for listener are REQUIRED by javassist.
{
CtClass listener = pool.makeClass("dorkbox.util.Swt_listener");
listener.addInterface(pool.get("org.eclipse.swt.widgets.Listener"));
ctField = new CtField(pool.get("java.lang.Runnable"), "runnable", listener);
ctField.setModifiers(Modifier.PROTECTED);
listener.addField(ctField);
method = CtNewMethod.make(CtClass.voidType, "handleEvent",
new CtClass[]{pool.get("org.eclipse.swt.widgets.Event")}, null,
"{" +
" this.runnable.run();" +
"}", listener);
listener.addMethod(method);
bytes = listener.toBytecode();
JnaClassUtils.defineClass(bytes);
}
method = swtOverriedClass.getDeclaredMethod("onShutdown");
body = "{" +
"org.eclipse.swt.widgets.Display currentDisplay = (org.eclipse.swt.widgets.Display)$1;" +
"Runnable runnable = $2;" +
"dorkbox.util.Swt_listener listener = new dorkbox.util.Swt_listener();" +
"listener.runnable = runnable;" +
"org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0];" +
"shell.addListener(org.eclipse.swt.SWT.Close, listener);" +
"}";
method.setBody(body);
bytes = swtOverriedClass.toBytecode();
// define this new class in our current classloader
JnaClassUtils.defineClass(bytes);
} catch (Throwable e) {
LoggerFactory.getLogger(Swt.class).error("Cannot initialize SWT", e);
}
}
currentDisplay = _currentDisplay;
currentDisplayThread = _currentDisplayThread;
syncExecMethod = _syncExecMethod;
} }
/** /**
@ -168,18 +76,14 @@ class Swt {
} }
}); });
boolean isLoadable = false; if (null != swtErrorClass) {
try {
try { return org.eclipse.swt.SWT.isLoadable();
// org.eclipse.swt.SWT.isLoadable(); } catch (Exception ignored) {
Class<?> clazz = Class.forName("org.eclipse.swt.SWT"); }
Method _isLoadableMethod = clazz.getMethod("isLoadable");
isLoadable = (Boolean) _isLoadableMethod.invoke(null);
} catch (Exception ignored) {
} }
return false;
return null != swtErrorClass && isLoadable;
} }
/** /**
@ -244,23 +148,20 @@ class Swt {
} }
// this class is over-written via Javassist, because reflection cannot create anonymous classes. Javassist can, with caveats.
@SuppressWarnings("unused")
public static public static
class SwtOverride { class SwtOverride {
static static
void onShutdown(final Object currentDisplay, final Runnable runnable) { void onShutdown(final Display currentDisplay, final Runnable runnable) {
// currentDisplay.getShells() must only be called inside the event thread! // currentDisplay.getShells() must only be called inside the event thread!
// org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0]; org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0];
// shell.addListener(org.eclipse.swt.SWT.Close, new org.eclipse.swt.widgets.Listener() { shell.addListener(org.eclipse.swt.SWT.Close, new org.eclipse.swt.widgets.Listener() {
// @Override @Override
// public public
// void handleEvent(final org.eclipse.swt.widgets.Event event) { void handleEvent(final org.eclipse.swt.widgets.Event event) {
// runnable.run(); runnable.run();
// } }
// }); });
throw new RuntimeException("This should never happen, as this class is over-written at runtime.");
} }
} }
@ -272,14 +173,7 @@ class Swt {
public static public static
void dispatch(final Runnable runnable) { void dispatch(final Runnable runnable) {
// currentDisplay.syncExec(runnable); currentDisplay.syncExec(runnable);
try {
syncExecMethod.invoke(currentDisplay, runnable);
} catch (Throwable e) {
LoggerFactory.getLogger(Swt.class)
.error("Unable to execute JavaFX runLater(). Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
} }
public static public static