diff --git a/src/dorkbox/util/ClassLoaderUtil.java b/src/dorkbox/util/ClassLoaderUtil.java index 97f2fbd..85131c4 100755 --- a/src/dorkbox/util/ClassLoaderUtil.java +++ b/src/dorkbox/util/ClassLoaderUtil.java @@ -16,19 +16,18 @@ package dorkbox.util; import java.lang.reflect.Method; -import java.util.Arrays; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.HashMap; -import java.util.List; import java.util.Map; +import com.sun.jna.JNIEnv; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.PointerByReference; import com.sun.jna.win32.StdCallFunctionMapper; +// http://hg.openjdk.java.net/jdk/jdk10/file/b09e56145e11/src/java.base/share/native/libjava/ClassLoader.c // http://hg.openjdk.java.net/jdk10/jdk10/jdk/file/777356696811/src/java.base/share/native/libjava/ClassLoader.c // http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/65464a307408/src/java.base/share/native/libjava/ClassLoader.c // http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/be698ac28848/src/share/native/java/lang/ClassLoader.c @@ -44,175 +43,185 @@ import com.sun.jna.win32.StdCallFunctionMapper; */ @SuppressWarnings("WeakerAccess") public class ClassLoaderUtil { + private static JVM libjvm; - @SuppressWarnings({"unchecked", "unused"}) - public static class Bootstrap { - public static final int JNI_VERSION_1_1 = 0x00010001; - public static final int JNI_VERSION_1_2 = 0x00010002; - public static final int JNI_VERSION_1_4 = 0x00010004; - public static final int JNI_VERSION_1_6 = 0x00010006; - public static final int JNI_VERSION_1_7 = 0x00010007; - public static final int JNI_VERSION_1_8 = 0x00010008; + // Note: this does not work in java8 x86 *on windows XP windows7, etc. It only works on x64 + @SuppressWarnings("UnusedReturnValue") + public + interface JVM extends com.sun.jna.Library { + void JVM_DefineClass(JNIEnv env, String name, Object classLoader, byte[] buffer, int length, Object protectionDomain); + Class JVM_FindLoadedClass(JNIEnv env, Object classLoader, String name); + } - // if we want to change the JNI version, this is how we do it. - public static int JNI_VERSION = JNI_VERSION_1_4; - - private static JVM libjvm; - - public static - class JavaVM extends Structure { - public static - class ByReference extends JavaVM implements Structure.ByReference {} - - public volatile JNIInvokeInterface.ByReference functions; - - JavaVM() { - } - - @Override - protected - List getFieldOrder() { - //noinspection ArraysAsListWithZeroOrOneArgument - return Arrays.asList("functions"); - } - } - - // Note: this does not work in java8 x86 *on windows XP windows7, etc. It only works on x64 - @SuppressWarnings("UnusedReturnValue") - public - interface JVM extends com.sun.jna.Library { - void JVM_DefineClass(Pointer env, String name, Object loader, byte[] buffer, int length, Object protectionDomain); - int JNI_GetCreatedJavaVMs(JavaVM.ByReference[] vmArray, int bufsize, int[] vmCount); - } - - @SuppressWarnings("unused") - public static - class JNIInvokeInterface extends Structure { - public static - class ByReference extends JNIInvokeInterface implements Structure.ByReference {} - - public volatile Pointer reserved0; - public volatile Pointer reserved1; - public volatile Pointer reserved2; - - public volatile Pointer DestroyJavaVM; - public volatile Pointer AttachCurrentThread; - public volatile Pointer DetachCurrentThread; - - public volatile GetEnv GetEnv; - public volatile Pointer AttachCurrentThreadAsDaemon; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("reserved0", - "reserved1", - "reserved2", - "DestroyJavaVM", - "AttachCurrentThread", - "DetachCurrentThread", - "GetEnv", - "AttachCurrentThreadAsDaemon"); - } - - public - interface GetEnv extends com.sun.jna.Callback { - int callback(JavaVM.ByReference vm, PointerByReference penv, int version); - } - } - - static { - String libName; - if (OS.isMacOsX()) { - if (OS.javaVersion < 7) { - libName = "JavaVM"; - } else { - String javaLocation = System.getProperty("java.home"); - - // have to explicitly specify the JVM library via full path - // this is OK, because for java on MacOSX, this is the only location it can exist - libName = javaLocation + "/lib/server/libjvm.dylib"; - } - } - else { - libName = "jvm"; - } - - // function name is SLIGHTLY different on windows x32 java builds. - // For actual name use: http://www.nirsoft.net/utils/dll_export_viewer.html - if (OS.isWindows() && OS.is32bit()) { - Map options = new HashMap(); - options.put(Library.OPTION_FUNCTION_MAPPER, new StdCallFunctionMapper() { - @Override - public - String getFunctionName(NativeLibrary library, Method method) { - String methodName = method.getName(); - if (methodName.equals("JVM_DefineClass")) { - // specifically Oracle Java 32bit builds. Tested on XP and Win7 - return "_JVM_DefineClass@24"; - } - return methodName; - } - }); - libjvm = Native.loadLibrary(libName, JVM.class, options); + private static final String libName; + static { + if (OS.isMacOsX()) { + if (OS.javaVersion < 7) { + libName = "JavaVM"; } else { - libjvm = Native.loadLibrary(libName, JVM.class); + String javaLocation = System.getProperty("java.home"); + + // have to explicitly specify the JVM library via full path + // this is OK, because for java on MacOSX, this is the only location it can exist + libName = javaLocation + "/lib/server/libjvm.dylib"; } } + else { + libName = "jvm"; + } - /** - * Inject class bytes directly into the bootstrap classloader. - *

