All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.imagej.patcher.LegacyExtensions Maven / Gradle / Ivy

Go to download

A runtime patcher to introduce extension points into the original ImageJ (1.x). This project offers extension points for use with ImageJ2 and it also offers limited support for headless operations.

There is a newer version: 1.2.7
Show newest version
/*
 * #%L
 * ImageJ software for multidimensional image processing and analysis.
 * %%
 * Copyright (C) 2009 - 2016 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
 * Institute of Molecular Cell Biology and Genetics.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package net.imagej.patcher;

import java.util.HashSet;
import java.util.Set;


/**
 * Assorted legacy patches / extension points for use in the legacy mode.
 * 
 * 

* The Fiji distribution of ImageJ accumulated patches and extensions to ImageJ * 1.x over the years. *

* *

* However, there was a lot of overlap with the ImageJ2 project, so it was * decided to focus Fiji more on the life-science specific part and move all the * parts that are of more general use into ImageJ2. That way, it is pretty * clear-cut what goes into Fiji and what goes into ImageJ2. *

* *

* This class contains the extension points (such as being able to override the * macro editor) ported from Fiji as well as the code for runtime patching * ImageJ 1.x needed both for the extension points and for more backwards * compatibility than ImageJ 1.x wanted to provide (e.g. when public methods or * classes that were used by Fiji plugins were removed all of a sudden, without * being deprecated first). *

* *

* The code in this class is only used in the legacy mode. *

* * @author Johannes Schindelin */ class LegacyExtensions { /* * Extension points */ /* * Runtime patches (using CodeHacker for patching) */ /** * Applies runtime patches to ImageJ 1.x for backwards-compatibility and extension points. * *

* These patches enable a patched ImageJ 1.x to call a different script editor or to override * the application icon. *

* *

* This method is called by {@link LegacyInjector#injectHooks(ClassLoader)}. *

* * @param hacker the {@link CodeHacker} instance */ public static void injectHooks(final CodeHacker hacker, boolean headless) { // // Below are patches to make ImageJ 1.x more backwards-compatible // // add back the (deprecated) killProcessor(), and overlay methods final String[] imagePlusMethods = { "public void killProcessor()", "{}", "public void setDisplayList(java.util.Vector list)", "getCanvas().setDisplayList(list);", "public java.util.Vector getDisplayList()", "return getCanvas().getDisplayList();", "public void setDisplayList(ij.gui.Roi roi, java.awt.Color strokeColor," + " int strokeWidth, java.awt.Color fillColor)", "setOverlay(roi, strokeColor, strokeWidth, fillColor);" }; for (int i = 0; i < imagePlusMethods.length; i++) try { hacker.insertNewMethod("ij.ImagePlus", imagePlusMethods[i], imagePlusMethods[++i]); } catch (Exception e) { /* ignore */ } // make sure that ImageJ has been initialized in batch mode hacker.insertAtTopOfMethod("ij.IJ", "public static java.lang.String runMacro(java.lang.String macro, java.lang.String arg)", "if (ij==null && ij.Menus.getCommands()==null) init();"); try { hacker.insertNewMethod("ij.CompositeImage", "public ij.ImagePlus[] splitChannels(boolean closeAfter)", "ij.ImagePlus[] result = ij.plugin.ChannelSplitter.split(this);" + "if (closeAfter) close();" + "return result;"); hacker.insertNewMethod("ij.plugin.filter.RGBStackSplitter", "public static ij.ImagePlus[] splitChannelsToArray(ij.ImagePlus imp, boolean closeAfter)", "if (!imp.isComposite()) {" + " ij.IJ.error(\"splitChannelsToArray was called on a non-composite image\");" + " return null;" + "}" + "ij.ImagePlus[] result = ij.plugin.ChannelSplitter.split(imp);" + "if (closeAfter)" + " imp.close();" + "return result;"); } catch (IllegalArgumentException e) { final Throwable cause = e.getCause(); if (cause != null && !cause.getClass().getName().endsWith("DuplicateMemberException")) { throw e; } } // handle mighty mouse (at least on old Linux, Java mistakes the horizontal wheel for a popup trigger) for (String fullClass : new String[] { "ij.gui.ImageCanvas", "ij.plugin.frame.RoiManager", "ij.text.TextPanel", "ij.gui.Toolbar" }) { hacker.handleMightyMousePressed(fullClass); } // tell IJ#runUserPlugIn to catch NoSuchMethodErrors final String runUserPlugInSig = "static java.lang.Object runUserPlugIn(java.lang.String commandName, java.lang.String className, java.lang.String arg, boolean createNewLoader)"; hacker.addCatch("ij.IJ", runUserPlugInSig, "java.lang.NoSuchMethodError", "if (ij.IJ._hooks.handleNoSuchMethodError($e))" + " throw new RuntimeException(ij.Macro.MACRO_CANCELED);" + "throw $e;"); // tell IJ#runUserPlugIn to be more careful about catching NoClassDefFoundError hacker.insertPrivateStaticField("ij.IJ", String.class, "originalClassName"); hacker.insertAtTopOfMethod("ij.IJ", runUserPlugInSig, "originalClassName = $2;"); hacker.insertAtTopOfExceptionHandlers("ij.IJ", runUserPlugInSig, "java.lang.NoClassDefFoundError", "java.lang.String realClassName = $1.getMessage();" + "int spaceParen = realClassName.indexOf(\" (\");" + "if (spaceParen > 0) realClassName = realClassName.substring(0, spaceParen);" + "if (!originalClassName.replace('.', '/').equals(realClassName)) {" + " if (realClassName.startsWith(\"javax/vecmath/\") || realClassName.startsWith(\"com/sun/j3d/\") || realClassName.startsWith(\"javax/media/j3d/\"))" + " ij.IJ.error(\"The class \" + originalClassName + \" did not find Java3D (\" + realClassName + \")\\nPlease call Plugins>3D Viewer to install\");" + " else" + " ij.IJ.handleException($1);" + " return null;" + "}"); // let the plugin class loader find stuff in $HOME/.plugins, too addExtraPlugins(hacker); // make sure that the GenericDialog is disposed in macro mode if (hacker.hasMethod("ij.gui.GenericDialog", "public void showDialog()")) { hacker.insertAtTopOfMethod("ij.gui.GenericDialog", "public void showDialog()", "if (macro) dispose();"); } // make sure NonBlockingGenericDialog does not wait in macro mode hacker.replaceCallInMethod("ij.gui.NonBlockingGenericDialog", "public void showDialog()", "java.lang.Object", "wait", "if (isShowing()) wait();"); // tell the showStatus() method to show the version() instead of empty status hacker.insertAtTopOfMethod("ij.ImageJ", "void showStatus(java.lang.String s)", "if ($1 == null || \"\".equals($1)) $1 = version();"); // make sure that the GenericDialog does not make a hidden main window visible if (!headless) { hacker.replaceCallInMethod("ij.gui.GenericDialog", "public (java.lang.String title, java.awt.Frame f)", "java.awt.Dialog", "super", "$proceed($1 != null && $1.isVisible() ? $1 : null, $2, $3);"); } // handle custom icon (e.g. for Fiji) addIconHooks(hacker); // optionally disallow batch mode from calling System.exit() hacker.insertPrivateStaticField("ij.ImageJ", Boolean.TYPE, "batchModeMayExit"); hacker.insertAtTopOfMethod("ij.ImageJ", "public static void main(java.lang.String[] args)", "batchModeMayExit = true;" + "for (int i = 0; i < $1.length; i++) {" + " if (\"-batch-no-exit\".equals($1[i])) {" + " batchModeMayExit = false;" + " $1[i] = \"-batch\";" + " }" + "}"); hacker.replaceCallInMethod("ij.ImageJ", "public static void main(java.lang.String[] args)", "java.lang.System", "exit", "if (batchModeMayExit) System.exit($1);" + "if ($1 == 0) return;" + "throw new RuntimeException(\"Exit code: \" + $1);"); // do not use the current directory as IJ home on Windows String prefsDir = System.getenv("IJ_PREFS_DIR"); if (prefsDir == null && System.getProperty("os.name").startsWith("Windows")) { prefsDir = System.getenv("user.home"); } if (prefsDir != null) { hacker.overrideFieldWrite("ij.Prefs", "public static java.lang.String getPrefsDir()", "prefsDir", "$_ = \"" + prefsDir + "\";"); } // tool names can be prefixes of other tools, watch out for that! hacker.replaceCallInMethod("ij.gui.Toolbar", "public int getToolId(java.lang.String name)", "java.lang.String", "startsWith", "$_ = $0.equals($1) || $0.startsWith($1 + \"-\") || $0.startsWith($1 + \" -\");"); // make sure Rhino gets the correct class loader final String javascript = hacker.existsClass("JavaScriptEvaluator") ? "JavaScriptEvaluator" : "ij.plugin.JavaScriptEvaluator"; hacker.insertAtTopOfMethod(javascript, "public void run()", "Thread.currentThread().setContextClassLoader(ij.IJ.getClassLoader());"); // make sure that the check for Bio-Formats is correct hacker.addToClassInitializer("ij.io.Opener", "try {" + " ij.IJ.getClassLoader().loadClass(\"loci.plugins.LociImporter\");" + " bioformats = true;" + "} catch (ClassNotFoundException e) {" + " bioformats = false;" + "}"); // make sure that symbolic links are *not* resolved (because then the parent info in the FileInfo would be wrong) hacker.replaceCallInMethod("ij.plugin.DragAndDrop", "public void openFile(java.io.File f)", "java.io.File", "getCanonicalPath", "$_ = $0.getAbsolutePath();"); // make sure no dialog is opened in headless mode hacker.insertAtTopOfMethod("ij.macro.Interpreter", "void showError(java.lang.String title, java.lang.String msg, java.lang.String[] variables)", "if (ij.IJ.getInstance() == null) {" + " java.lang.System.err.println($1 + \": \" + $2);" + " return;" + "}"); // let IJ.handleException override the macro interpreter's call()'s exception handling hacker.insertAtTopOfExceptionHandlers("ij.macro.Functions", "java.lang.String call()", "java.lang.reflect.InvocationTargetException", "ij.IJ.handleException($1);" + "return null;"); // Add back the "Convert to 8-bit Grayscale" checkbox to Import>Image Sequence if (!hacker.hasField("ij.plugin.FolderOpener", "convertToGrayscale")) { hacker.insertPrivateStaticField("ij.plugin.FolderOpener", Boolean.TYPE, "convertToGrayscale"); hacker.replaceCallInMethod("ij.plugin.FolderOpener", "public void run(java.lang.String arg)", "ij.io.Opener", "openImage", "$_ = $0.openImage($1, $2);" + "if (convertToGrayscale) {" + " final String saved = ij.Macro.getOptions();" + " ij.IJ.run($_, \"8-bit\", \"\");" + " if (saved != null && !saved.equals(\"\")) ij.Macro.setOptions(saved);" + "}"); hacker.replaceCallInMethod("ij.plugin.FolderOpener", "boolean showDialog(ij.ImagePlus imp, java.lang.String[] list)", "ij.plugin.FolderOpener$FolderOpenerDialog", "addCheckbox", "$0.addCheckbox(\"Convert to 8-bit Grayscale\", convertToGrayscale);" + "$0.addCheckbox($1, $2);", 1); hacker.replaceCallInMethod("ij.plugin.FolderOpener", "boolean showDialog(ij.ImagePlus imp, java.lang.String[] list)", "ij.plugin.FolderOpener$FolderOpenerDialog", "getNextBoolean", "convertToGrayscale = $0.getNextBoolean();" + "$_ = $0.getNextBoolean();" + "if (convertToGrayscale && $_) {" + " ij.IJ.error(\"Cannot convert to grayscale and RGB at the same time.\");" + " return false;" + "}", 1); } // handle HTTPS in addition to HTTP hacker.handleHTTPS("ij.macro.Functions", "java.lang.String exec()"); hacker.handleHTTPS("ij.plugin.DragAndDrop", "public void drop(java.awt.dnd.DropTargetDropEvent dtde)"); hacker.handleHTTPS("ij.plugin.ListVirtualStack", "public void run(java.lang.String arg)"); hacker.handleHTTPS("ij.plugin.ListVirtualStack", "java.lang.String[] open(java.lang.String path)"); addEditorExtensionPoints(hacker); overrideAppVersion(hacker); insertAppNameHooks(hacker); insertRefreshMenusHook(hacker); overrideStartupMacrosForFiji(hacker); handleMacAdapter(hacker); handleMenuCallbacks(hacker, headless); installOpenInterceptor(hacker); handleMacroGetOptions(hacker); interceptCloseAllWindows(hacker); interceptImageWindowClose(hacker); interceptDisposal(hacker); } // -- methods to install additional hooks into ImageJ 1.x -- /** * Install a hook to optionally run a Runnable at the end of Help>Refresh Menus. * *

* See {@link LegacyHooks#runAfterRefreshMenus()}. *

* * @param hacker the {@link CodeHacker} to use for patching */ private static void insertRefreshMenusHook(CodeHacker hacker) { hacker.insertAtBottomOfMethod("ij.Menus", "public static void updateImageJMenus()", "ij.IJ._hooks.runAfterRefreshMenus();"); } private static void addEditorExtensionPoints(final CodeHacker hacker) { hacker.insertAtTopOfMethod("ij.io.Opener", "public void open(java.lang.String path)", "if (isText($1) && ij.IJ._hooks.openInEditor($1)) return;"); hacker.dontReturnOnNull("ij.plugin.frame.Recorder", "void createMacro()"); hacker.replaceCallInMethod("ij.plugin.frame.Recorder", "void createMacro()", "ij.IJ", "runPlugIn", "$_ = null;"); hacker.replaceCallInMethod("ij.plugin.frame.Recorder", "void createMacro()", "ij.plugin.frame.Editor", "createMacro", "if ($1.endsWith(\".txt\")) {" + " $1 = $1.substring($1.length() - 3) + \"ijm\";" + "}" + "if (!ij.IJ._hooks.createInEditor($1, $2)) {" + " ((ij.plugin.frame.Editor)ij.IJ.runPlugIn(\"ij.plugin.frame.Editor\", \"\")).createMacro($1, $2);" + "}"); hacker.insertPublicStaticField("ij.plugin.frame.Recorder", String.class, "nameForEditor", null); hacker.insertAtTopOfMethod("ij.plugin.frame.Recorder", "void createPlugin(java.lang.String text, java.lang.String name)", "this.nameForEditor = $2;"); hacker.replaceCallInMethod("ij.plugin.frame.Recorder", "void createPlugin(java.lang.String text, java.lang.String name)", "ij.IJ", "runPlugIn", "$_ = null;" + "new ij.plugin.NewPlugin().createPlugin(this.nameForEditor, ij.plugin.NewPlugin.PLUGIN, $2);" + "return;"); hacker.replaceCallInMethod("ij.plugin.NewPlugin", "public void createMacro(java.lang.String name)", "ij.plugin.frame.Editor", "", "$_ = null;"); hacker.replaceCallInMethod("ij.plugin.NewPlugin", "public void createMacro(java.lang.String name)", "ij.plugin.frame.Editor", "create", "if ($1.endsWith(\".txt\")) {" + " $1 = $1.substring(0, $1.length() - 3) + \"ijm\";" + "}" + "if ($1.endsWith(\".ijm\") && ij.IJ._hooks.createInEditor($1, $2)) return;" + "int options = (monospaced ? ij.plugin.frame.Editor.MONOSPACED : 0)" + " | (menuBar ? ij.plugin.frame.Editor.MENU_BAR : 0);" + "new ij.plugin.frame.Editor(rows, columns, 0, options).create($1, $2);"); hacker.dontReturnOnNull("ij.plugin.NewPlugin", "public void createPlugin(java.lang.String name, int type, java.lang.String methods)"); hacker.replaceCallInMethod("ij.plugin.NewPlugin", "public void createPlugin(java.lang.String name, int type, java.lang.String methods)", "ij.IJ", "runPlugIn", "$_ = null;"); hacker.replaceCallInMethod("ij.plugin.NewPlugin", "public void createPlugin(java.lang.String name, int type, java.lang.String methods)", "ij.plugin.frame.Editor", "create", "if (!ij.IJ._hooks.createInEditor($1, $2)) {" + " ((ij.plugin.frame.Editor)ij.IJ.runPlugIn(\"ij.plugin.frame.Editor\", \"\")).create($1, $2);" + "}"); hacker.replaceCallInMethod("ij.plugin.Compiler", "void edit()", "ij.IJ", "runPlugIn", "if (ij.IJ._hooks.openInEditor(dir + name)) $_ = null;" + "else $_ = $proceed($$);"); // hacker.hasMethod check for private void showCode(String title, String code) then replace in there otherwise keep the existing code String methodSig; if (hacker.hasMethod("ij.gui.Toolbar", "private void showCode(java.lang.String title, java.lang.String code)")) { methodSig = "private void showCode(java.lang.String title, java.lang.String code)"; } else { methodSig = "public void itemStateChanged(java.awt.event.ItemEvent e)"; } hacker.replaceCallInMethod("ij.gui.Toolbar", methodSig, "ij.plugin.frame.Editor", "create", "if ($1.endsWith(\".txt\")) $1 = $1.substring(0, $1.length() - 4);" + "$1 += \".ijm\";" + "if (!ij.IJ._hooks.createInEditor($1, $2)) $_ = $proceed($$);"); hacker.replaceCallInMethod("ij.plugin.CommandFinder", "private boolean showMacro(java.lang.String cmd)", "ij.plugin.frame.Editor", "create", "if ($1.endsWith(\".txt\")) $1 = $1.substring(0, $1.length() - 4);" + "$1 += \".ijm\";" + "if (!ij.IJ._hooks.createInEditor($1, $2)) $_ = $proceed($$);"); } private static void overrideAppVersion(final CodeHacker hacker) { hacker.insertAtBottomOfMethod("ij.ImageJ", "public java.lang.String version()", "String version = ij.IJ._hooks.getAppVersion();" + "if (version != null) {" + " $_ = $_.replace(VERSION, version);" + "}"); // Of course there is not a *single* way to obtain the version. // That would have been too easy, wouldn't it? hacker.insertAtTopOfMethod("ij.IJ", "public static String getVersion()", "String version = ij.IJ._hooks.getAppVersion();" + "if (version != null) return version;"); } /** * Inserts hooks to replace the application name. */ private static void insertAppNameHooks(final CodeHacker hacker) { final String appName = "ij.IJ._hooks.getAppName()"; final String replace = ".replace(\"ImageJ\", " + appName + ")"; hacker.insertAtTopOfMethod("ij.IJ", "public void error(java.lang.String title, java.lang.String msg)", "if ($1 == null || $1.equals(\"ImageJ\")) $1 = " + appName + ";"); hacker.insertAtBottomOfMethod("ij.ImageJ", "public java.lang.String version()", "$_ = $_" + replace + ";"); hacker.replaceAppNameInCall("ij.ImageJ", "public (java.applet.Applet applet, int mode)", "super", 1, appName); hacker.replaceAppNameInNew("ij.ImageJ", "public void run()", "ij.gui.GenericDialog", 1, appName); hacker.replaceAppNameInCall("ij.ImageJ", "public void run()", "addMessage", 1, appName); if (hacker.hasMethod("ij.plugin.CommandFinder", "public void export()")) { hacker.replaceAppNameInNew("ij.plugin.CommandFinder", "public void export()", "ij.text.TextWindow", 1, appName); } hacker.replaceAppNameInCall("ij.plugin.Hotkeys", "public void removeHotkey()", "addMessage", 1, appName); hacker.replaceAppNameInCall("ij.plugin.Hotkeys", "public void removeHotkey()", "showStatus", 1, appName); if (hacker.existsClass("ij.plugin.AppearanceOptions")) { hacker.replaceAppNameInCall("ij.plugin.AppearanceOptions", "void showDialog()", "showMessage", 2, appName); } else { hacker.replaceAppNameInCall("ij.plugin.Options", "public void appearance()", "showMessage", 2, appName); } if (hacker.hasMethod("ij.gui.YesNoCancelDialog", "public (java.awt.Frame parent, java.lang.String title, java.lang.String msg, java.lang.String yesLabel, java.lang.String noLabel)")) { hacker.replaceAppNameInCall("ij.gui.YesNoCancelDialog", "public (java.awt.Frame parent, java.lang.String title, java.lang.String msg, java.lang.String yesLabel, java.lang.String noLabel)", "super", 2, appName); } else { hacker.replaceAppNameInCall("ij.gui.YesNoCancelDialog", "public (java.awt.Frame parent, java.lang.String title, java.lang.String msg)", "super", 2, appName); } hacker.replaceAppNameInCall("ij.gui.Toolbar", "private void showMessage(int toolId)", "showStatus", 1, appName); } private static void addIconHooks(final CodeHacker hacker) { final String icon = "ij.IJ._hooks.getIconURL()"; hacker.replaceCallInMethod("ij.ImageJ", "void setIcon()", "java.lang.Class", "getResource", "java.net.URL _iconURL = " + icon + ";\n" + "if (_iconURL == null) $_ = $0.getResource($1);" + "else $_ = _iconURL;"); hacker.insertAtTopOfMethod("ij.ImageJ", "public (java.applet.Applet applet, int mode)", "if ($2 != 2 /* ij.ImageJ.NO_SHOW */) setIcon();"); hacker.insertAtTopOfMethod("ij.WindowManager", "public void addWindow(java.awt.Frame window)", "java.net.URL _iconURL = " + icon + ";\n" + "if (_iconURL != null && $1 != null) {" + " java.awt.Image img = $1.createImage((java.awt.image.ImageProducer)_iconURL.getContent());" + " if (img != null) {" + " $1.setIconImage(img);" + " }" + "}"); } /** * Makes sure that the legacy plugin class loader finds stuff in * {@code $HOME/.plugins/}. */ private static void addExtraPlugins(final CodeHacker hacker) { for (final String methodName : new String[] { "addJAR", "addJar" }) { if (hacker.hasMethod("ij.io.PluginClassLoader", "private void " + methodName + "(java.io.File file)")) { hacker.insertAtTopOfMethod("ij.io.PluginClassLoader", "void init(java.lang.String path)", extraPluginJarsHandler("if (file.isDirectory()) addDirectory(file);" + "else " + methodName + "(file);")); } } // avoid parsing ij.jar for plugins hacker.replaceCallInMethod("ij.Menus", "InputStream autoGenerateConfigFile(java.lang.String jar)", "java.util.zip.ZipEntry", "getName", "$_ = $proceed($$);" + "if (\"IJ_Props.txt\".equals($_)) return null;"); // make sure that extra directories added to the plugin class path work, too hacker.insertAtTopOfMethod("ij.Menus", "InputStream getConfigurationFile(java.lang.String jar)", "java.io.File isDir = new java.io.File($1);" + "if (!isDir.exists()) return null;" + "if (isDir.isDirectory()) {" + " java.io.File config = new java.io.File(isDir, \"plugins.config\");" + " if (config.exists()) return new java.io.FileInputStream(config);" + " return ij.IJ._hooks.autoGenerateConfigFile(isDir);" + "}"); // fix overzealous assumption that all plugins are in plugins.dir hacker.insertPrivateStaticField("ij.Menus", Set.class, "_extraJars"); hacker.insertAtTopOfMethod("ij.Menus", "java.lang.String getSubmenuName(java.lang.String jarPath)", "if (_extraJars.contains($1)) return null;"); // make sure that .jar files are iterated in alphabetical order hacker.replaceCallInMethod("ij.Menus", "public static synchronized java.lang.String[] getPlugins()", "java.io.File", "list", "$_ = $proceed($$);" + "if ($_ != null) java.util.Arrays.sort($_);"); // add the extra .jar files to the list of plugin .jar files to be processed. hacker.insertAtBottomOfMethod("ij.Menus", "public static synchronized java.lang.String[] getPlugins()", "if (_extraJars == null) _extraJars = new java.util.HashSet();" + extraPluginJarsHandler("if (jarFiles == null) jarFiles = new java.util.Vector();" + "jarFiles.addElement(file.getAbsolutePath());" + "_extraJars.add(file.getAbsolutePath());")); // exclude -sources.jar entries generated by Maven. hacker.insertAtBottomOfMethod("ij.Menus", "public static synchronized java.lang.String[] getPlugins()", "if (jarFiles != null) {" + " for (int i = jarFiles.size() - 1; i >= 0; i--) {" + " String entry = (String) jarFiles.elementAt(i);" + " if (entry.endsWith(\"-sources.jar\")) {" + " jarFiles.remove(i);" + " }" + " }" + "}"); // force IJ.getClassLoader() to instantiate a PluginClassLoader hacker.replaceCallInMethod( "ij.IJ", "public static ClassLoader getClassLoader()", "java.lang.System", "getProperty", "$_ = System.getProperty($1);\n" + "if ($_ == null && $1.equals(\"plugins.dir\")) $_ = \"/non-existant/\";"); } private static String extraPluginJarsHandler(final String code) { return "for (java.util.Iterator iter = ij.IJ._hooks.handleExtraPluginJars().iterator();\n" + "iter.hasNext(); ) {\n" + "\tjava.io.File file = (java.io.File)iter.next();\n" + code + "\n" + "}\n"; } private static void overrideStartupMacrosForFiji(CodeHacker hacker) { hacker.replaceCallInMethod("ij.Menus", "void installStartupMacroSet()", "java.io.File", "", "if ($1.endsWith(\"StartupMacros.txt\")) {" + " java.lang.String fijiPath = $1.substring(0, $1.length() - 3) + \"fiji.ijm\";" + " java.io.File fijiFile = new java.io.File(fijiPath);" + " $_ = fijiFile.exists() ? fijiFile : new java.io.File($1);" + "} else $_ = new java.io.File($1);"); if (hacker.hasMethod("ij.plugin.MacroInstaller", "public void installStartupMacros(java.lang.String path)")) { hacker.replaceCallInMethod("ij.plugin.MacroInstaller", "public void installStartupMacros(java.lang.String path)", "ij.plugin.MacroInstaller", "installFile", "if ($1.endsWith(\"StartupMacros.txt\")) {" + " java.lang.String fijiPath = $1.substring(0, $1.length() - 3) + \"fiji.ijm\";" + " java.io.File fijiFile = new java.io.File(fijiPath);" + " $0.installFile(fijiFile.exists() ? fijiFile.getPath() : $1);" + "} else $0.installFile($1);"); } else { hacker.replaceCallInMethod("ij.Menus", "void installStartupMacroSet()", "ij.plugin.MacroInstaller", "installFile", "if ($1.endsWith(\"StartupMacros.txt\")) {" + " java.lang.String fijiPath = $1.substring(0, $1.length() - 3) + \"fiji.ijm\";" + " java.io.File fijiFile = new java.io.File(fijiPath);" + " $0.installFile(fijiFile.exists() ? fijiFile.getPath() : $1);" + "} else $0.installFile($1);"); } } private static void handleMacAdapter(final CodeHacker hacker) { // Without the ApplicationListener, MacAdapter cannot load, and hence CodeHacker would fail // to load it if we patched the class. if (!hacker.existsClass("com.apple.eawt.ApplicationListener")) return; hacker.insertAtTopOfMethod("MacAdapter", "public void run(java.lang.String arg)", "return;"); } private static void handleMenuCallbacks(final CodeHacker hacker, boolean headless) { hacker.insertAtTopOfMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.IJ._hooks.addMenuItem(null, null);"); hacker.insertPrivateStaticField("ij.Menus", String.class, "_currentMenuPath"); // so that addSubMenu() has the correct menu path -- even in headless mode hacker.insertAtTopOfMethod("ij.Menus", "private static java.awt.Menu getMenu(java.lang.String menuName, boolean readFromProps)", "_currentMenuPath = $1;"); // so that addPlugInItem() has the correct menu path -- even in headless mode hacker.insertAtBottomOfMethod("ij.Menus", "private static java.awt.Menu getMenu(java.lang.String menuName, boolean readFromProps)", "_currentMenuPath = $1;"); hacker.insertAtTopOfMethod("ij.Menus", "static java.awt.Menu addSubMenu(java.awt.Menu menu, java.lang.String name)", "_currentMenuPath += \">\" + $2.replace('_', ' ');"); hacker.replaceCallInMethod("ij.Menus", "void addPluginsMenu()", "ij.Menus", "addPluginItem", "_currentMenuPath = \"Plugins\";" + "$_ = $proceed($$);"); hacker.replaceCallInMethod("ij.Menus", "void addPluginsMenu()", "ij.Menus", "addSubMenu", "_currentMenuPath = \"Plugins\";" + "$_ = $proceed($$);"); hacker.replaceCallInMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.Menus", "addPlugInItem", "if (\"Quit\".equals($2) || \"Open...\".equals($2) || \"Close\".equals($2) || \"Revert\".equals($2))" + " _currentMenuPath = \"File\";" + "else if(\"Show Info...\".equals($2) || \"Crop\".equals($2))" + " _currentMenuPath = \"Image\";" + "else if (\"Image Calculator...\".equals($2))" + " _currentMenuPath = \"Process\";" + "else if (\"About ImageJ...\".equals($2) || \"Update ImageJ...\".equals($2))" + " _currentMenuPath = \"Help\";" + "else if (\"Open as Panel\".equals($2))" + " _currentMenuPath = \"Help>Examples\";" + "else if ($3.startsWith(\"ij.plugin.SimpleCommands(\\\"showdir\"))" + " _currentMenuPath = \"File>Show Folder\";" + "$_ = $proceed($$);"); // Wow. There are so many different ways ImageJ 1.x adds menu entries. See e.g. "Repeat Command". hacker.replaceCallInMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.Menus", "addItem", "ij.IJ._hooks.addMenuItem(_currentMenuPath + \">\" + $2, null);" + "$_ = $proceed($$);"); hacker.insertPrivateStaticField("ij.Menus", HashSet.class, "_separators"); hacker.insertAtTopOfMethod("ij.Menus", "void installJarPlugin(java.lang.String jar, java.lang.String s)", " if (_separators == null) _separators = new java.util.HashSet();" + "_currentMenuPath = \"Plugins\";"); hacker.replaceCallInMethod("ij.Menus", "void installJarPlugin(java.lang.String jar, java.lang.String s)", "java.lang.String", "substring", "$_ = $proceed($$);" + "ij.IJ._hooks.addMenuItem(_currentMenuPath, $_);", 4); hacker.insertAtTopOfMethod("ij.Menus", "void addPlugInItem(java.awt.Menu menu, java.lang.String label, java.lang.String className, int shortcut, boolean shift)", "ij.IJ._hooks.addMenuItem(_currentMenuPath + \">\" + $2, $3);"); hacker.insertAtTopOfMethod("ij.Menus", "static void addPluginItem(java.awt.Menu submenu, java.lang.String s)", "int comma = $2.lastIndexOf(',');" + "if (comma > 0) {" + " java.lang.String label = $2.substring(1, comma - 1);" + " if (label.endsWith(\"]\")) {" + " int open = label.indexOf(\"[\");" + " if (open > 0 && label.substring(open + 1, comma - 3).matches(\"[A-Za-z0-9]\"))" + " label = label.substring(0, open);" + " }" + " while (comma + 2 < $2.length() && $2.charAt(comma + 1) == ' ')" + " comma++;" + " if (mbar == null && _separators != null) {" + " if (!_separators.contains(_currentMenuPath)) {" + " _separators.add(_currentMenuPath);" + " ij.IJ._hooks.addMenuItem(_currentMenuPath + \">-\", null);" + " }" + " }" + " ij.IJ._hooks.addMenuItem(_currentMenuPath + \">\" + label," + " $2.substring(comma + 1));" + "}"); hacker.insertAtTopOfMethod("ij.Menus", "java.awt.CheckboxMenuItem addCheckboxItem(java.awt.Menu menu, java.lang.String label, java.lang.String className)", "ij.IJ._hooks.addMenuItem(_currentMenuPath + \">\" + $2, $3);"); // handle separators (we cannot simply look for java.awt.Menu#addSeparator // because we might be running in headless mode) hacker.replaceCallInMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.Menus", "addPlugInItem", "if (\"Cache Sample Images \".equals($2))" + " ij.IJ._hooks.addMenuItem(\"File>Open Samples>-\", null);" + "else if (\"Close\".equals($2) || \"Page Setup...\".equals($2) || \"Cut\".equals($2) ||" + " \"Clear\".equals($2) || \"Crop\".equals($2) || \"Set Scale...\".equals($2) ||" + " \"Dev. Resources...\".equals($2) || \"Update ImageJ...\".equals($2) ||" + " \"Quit\".equals($2)) {" + " int separator = _currentMenuPath.indexOf('>');" + " if (separator < 0) separator = _currentMenuPath.length();" + " ij.IJ._hooks.addMenuItem(_currentMenuPath.substring(0, separator) + \">-\", null);" + "}" + "$_ = $proceed($$);" + "if (\"Tile\".equals($2))" + " ij.IJ._hooks.addMenuItem(\"Window>-\", null);"); hacker.replaceCallInMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.Menus", "addCheckboxItem", "if (\"RGB Stack\".equals($2))" + " ij.IJ._hooks.addMenuItem(_currentMenuPath + \">-\", null);" + "$_ = $proceed($$);"); hacker.replaceCallInMethod("ij.Menus", "java.lang.String addMenuBar()", "ij.Menus", "getMenu", "if (\"Edit>Selection\".equals($1) || \"Image>Adjust\".equals($1) || \"Image>Lookup Tables\".equals($1) ||" + " \"Process>Batch\".equals($1) || \"Help>About Plugins\".equals($1)) {" + " int separator = $1.indexOf('>');" + " ij.IJ._hooks.addMenuItem($1.substring(0, separator) + \">-\", null);" + "}" + "$_ = $proceed($$);"); hacker.replaceCallInMethod("ij.Menus", "static java.awt.Menu addSubMenu(java.awt.Menu menu, java.lang.String name)", "java.lang.String", "equals", "$_ = $proceed($$);" + "if ($_ && \"-\".equals($1))" + " ij.IJ._hooks.addMenuItem(_currentMenuPath + \">-\", null);"); hacker.replaceCallInMethod("ij.Menus", "static void addLuts(java.awt.Menu submenu)", "ij.IJ", "isLinux", "ij.IJ._hooks.addMenuItem(_currentMenuPath + \">-\", null);" + "$_ = $proceed($$);"); hacker.replaceCallInMethod("ij.Menus", "void addPluginsMenu()", "ij.Prefs", "getString", "$_ = $proceed($$);" + "if ($_ != null && $_.startsWith(\"-\"))" + " ij.IJ._hooks.addMenuItem(\"Plugins>-\", null);"); hacker.insertAtTopOfMethod("ij.Menus", "static void addSeparator(java.awt.Menu menu)", "ij.IJ._hooks.addMenuItem(_currentMenuPath + \">-\", null);"); hacker.replaceCallInMethod("ij.Menus", "void installPlugins()", "ij.Prefs", "getString", "$_ = $proceed($$);" + "if ($_ != null && $_.length() > 0) {" + " String className = $_.substring($_.lastIndexOf(',') + 1);" + " if (!className.startsWith(\"ij.\")) _currentMenuPath = null;" + " else {" + " char c = $_.charAt(0);" + " if (c == IMPORT_MENU) _currentMenuPath = \"File>Import\";" + " else if (c == SAVE_AS_MENU) _currentMenuPath = \"File>Save As\";" + " else if (c == SHORTCUTS_MENU) _currentMenuPath = \"Plugins>Shortcuts\";" + " else if (c == ABOUT_MENU) _currentMenuPath = \"Help>About Plugins\";" + " else if (c == FILTERS_MENU) _currentMenuPath = \"Process>Filters\";" + " else if (c == TOOLS_MENU) _currentMenuPath = \"Analyze>Tools\";" + " else if (c == UTILITIES_MENU) _currentMenuPath = \"Plugins>Utilities\";" + " else _currentMenuPath = \"Plugins\";" + " }" + "}"); if (headless) { hacker.replaceCallInMethod("ij.Menus", "void installPlugins()", "java.lang.String", "substring", "$_ = $proceed($$);" + "if (_currentMenuPath != null) addPluginItem((java.awt.Menu) null, $_);", 2); } } private static void installOpenInterceptor(CodeHacker hacker) { // Intercept ij.IJ open methods // If the open method is intercepted, the hooks.interceptOpen method needs // to perform any necessary display operations hacker.insertAtTopOfMethod("ij.IJ", "public static void open(java.lang.String path)", "Object result = ij.IJ._hooks.interceptFileOpen($1);" + "if (result != null) {" + "if (result instanceof java.lang.String) path = (java.lang.String)result;" + "else return;" + "}"); // If openImage is intercepted, we return the opened ImagePlus without displaying it hacker.insertAtTopOfMethod("ij.IJ", "public static ij.ImagePlus openImage(java.lang.String path)", "Object result = ij.IJ._hooks.interceptOpenImage($1, -1);" + "if (result != null) {" + "if (result instanceof ij.ImagePlus) return (ij.ImagePlus)result;" + "else if (result instanceof java.lang.String) path = (java.lang.String)result;" + "else return null; " + "}"); hacker.insertAtTopOfMethod("ij.IJ", "public static ij.ImagePlus openImage(java.lang.String path, int sliceIndex)", "Object result = ij.IJ._hooks.interceptOpenImage($1, $2);" + "if (result != null) {" + "if (result instanceof ij.ImagePlus) return (ij.ImagePlus)result;" + "else if (result instanceof java.lang.String) path = (java.lang.String)result;" + "else return null; " + "}"); hacker.replaceCallInMethod("ij.plugin.FolderOpener", "public void run(java.lang.String arg)", "ij.io.Opener", "openImage", "Object result = ij.IJ._hooks.interceptOpenImage($1 + $2, -1);" + "if (result != null && result instanceof ij.ImagePlus) {" + " $_ = (ij.ImagePlus)result;" + "} else {" + " $_ = $proceed($$);" + "}"); // Intercept File > Open Recent hacker.replaceCallInMethod("ij.RecentOpener", "public void run()", "ij.io.Opener", "open", "Object result = ij.IJ._hooks.interceptOpenRecent(path);" + "if (result == null) o.open(path);" + "else if (! (result instanceof ij.ImagePlus)) return;"); // Intercept DragAndDrop openings hacker.insertAtTopOfMethod("ij.plugin.DragAndDrop", "public void openFile(java.io.File f)", "Object result = ij.IJ._hooks.interceptDragAndDropFile($1);" + "if (result != null && !(result instanceof java.lang.String)) {" + " return;" + "}"); // Make sure that .lut files are not intercepted hacker.replaceCallInMethod("ij.Executer", "public static boolean loadLut(java.lang.String name)", "ij.IJ", "open", "ij.ImagePlus imp = (ij.ImagePlus) ij.IJ.runPlugIn(\"ij.plugin.LutLoader\", $1);" + "if (imp != null && imp.getWidth() > 0) {" + " imp.show();" + "}"); } private static void handleMacroGetOptions(CodeHacker hacker) { // remove that pesky, pesky Run$_ restriction hacker.replaceCallInMethod("ij.Macro", "public static java.lang.String getOptions()", "java.lang.String", "startsWith", "$_ = \"Run$_\".equals($1) ? true : $proceed($$);"); // look at more threads than just the current one, if the legacy hooks have some for us hacker.replaceCallInMethod("ij.Macro", "public static java.lang.String getOptions()", "java.util.Hashtable", "get", "$_ = $proceed($$);" + "if ($_ == null) {" + " java.lang.Iterable ancestors = ij.IJ._hooks.getThreadAncestors();" + " if (ancestors != null) {" + " for (java.util.Iterator iter = ancestors.iterator(); $_ == null && iter.hasNext(); ) {" + " $_ = $proceed(iter.next());" + " }" + " }" + "}"); } private static void interceptCloseAllWindows(final CodeHacker hacker) { hacker.insertAtBottomOfMethod("ij.WindowManager", "public synchronized static boolean closeAllWindows()", "if ($_ == true) {" + " $_ = ij.IJ._hooks.interceptCloseAllWindows();" + "}"); } private static void interceptImageWindowClose(final CodeHacker hacker) { // Ensure that even if an ImageWindow is closed during quitting, it is // still disposed. hacker.replaceCallInMethod("ij.gui.ImageWindow", "public boolean close()", "ij.WindowManager", "removeWindow", "ij.IJ._hooks.interceptImageWindowClose(this);" + "ij.WindowManager.removeWindow((java.awt.Frame)this);"); } private static void interceptDisposal(final CodeHacker hacker) { hacker.replaceCallInMethod("ij.ImageJ", "public void run()", "ij.IJ", "cleanup", "if (!ij.IJ._hooks.disposing()) {" + " quitting = false;" + " return;" + "}" + "$_ = $proceed($$);"); } // -- methods to configure LegacyEnvironment instances -- static void noPluginClassLoader(final CodeHacker hacker) { hacker.insertPrivateStaticField("ij.IJ", ClassLoader.class, "_classLoader"); final String initClassLoader = "_classLoader = Thread.currentThread().getContextClassLoader();"; hacker.insertAtTopOfMethod("ij.IJ", "static void init()", initClassLoader); hacker.insertAtTopOfMethod("ij.IJ", "static void init(ij.ImageJ imagej, java.applet.Applet theApplet)", initClassLoader); // Make sure that bare .class files in plugins/ and subdirectories are seen by ImageJ 1.x. // This needs to be done *after* Menus made sure that IJ.getDirectory("plugins") returns non-null final String supportBarePlugins = "java.lang.ClassLoader loader = " + LegacyInjector.ESSENTIAL_LEGACY_HOOKS_CLASS + " .missingSubdirs(_classLoader, true);" + "if (loader != null) {" + " Thread.currentThread().setContextClassLoader(loader);" + " _classLoader = loader;" + "}"; hacker.insertAtBottomOfMethod("ij.IJ", "static void init()", supportBarePlugins); hacker.insertAtBottomOfMethod("ij.IJ", "static void init(ij.ImageJ imagej, java.applet.Applet theApplet)", supportBarePlugins); hacker.insertAtTopOfMethod("ij.IJ", "public static ClassLoader getClassLoader()", "if (_classLoader == null) {" + initClassLoader + supportBarePlugins + "}" + "return _classLoader;"); disableRefreshMenus(hacker); // make sure that IJ#runUserPlugIn can execute package-less plugins in subdirectories of $IJ/plugin/ final String runUserPlugInSig = "static java.lang.Object runUserPlugIn(java.lang.String commandName, java.lang.String className, java.lang.String arg, boolean createNewLoader)"; hacker.insertAtTopOfExceptionHandlers("ij.IJ", runUserPlugInSig, "java.lang.NoClassDefFoundError", "java.lang.ClassLoader loader = " + LegacyInjector.ESSENTIAL_LEGACY_HOOKS_CLASS + " .missingSubdirs(getClassLoader(), false);" + "if (loader != null) _classLoader = loader;"); hacker.replaceCallInMethod("ij.IJ", runUserPlugInSig, "java.lang.ClassLoader", "loadClass", "try {" + " $_ = $proceed($$);" + "} catch (java.lang.ClassNotFoundException e) {" + " java.lang.ClassLoader loader = " + LegacyInjector.ESSENTIAL_LEGACY_HOOKS_CLASS + " .missingSubdirs(getClassLoader(), true);" + " if (loader == null) {" + " throw e;" + " } else {" + " _classLoader = loader;" + " $_ = loader.loadClass($1);" + " }" + "}"); } static void disableRefreshMenus(final CodeHacker hacker) { hacker.insertAtTopOfMethod("ij.IJ", "static void setClassLoader(java.lang.ClassLoader loader)", "ij.IJ.showMessage(\"NOTICE: Please restart ImageJ to complete plugin or macro installation, add " + "new commands to the menus, etc...\");" + "return;"); } static void suppressIJ1ScriptDiscovery(CodeHacker hacker) { hacker.insertAtTopOfMethod("ij.Menus", "private static boolean validMacroName(java.lang.String name, boolean hasUnderscore)", "return false;"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy