Java 9 support (compile and runtime) - OSX Wrapper to dynamically use proper app handlers - remove usage of JDK internals
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/253887b2 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/253887b2 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/253887b2 Branch: refs/heads/master Commit: 253887b2daa414fd2e89add8813de7cd2863ffff Parents: 7ba05f3 Author: Nikita Timofeev <stari...@gmail.com> Authored: Mon Oct 2 16:21:31 2017 +0300 Committer: Nikita Timofeev <stari...@gmail.com> Committed: Mon Oct 2 16:21:31 2017 +0300 ---------------------------------------------------------------------- .../modeler/osx/OSXApplicationWrapper.java | 149 +++++++++++++++++++ .../modeler/osx/OSXPlatformInitializer.java | 38 ++--- .../modeler/osx/OSXQuitResponseWrapper.java | 73 +++++++++ .../components/image/FilteredIconFactory.java | 5 +- 4 files changed, 236 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/253887b2/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXApplicationWrapper.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXApplicationWrapper.java b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXApplicationWrapper.java new file mode 100644 index 0000000..f02bff5 --- /dev/null +++ b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXApplicationWrapper.java @@ -0,0 +1,149 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cayenne.modeler.osx; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.function.Consumer; + +import com.apple.eawt.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class wraps apple {@link com.apple.eawt.Application} class and dynamically + * proxying it's lifecycle handlers setup. + * <p> + * This code exists to support both Java 8 and 9, as handler interfaces where incompatibly moved + * to other package between these versions. + * <p> + * See <a href="https://bugs.openjdk.java.net/browse/JDK-8160437">JDK-8160437 issue</a> for details. + * + * @see #setAboutHandler(Runnable) run action on "About App" menu item select + * @see #setPreferencesHandler(Runnable) run action on "Preferences..." menu item select + * @see #setQuitHandler(Consumer) run action on "Quit App" menu item select + * + * @see OSXQuitResponseWrapper + * + * @since 4.1 + */ +public class OSXApplicationWrapper { + + private static final Logger logger = LoggerFactory.getLogger(OSXApplicationWrapper.class); + + // package for handler classes for Java 8 and older + private static final String JAVA8_PACKAGE = "com.apple.eawt."; + + // package for handler classes for Java 9 and newer + private static final String JAVA9_PACKAGE = "java.awt.desktop."; + + private final Application application; + + private Class<?> aboutHandlerClass; + private Method setAboutHandler; + + private Class<?> preferencesHandlerClass; + private Method setPreferencesHandler; + + private Class<?> quitHandlerClass; + private Method setQuitHandler; + + public OSXApplicationWrapper(Application application) { + this.application = application; + initMethods(); + } + + public void setPreferencesHandler(Runnable action) { + setHandler(setPreferencesHandler, preferencesHandlerClass, action); + } + + public void setAboutHandler(Runnable action) { + setHandler(setAboutHandler, aboutHandlerClass, action); + } + + public void setQuitHandler(Consumer<OSXQuitResponseWrapper> action) { + InvocationHandler handler = (proxy, method, args) -> { + // args: 0 - event, 1 - quitResponse + action.accept(new OSXQuitResponseWrapper(args[1])); + return null; + }; + Object proxy = createProxy(quitHandlerClass, handler); + try { + setQuitHandler.invoke(application, proxy); + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.warn("Unable to call " + setQuitHandler.getName(), ex); + } + } + + /** + * Find required handlers' methods and classes + */ + private void initMethods() { + aboutHandlerClass = getHandlerClass("AboutHandler"); + setAboutHandler = getMethod("setAboutHandler", aboutHandlerClass); + + preferencesHandlerClass = getHandlerClass("PreferencesHandler"); + setPreferencesHandler = getMethod("setPreferencesHandler", preferencesHandlerClass); + + quitHandlerClass = getHandlerClass("QuitHandler"); + setQuitHandler = getMethod("setQuitHandler", quitHandlerClass); + } + + private void setHandler(Method setMethod, Class<?> handlerClass, Runnable action) { + InvocationHandler handler = (proxy, method, args) -> { + action.run(); + return null; + }; + Object proxy = createProxy(handlerClass, handler); + try { + setMethod.invoke(application, proxy); + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.warn("Unable to call " + setMethod.getName(), ex); + } + } + + private Object createProxy(Class<?> handlerClass, InvocationHandler handler) { + return Proxy.newProxyInstance(OSXApplicationWrapper.class.getClassLoader(), new Class<?>[]{handlerClass}, handler); + } + + private Method getMethod(String name, Class<?> ... parameters) { + try { + return application.getClass().getMethod(name, parameters); + } catch (NoSuchMethodException ex) { + logger.warn("Unable to find method " + name, ex); + return null; + } + } + + private Class<?> getHandlerClass(String className) { + try { + return Class.forName(JAVA8_PACKAGE + className); + } catch (ClassNotFoundException ex) { + try { + return Class.forName(JAVA9_PACKAGE + className); + } catch (ClassNotFoundException ex2) { + return null; + } + } + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/253887b2/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXPlatformInitializer.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXPlatformInitializer.java b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXPlatformInitializer.java index fa47935..21c8805 100644 --- a/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXPlatformInitializer.java +++ b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXPlatformInitializer.java @@ -40,12 +40,7 @@ import org.apache.cayenne.modeler.action.ConfigurePreferencesAction; import org.apache.cayenne.modeler.action.ExitAction; import org.apache.cayenne.modeler.init.platform.PlatformInitializer; -import com.apple.eawt.AboutHandler; -import com.apple.eawt.AppEvent; import com.apple.eawt.Application; -import com.apple.eawt.PreferencesHandler; -import com.apple.eawt.QuitHandler; -import com.apple.eawt.QuitResponse; public class OSXPlatformInitializer implements PlatformInitializer { @@ -58,27 +53,18 @@ public class OSXPlatformInitializer implements PlatformInitializer { overrideUIDefaults(); // configure special Mac menu handlers - Application app = Application.getApplication(); - app.setAboutHandler(new AboutHandler() { - @Override - public void handleAbout(AppEvent.AboutEvent aboutEvent) { - actionManager.getAction(AboutAction.class).showAboutDialog(); - } - }); - - app.setPreferencesHandler(new PreferencesHandler() { - @Override - public void handlePreferences(AppEvent.PreferencesEvent preferencesEvent) { - actionManager.getAction(ConfigurePreferencesAction.class).showPreferencesDialog(); - } - }); - - app.setQuitHandler(new QuitHandler() { - @Override - public void handleQuitRequestWith(AppEvent.QuitEvent quitEvent, QuitResponse quitResponse) { - if(!actionManager.getAction(ExitAction.class).exit()) { - quitResponse.cancelQuit(); - } + OSXApplicationWrapper wrapper = new OSXApplicationWrapper(Application.getApplication()); + wrapper.setAboutHandler(() + -> actionManager.getAction(AboutAction.class).showAboutDialog()); + + wrapper.setPreferencesHandler(() + -> actionManager.getAction(ConfigurePreferencesAction.class).showPreferencesDialog()); + + wrapper.setQuitHandler(r -> { + if(!actionManager.getAction(ExitAction.class).exit()) { + r.cancelQuit(); + } else { + r.performQuit(); } }); } http://git-wip-us.apache.org/repos/asf/cayenne/blob/253887b2/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXQuitResponseWrapper.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXQuitResponseWrapper.java b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXQuitResponseWrapper.java new file mode 100644 index 0000000..f7e049b --- /dev/null +++ b/modeler/cayenne-modeler-mac-ext/src/main/java/org/apache/cayenne/modeler/osx/OSXQuitResponseWrapper.java @@ -0,0 +1,73 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.cayenne.modeler.osx; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Small wrapper around QuitResponse class that can reside in different packages: + * com.apple.eawt.QuitResponse in JDK 8 and java.awt.desktop.QuitResponse in JDK 9. + * Luckily it has same signature so we can dynamically resolve it's methods. + * + * @since 4.1 + */ +public class OSXQuitResponseWrapper { + + private static final Logger logger = LoggerFactory.getLogger(OSXQuitResponseWrapper.class); + + private Method performQuit; + + private Method cancelQuit; + + private final Object quitResponse; + + public OSXQuitResponseWrapper(Object quitResponse) { + this.quitResponse = quitResponse; + try { + performQuit = quitResponse.getClass().getMethod("performQuit"); + cancelQuit = quitResponse.getClass().getMethod("cancelQuit"); + } catch (NoSuchMethodException ex) { + logger.warn("Unable to find methods for quit response", ex); + } + } + + public void performQuit() { + safePerform(performQuit); + } + + public void cancelQuit() { + safePerform(cancelQuit); + } + + private void safePerform(Method method) { + if(method == null) { + return; + } + try { + method.invoke(quitResponse); + } catch (IllegalAccessException | InvocationTargetException ex) { + logger.warn("Unable to call " + method.getName(), ex); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/253887b2/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/swing/components/image/FilteredIconFactory.java ---------------------------------------------------------------------- diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/swing/components/image/FilteredIconFactory.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/swing/components/image/FilteredIconFactory.java index 4e92238..cb92b1c 100644 --- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/swing/components/image/FilteredIconFactory.java +++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/swing/components/image/FilteredIconFactory.java @@ -25,12 +25,11 @@ import java.awt.image.FilteredImageSource; import java.awt.image.ImageProducer; import java.awt.image.RGBImageFilter; import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.UIManager; -import sun.swing.ImageIconUIResource; - /** * @since 4.0 */ @@ -64,7 +63,7 @@ public class FilteredIconFactory { icon.paintIcon(DUMMY, img.getGraphics(), 0, 0); ImageProducer producer = new FilteredImageSource(img.getSource(), filterType.filter); Image resultImage = DUMMY.createImage(producer); - return new ImageIconUIResource(resultImage); + return new ImageIcon(resultImage); } return null; }