- * This is a VERY DANGEROUS method to use! - * - * @param classBytes - * the bytes to inject - */ - public static - void defineClass(byte[] classBytes) throws Exception { - // get the number of JVM's running - int[] jvmCount = {100}; - libjvm.JNI_GetCreatedJavaVMs(null, 0, jvmCount); - // actually get the JVM's - JavaVM.ByReference[] vms = new JavaVM.ByReference[jvmCount[0]]; - for (int i = 0, vmsLength = vms.length; i < vmsLength; i++) { - vms[i] = new JavaVM.ByReference(); - } + // function name is SLIGHTLY different on windows x32 java builds. + // For actual name use: http://www.nirsoft.net/utils/dll_export_viewer.html + Map options = new HashMap(); + options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE); - // now get the JVM's - libjvm.JNI_GetCreatedJavaVMs(vms, vms.length, jvmCount); - - Exception exception = null; - for (int i = 0; i < jvmCount[0]; ++i) { - JavaVM.ByReference vm = vms[i]; - PointerByReference penv = new PointerByReference(); - vm.functions.GetEnv.callback(vm, penv, ClassLoaderUtil.Bootstrap.JNI_VERSION); - - // inject into all JVM's that are started by us (is USUALLY 1, but not always) - try { - libjvm.JVM_DefineClass(penv.getValue(), null, null, classBytes, classBytes.length, null); - } catch (Exception e) { - exception = e; + if (OS.isWindows() && OS.is32bit()) { + options.put(Library.OPTION_FUNCTION_MAPPER, new StdCallFunctionMapper() { + @Override + public + String getFunctionName(NativeLibrary library, Method method) { + String methodName = method.getName(); + if (methodName.equals("JVM_DefineClass")) { + // specifically Oracle Java 32bit builds. Tested on XP and Win7 + return "_JVM_DefineClass@24"; + } + return methodName; } - } - - // something failed, just show us THE LAST of the failures - if (exception != null) { - throw exception; - } + }); + libjvm = Native.load(libName, JVM.class, options); + } else { + libjvm = Native.load(libName, JVM.class, options); } } + /** * Defines a class in the current threads class-loader - * @param bytes the bytes of the class to define + * + * @param classBytes the bytes of the class to define */ public static - void defineClass(final byte[] bytes) throws Exception { + void defineClass(final byte[] classBytes) throws Exception { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); - defineClass.setAccessible(true); - defineClass.invoke(classLoader, bytes, 0, bytes.length); + + defineClass(classLoader, classBytes); + } + + /** + * Inject class bytes directly into the bootstrap classloader. + *

+ * This is a VERY DANGEROUS method to use! + * + * @param classLoader the classLoader to use. null will use the BOOTSTRAP classloader + * @param classBytes the bytes to inject + */ + public static + void defineClass(ClassLoader classLoader, byte[] classBytes) throws Exception { + // inject into the FIRST JVM that are started by us (is USUALLY 1, but not always) + libjvm.JVM_DefineClass(JNIEnv.CURRENT, null, classLoader, classBytes, classBytes.length, null); + } + + /** + * Check to see if a class is already loaded or not, WITHOUT linking/loading the class! + * + * You should check all accessible classLoaders! + * + * @param classLoader the classLoader to use. null will use the BOOTSTRAP classloader + * @param className the name to check + */ + public static + boolean isClassLoaded(ClassLoader classLoader, String className) { + // Pointer javaEnv = getJavaEnv(); + // + // NativeLibrary library = ((Library.Handler) libjvm).getNativeLibrary(); + // Function jvm_findLoadedClass = library.getFunction("JVM_FindLoadedClass"); + // jvm_findLoadedClass.invokeObject() + + // this.peer = library.getSymbolAddress(functionName); + + + // result = Native.invokeObject(this, this.peer, callFlags, args); + // jstring NewStringUTF(JNIEnv *env, const char *bytes); + // libjvm.JVM_FindLoadedClass(JNIEnv.CURRENT, classLoader, new JString(className)); + + // try { + // if (OS.javaVersion < 9) { + // // so we can use reflection to check, but only if we are < java9 + // + // } else { + // // if we are java9+, we are "screwed" in that we cannot use reflection to tell if any of the SWT classes are ALREADY loaded. + // + // // // there are problems + // // + // // Class c = new ClassLoader() { + // // Class c = findLoadedClass("org.eclipse.swt.widgets.Display"); + // // + // // }.c; + // // + // // // isSwtLoaded_ = SWT.isLoadable() && c != null; + // // isSwtLoaded_= null != Class.forName("org.eclipse.swt.SWTError", false, Swt.class.getClassLoader()); + // } + // // isSwtLoaded_= null != Class.forName("org.eclipse.swt.widgets.Display", false, Swt.class.getClassLoader()); + // + // // final String SWT_INTERNAL_CLASS = "org.eclipse.swt.internal.gtk.OS"; + // + // // FindClassLoader cl = (FindClassLoader) FindClassLoader.class.getClassLoader(); + // // isSwtLoaded_= null != FindClassLoader.find(cl, "org.eclipse.swt.widgets.Display"); + // } catch (Throwable e) { + // LoggerFactory.getLogger(Swt.class).debug("Error detecting if SWT is loaded", e); + // } + + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public + Boolean run() { + try { + Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + m.setAccessible(true); + + return null != m.invoke(classLoader, className); + } catch (Exception ignored) { + return false; + } + } + }); + } + + /** + * Check to see if a class is already loaded or not. + */ + public static + boolean isLibraryLoaded(String libraryName) { + // JVM_FindLoadedClass + + // JVM_FindLibraryEntry + + // /* + // * Class: java_lang_ClassLoader_NativeLibrary + // * Method: find + // * Signature: (Ljava/lang/String;)J + // */ + // JNIEXPORT jlong JNICALL + // Java_java_lang_ClassLoader_00024NativeLibrary_find + // (JNIEnv *env, jobject this, jstring name) + // { + // jlong handle; + // const char *cname; + // jlong res; + // + // if (!initIDs(env)) + // return jlong_zero; + // + // handle = (*env)->GetLongField(env, this, handleID); + // cname = (*env)->GetStringUTFChars(env, name, 0); + // if (cname == 0) + // return jlong_zero; + // res = ptr_to_jlong(JVM_FindLibraryEntry(jlong_to_ptr(handle), cname)); + // (*env)->ReleaseStringUTFChars(env, name, cname); + // return res; + // } + + return false; } } diff --git a/src/dorkbox/util/JavaFX.java b/src/dorkbox/util/JavaFX.java deleted file mode 100644 index 4f2fbce..0000000 --- a/src/dorkbox/util/JavaFX.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2016 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util; - - -import java.lang.reflect.Method; - -import org.slf4j.LoggerFactory; - -/** - * Utility methods for JavaFX. - *

- * We use reflection for these methods so that we can compile everything under Java 1.6 (which doesn't have JavaFX). - */ -public -class JavaFX { - public final static boolean isLoaded; - public final static boolean isGtk3; - - // Methods are cached for performance - private static final Method dispatchMethod; - private static final Method isEventThreadMethod; - private static final Object isEventThreadObject; - - - static { - boolean isJavaFxLoaded_ = false; - boolean isJavaFxGtk3_ = false; - - try { - // this is important to use reflection, because if JavaFX is not being used, calling getToolkit() will initialize it... - java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); - m.setAccessible(true); - ClassLoader cl = JavaFX.class.getClassLoader(); - - // JavaFX Java7,8 is GTK2 only. Java9 can have it be GTK3 if -Djdk.gtk.version=3 is specified - // see http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html - isJavaFxLoaded_ = (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application")); - - if (isJavaFxLoaded_) { - // JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified - // see - // http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html - // https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm - // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. - - isJavaFxGtk3_ = OS.javaVersion >= 9 && System.getProperty("jdk.gtk.version", "2").equals("3"); - } - } catch (Throwable e) { - LoggerFactory.getLogger(JavaFX.class).debug("Error detecting if JavaFX is loaded", e); - } - - isLoaded = isJavaFxLoaded_; - isGtk3 = isJavaFxGtk3_; - - - Method _isEventThreadMethod = null; - Method _dispatchMethod = null; - Object _isEventThreadObject = null; - - if (isJavaFxLoaded_) { - try { - Class clazz = Class.forName("javafx.application.Platform"); - _dispatchMethod = clazz.getMethod("runLater", Runnable.class); - - // JAVA 7 - // javafx.application.Platform.isFxApplicationThread(); - - // JAVA 8 - // com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread(); - if (OS.javaVersion <= 7) { - clazz = Class.forName("javafx.application.Platform"); - _isEventThreadMethod = clazz.getMethod("isFxApplicationThread"); - _isEventThreadObject = null; - } else { - clazz = Class.forName("com.sun.javafx.tk.Toolkit"); - _isEventThreadMethod = clazz.getMethod("getToolkit"); - - _isEventThreadObject = _isEventThreadMethod.invoke(null); - _isEventThreadMethod = _isEventThreadObject.getClass().getMethod("isFxUserThread", (java.lang.Class[])null); - } - } catch (Throwable e) { - LoggerFactory.getLogger(JavaFX.class).error("Cannot initialize JavaFX", e); - } - } - - dispatchMethod = _dispatchMethod; - isEventThreadMethod = _isEventThreadMethod; - isEventThreadObject = _isEventThreadObject; -} - - public static - void dispatch(final Runnable runnable) { -// javafx.application.Platform.runLater(runnable); - - try { - dispatchMethod.invoke(null, runnable); - } catch (Throwable e) { - LoggerFactory.getLogger(JavaFX.class) - .error("Unable to execute JavaFX runLater(). Please create an issue with your OS and Java " + - "version so we may further investigate this issue."); - } - } - - @SuppressWarnings("ConfusingArgumentToVarargsMethod") - public static - boolean isEventThread() { - // JAVA 7 - // javafx.application.Platform.isFxApplicationThread(); - - // JAVA 8 - // com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread(); - - try { - if (OS.javaVersion <= 7) { - return (Boolean) isEventThreadMethod.invoke(null); - } else { - return (Boolean) isEventThreadMethod.invoke(isEventThreadObject); - } - } catch (Throwable e) { - LoggerFactory.getLogger(JavaFX.class) - .error("Unable to check if JavaFX is in the event thread. Please create an issue with your OS and Java " + - "version so we may further investigate this issue."); - } - - return false; - } - - public static - void onShutdown(final Runnable runnable) { - // com.sun.javafx.tk.Toolkit.getToolkit() - // .addShutdownHook(runnable); - - try { - Class clazz = Class.forName("com.sun.javafx.tk.Toolkit"); - Method method = clazz.getMethod("getToolkit"); - Object o = method.invoke(null); - Method m = o.getClass().getMethod("addShutdownHook", Runnable.class); - m.invoke(o, runnable); - } catch (Throwable e) { - LoggerFactory.getLogger(JavaFX.class) - .error("Unable to insert shutdown hook into JavaFX. Please create an issue with your OS and Java " + - "version so we may further investigate this issue."); - } - } -} diff --git a/src/dorkbox/util/Swt.java b/src/dorkbox/util/Swt.java deleted file mode 100644 index b3df91a..0000000 --- a/src/dorkbox/util/Swt.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2016 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.util; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.slf4j.LoggerFactory; - -/** - * Utility methods for SWT. Some of the methods will be overwritten via Javassist so we don't require a hard dependency on SWT. - *

- * SWT system tray types are GtkStatusIcon trays (so we don't want to use them) - */ -@SuppressWarnings("Convert2Lambda") -public -class Swt { - public final static boolean isLoaded; - public final static boolean isGtk3; - - private static final Display currentDisplay; - private static final Thread currentDisplayThread; - - private static final int version; - - static { - boolean isSwtLoaded_ = false; - boolean isSwtGtk3_ = false; - - try { - // maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be the same!! - // SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3 - isSwtLoaded_ = null != Class.forName("org.eclipse.swt.widgets.Display"); - - if (isSwtLoaded_) { - // Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to - // System.setProperty("SWT_GTK3", "0"); (or -DSWT_GTK3=1) - - // was SWT forced? - String swt_gtk3 = System.getProperty("SWT_GTK3"); - isSwtGtk3_ = swt_gtk3 != null && !swt_gtk3.equals("0"); - if (!isSwtGtk3_) { - // check a different property - String property = System.getProperty("org.eclipse.swt.internal.gtk.version"); - isSwtGtk3_ = property != null && !property.startsWith("2."); - } - } - } catch (Throwable e) { - LoggerFactory.getLogger(Swt.class).debug("Error detecting if SWT is loaded", e); - } - - - int _version = 0; - - // we MUST save the currentDisplay now, otherwise it is "null" when methods are run from the swing EDT. - // also save the SWT version - // NOTE: we cannot check if there is a default display, because JUST CHECKING will initialize a new one - Display _currentDisplay = null; - Thread _currentDisplayThread = null; - - - if (isSwtLoaded_ && SWT.isLoadable()) { - try { - _version = SWT.getVersion(); - _currentDisplay = Display.getCurrent(); - - if (_currentDisplay != null) { - _currentDisplayThread = Display.getCurrent().getThread(); - } - } catch (Throwable e) { - LoggerFactory.getLogger(Swt.class).error("Cannot initialize SWT", e); - } - } - - // if we use SWT incorrectly (ie, it's available on the classpath, but we didn't start the application) then - // '_currentDisplay' will be null. This is a reasonable way to detect if SWT is being used or not. - if (_currentDisplay == null) { - _version = 0; - isSwtLoaded_ = false; - isSwtGtk3_ = false; - } - - currentDisplay = _currentDisplay; - currentDisplayThread = _currentDisplayThread; - version = _version; - isLoaded = isSwtLoaded_; - isGtk3 = isSwtGtk3_; - } - - - private static - void onShutdown(final Display currentDisplay, final Runnable runnable) { - // currentDisplay.getShells() must only be called inside the event thread! - - org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0]; - shell.addListener(org.eclipse.swt.SWT.Close, new org.eclipse.swt.widgets.Listener() { - @Override - public - void handleEvent(final org.eclipse.swt.widgets.Event event) { - runnable.run(); - } - }); - } - - public static - void dispatch(final Runnable runnable) { - currentDisplay.syncExec(runnable); - } - - public static - boolean isEventThread() { - return Thread.currentThread() == currentDisplayThread; - } - - public static - void onShutdown(final Runnable runnable) { - // currentDisplay.getShells() must only be called inside the event thread! - if (isEventThread()) { - onShutdown(currentDisplay, runnable); - } else { - dispatch(new Runnable() { - @Override - public - void run() { - onShutdown(currentDisplay, runnable); - } - }); - } - } - - public static - int getVersion() { - return version; - } -} diff --git a/src/dorkbox/util/javaFx/JavaFX.java b/src/dorkbox/util/javaFx/JavaFX.java new file mode 100644 index 0000000..04a18c8 --- /dev/null +++ b/src/dorkbox/util/javaFx/JavaFX.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.javaFx; + + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import dorkbox.util.ClassLoaderUtil; +import dorkbox.util.OS; +import dorkbox.util.swt.Swt; + +/** + * Utility methods for JavaFX. + */ +public +class JavaFX { + public final static boolean isLoaded; + public final static boolean isGtk3; + + + static { + // There is a silly amount of redirection, simply because we have to be able to access JavaFX, but only if it's in use. + // Since this class is the place other code interacts with, we can use JavaFX stuff if necessary without loading/linking + // the JavaFX classes by accident + + // We cannot use getToolkit(), because if JavaFX is not being used, calling getToolkit() will initialize it... + // see: https://bugs.openjdk.java.net/browse/JDK-8090933 + + + boolean isJavaFxLoaded_ = ClassLoaderUtil.isClassLoaded(ClassLoader.getSystemClassLoader(), "javafx.application.Platform"); + if (!isJavaFxLoaded_) { + // check both classloaders + isJavaFxLoaded_ = ClassLoaderUtil.isClassLoaded(Thread.currentThread().getContextClassLoader(), "javafx.application.Platform"); + } + + boolean isJavaFxGtk3_ = false; + if (isJavaFxLoaded_) { + // JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified + // see + // http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html + // https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm + // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. + + + if (OS.javaVersion >= 9) { + // HILARIOUSLY enough, you can use JavaFX + SWT..... And the javaFX GTK version info SHOULD + // be based on what SWT has loaded + + // https://github.com/teamfx/openjfx-9-dev-rt/blob/master/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java + + if (Swt.isLoaded && !Swt.isGtk3) { + isJavaFxGtk3_ = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public + Boolean run() { + String version = System.getProperty("jdk.gtk.version", "2"); + return "3".equals(version) || version.startsWith("3."); + } + }); + } + } + } + + + isLoaded = isJavaFxLoaded_; + isGtk3 = isJavaFxGtk3_; + } + + public static + void dispatch(final Runnable runnable) { + JavaFxDispatch.dispatch(runnable); + } + + public static + boolean isEventThread() { + return JavaFxDispatch.isEventThread(); + } + + public static + void onShutdown(final Runnable runnable) { + JavaFxDispatch.onShutdown(runnable); + } +} diff --git a/src/dorkbox/util/javaFx/JavaFxDispatch.java b/src/dorkbox/util/javaFx/JavaFxDispatch.java new file mode 100644 index 0000000..eb37155 --- /dev/null +++ b/src/dorkbox/util/javaFx/JavaFxDispatch.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.javaFx; + + +import javafx.application.Platform; + +/** + * Utility methods for JavaFX. Redirection is used here so we don't load/link the JavaFX classes if we aren't using them + */ +class JavaFxDispatch { + static + void dispatch(final Runnable runnable) { + Platform.runLater(runnable); + } + + static + boolean isEventThread() { + return Platform.isFxApplicationThread(); + } + + static + void onShutdown(final Runnable runnable) { + com.sun.javafx.tk.Toolkit.getToolkit().addShutdownHook(runnable); + } +} diff --git a/src/dorkbox/util/jna/linux/GtkCheck.java b/src/dorkbox/util/jna/linux/GtkCheck.java index 458e04f..642b5ee 100644 --- a/src/dorkbox/util/jna/linux/GtkCheck.java +++ b/src/dorkbox/util/jna/linux/GtkCheck.java @@ -15,9 +15,9 @@ */ package dorkbox.util.jna.linux; -import dorkbox.util.JavaFX; +import dorkbox.util.javaFx.JavaFX; import dorkbox.util.SwingUtil; -import dorkbox.util.Swt; +import dorkbox.util.swt.Swt; /** * Accessor methods/logic for determining if GTK is already loaded by the Swing/JavaFX/SWT, or if GTK has been manually loaded via diff --git a/src/dorkbox/util/jna/linux/GtkEventDispatch.java b/src/dorkbox/util/jna/linux/GtkEventDispatch.java index da5f1b0..74c740f 100644 --- a/src/dorkbox/util/jna/linux/GtkEventDispatch.java +++ b/src/dorkbox/util/jna/linux/GtkEventDispatch.java @@ -26,8 +26,8 @@ import org.slf4j.LoggerFactory; import com.sun.jna.Pointer; -import dorkbox.util.JavaFX; -import dorkbox.util.Swt; +import dorkbox.util.javaFx.JavaFX; +import dorkbox.util.swt.Swt; public class GtkEventDispatch { @@ -282,12 +282,10 @@ class GtkEventDispatch { return; } - if (Swt.isLoaded) { - if (Swt.isEventThread()) { - // Run directly on the SWT event thread. If it's not on the dispatch thread, we can use raw GTK to put it there - runnable.run(); - return; - } + if (Swt.isLoaded && Swt.isEventThread()) { + // Run directly on the SWT event thread. If it's not on the dispatch thread, we will use GTK to put it there + runnable.run(); + return; } } diff --git a/src/dorkbox/util/jna/linux/GtkLoader.java b/src/dorkbox/util/jna/linux/GtkLoader.java index 1023cb8..7c85364 100644 --- a/src/dorkbox/util/jna/linux/GtkLoader.java +++ b/src/dorkbox/util/jna/linux/GtkLoader.java @@ -21,7 +21,7 @@ import com.sun.jna.Function; import com.sun.jna.NativeLibrary; import dorkbox.util.OS; -import dorkbox.util.Swt; +import dorkbox.util.swt.Swt; import dorkbox.util.jna.JnaHelper; /** diff --git a/src/dorkbox/util/jna/linux/GtkTheme.java b/src/dorkbox/util/jna/linux/GtkTheme.java index 913d604..95eeaec 100644 --- a/src/dorkbox/util/jna/linux/GtkTheme.java +++ b/src/dorkbox/util/jna/linux/GtkTheme.java @@ -37,7 +37,7 @@ import dorkbox.util.FileUtil; import dorkbox.util.MathUtil; import dorkbox.util.OS; import dorkbox.util.OSUtil; -import dorkbox.util.Swt; +import dorkbox.util.swt.Swt; import dorkbox.util.jna.linux.structs.GtkRequisition; import dorkbox.util.jna.linux.structs.GtkStyle; import dorkbox.util.jna.linux.structs.PangoRectangle; diff --git a/src/dorkbox/util/swt/Swt.java b/src/dorkbox/util/swt/Swt.java new file mode 100644 index 0000000..51500a1 --- /dev/null +++ b/src/dorkbox/util/swt/Swt.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.swt; + +import dorkbox.util.ClassLoaderUtil; + +/** + * Utility methods for SWT. + */ +@SuppressWarnings("Convert2Lambda") +public +class Swt { + public final static boolean isLoaded; + public final static boolean isGtk3; + private static final int version; + + 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. + // Since this class is the place other code interacts with, we can use SWT stuff if necessary without loading/linking + // the SWT classes by accident + + boolean isSwtLoaded_ = ClassLoaderUtil.isClassLoaded(ClassLoader.getSystemClassLoader(), "org.eclipse.swt.widgets.Display"); + if (!isSwtLoaded_) { + // check both classloaders + isSwtLoaded_ = ClassLoaderUtil.isClassLoaded(Thread.currentThread().getContextClassLoader(), "org.eclipse.swt.widgets.Display"); + } + + int _version = 0; + if (isSwtLoaded_) { + _version = SwtDispatch.getVersion(); + } + + version = _version; + isLoaded = isSwtLoaded_; + isGtk3 = isSwtLoaded_ && SwtDispatch.isGtk3(); + } + + public static + void main(String[] args) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + boolean classLo2aded = ClassLoaderUtil.isClassLoaded(contextClassLoader, "dorkbox/util/swt/Swt.java"); + boolean classLoaded = ClassLoaderUtil.isClassLoaded(contextClassLoader, "org.eclipse.swt.SWTError"); + boolean classLo3aded = ClassLoaderUtil.isClassLoaded(contextClassLoader, ":asd"); + } + + public static + int getVersion() { + return version; + } + + public static + void dispatch(final Runnable runnable) { + SwtDispatch.dispatch(runnable); + } + + public static + boolean isEventThread() { + return SwtDispatch.isEventThread(); + } + + public static + void onShutdown(final Runnable runnable) { + SwtDispatch.onShutdown(runnable); + } +} diff --git a/src/dorkbox/util/swt/SwtDispatch.java b/src/dorkbox/util/swt/SwtDispatch.java new file mode 100644 index 0000000..2e8fe4e --- /dev/null +++ b/src/dorkbox/util/swt/SwtDispatch.java @@ -0,0 +1,202 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util.swt; + +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.slf4j.LoggerFactory; + +import dorkbox.util.OS; + +/** + * Utility methods for SWT. + */ +@SuppressWarnings("Convert2Lambda") +class SwtDispatch { + private static final Display currentDisplay; + private static final Thread currentDisplayThread; + + private static final int version; + + static { + int _version = 0; + + // Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to + // System.setProperty("SWT_GTK3", "0"); (or -DSWT_GTK3=1) + + + // we MUST save the currentDisplay now, otherwise it is "null" when methods are run from the swing EDT. + // also save the SWT version + // NOTE: we cannot check if there is a default display, because JUST CHECKING will initialize a new one + Display _currentDisplay = null; + Thread _currentDisplayThread = null; + + try { + _version = SWT.getVersion(); + _currentDisplay = Display.getCurrent(); + + if (_currentDisplay != null) { + _currentDisplayThread = Display.getCurrent().getThread(); + } + } catch (Throwable e) { + LoggerFactory.getLogger(SwtDispatch.class).error("Cannot initialize SWT", e); + } + + // if we use SWT incorrectly (ie, it's available on the classpath, but we didn't start the application) then + // '_currentDisplay' will be null. This is a reasonable way to detect if SWT is being used or not. + if (_currentDisplay == null) { + _version = 0; + } + + currentDisplay = _currentDisplay; + currentDisplayThread = _currentDisplayThread; + version = _version; + } + + /** + * This is an indirect reference to SWT, so we only check if SWT is loadable if certain classes are available. + * @return true if SWT is loadable + */ + static boolean isLoadable() { + Class swtErrorClass = AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public + Class run() { + try { + return Class.forName("org.eclipse.swt.SWTError", true, ClassLoader.getSystemClassLoader()); + } catch (Exception ignored) { + } + try { + return Class.forName("org.eclipse.swt.SWTError", true, Thread.currentThread().getContextClassLoader()); + } catch (Exception ignored) { + } + return null; + } + }); + + return null != swtErrorClass && SWT.isLoadable(); + } + + /** + * This is only necessary for linux. + * + * @return true if SWT is GTK3. False if SWT is GTK2. If for some reason we DO NOT KNOW, then we return false (GTK2). + */ + static boolean isGtk3() { + if (!OS.isLinux()) { + return false; + } + + final String SWT_INTERNAL_CLASS = "org.eclipse.swt.internal.gtk.OS"; + Class osClass = AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public + Class run() { + try { + return Class.forName(SWT_INTERNAL_CLASS, true, ClassLoader.getSystemClassLoader()); + } catch (Exception ignored) { + } + + try { + return Class.forName(SWT_INTERNAL_CLASS, true, Thread.currentThread().getContextClassLoader()); + } catch (Exception ignored) { + } + + return null; + } + }); + + + if (osClass == null) { + return false; + } + + Method method = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public + Method run() { + try { + return osClass.getMethod("gtk_major_version"); + } catch (Exception e) { + return null; + } + } + }); + + if (method == null) { + return false; + } + + int version = 0; + try { + version = ((Number)method.invoke(osClass)).intValue(); + } catch (Exception ignored) { + // this method doesn't exist. + } + + return version == 3; + } + + + private static + void onShutdown(final Display currentDisplay, final Runnable runnable) { + // currentDisplay.getShells() must only be called inside the event thread! + + org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0]; + shell.addListener(SWT.Close, new org.eclipse.swt.widgets.Listener() { + @Override + public + void handleEvent(final org.eclipse.swt.widgets.Event event) { + runnable.run(); + } + }); + } + + static + void dispatch(final Runnable runnable) { + currentDisplay.syncExec(runnable); + } + + static + boolean isEventThread() { + return Thread.currentThread() == currentDisplayThread; + } + + static + void onShutdown(final Runnable runnable) { + // currentDisplay.getShells() must only be called inside the event thread! + if (isEventThread()) { + onShutdown(currentDisplay, runnable); + } else { + dispatch(new Runnable() { + @Override + public + void run() { + onShutdown(currentDisplay, runnable); + } + }); + } + } + + static + int getVersion() { + return version; + } +}