Start a new projects: ij1-patcher

This project will not depend on ImageJ2, but ij-legacy will rely on
ij1-patcher to provide an appropriately patched ImageJ 1.x.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Extract utilities from SciJava-common into ij1-patcher

To be as useful as possible, the ij1-patcher project can have only
javassist as a dependency (and that only because there is simply no other
way to patch ImageJ 1.x efficiently).

Therefore, let's provide SciJava-common' utility functions required by the
classes we want to move into ij1-patcher.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Move some classes from ij-legacy into the new ij1-patcher

This change moves a self-contained set of classes related to
runtime-patching ImageJ 1.x from ij-legacy into ij1-patcher.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
CodeHacker: add a way to commit non-IJ1 classes into the given class loader

We add three classes in the legacy patching: LegacyHooks,
EssentialLegacyHooks and HeadlessGenericDialog. These must be known to
the class loader, of course.

With LegacyHooks, we want to make it accessible both to the calling
classes and ImageJ, so it makes sense to share that interface (via the
slightly method to return it from findClasses(), violating the
convention that a class loader should only return classes whose
associated class loader is in its hierarchy).

With EssentialLegacyHooks and HeadlessGenericDialog, this is not
possible: they access the ImageJ 1.x classes themselves, and therefore
they would access the *wrong* ImageJ 1.x classes if we were to reuse
those classes from a different class loader. Therefore, we need to
commit those classes to the same class loader the ImageJ 1.x classes
are living in.

This makes only a difference if the class loader that gets passed to
CodeHacker is a different one than the one which loaded CodeHacker.
That is exactly the way we intend to handle things in the near future:
to be at least able to encapsulate ImageJ 1.x "instances" by loading
them into special-purpose class loaders.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Make sure HeadlessGenericDialog & EssentialLegacyHooks are loaded into the correct class loader

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Avoid using ImageJ 1.x classes in LegacyHooks directly

Since we want to share the LegacyHooks interface between the
LegacyClassLoader and the ClassLoader that performed the ImageJ 1.x
patching, the LegacyHooks interface cannot pass an ImagePlus
instance around: it would be incompatible (by reference, the
LegacyHooks' reference to ImagePlus would be loaded from the class
loader that loaded the LegacyHooks interface itself).

Side-step the whole issue by passing Object instances around, not
ImagePlus instances.

This also opens the door for the future when we might want to have a way
to translate between ImagePlus instances loaded into different class
loaders.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add two more methods to the legacy hooks

The whole idea of the legacy patching is to provide more extension
points than ImageJ 1.x offers on its own.

One very important extension point required by literally every
application that might need to make use of extension points is a
callback when ImageJ 1.x was initialized.

There are possible chicken-and-egg problems here, with the legacy hooks
living in ImageJ 1.x' jars/ directory: the legacy hooks can only be
loaded using ImageJ 1.x' PluginClassLoader, therefore they cannot be
installed before ImageJ 1.x was initialized.

To remedy that situation, we change the EssentialLegacyHooks (i.e. the
hooks installed initially) to interpret the system property
ij1.patcher.initializer as name of a class implementing the Runnable
interface, to be called upon initialization.

Of course, this is too late for some of the extension points (such as
setting the window title of the ImageJ 1.x main window), but that cannot
be helped: we simply have no way to access classes in the
PluginClassLoader before that class loader's class path is determined --
which happens *during* the initialization of the main window, i.e.
*after* the window title/icon are set.

It is therefore the duty of the initializer to fix that, first thing
(unless we are running in batch mode, in which case IJ.getInstance()
will be null).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Ensure initial legacy hooks are loaded using the correct class loader

The class loader used to load CodeHacker might be different from the one
into which we want to load the patched ImageJ 1.x. To ensure that the
initial legacy hooks can find the correct ImageJ, we must therefore load
the initial legacy hooks using the same class loader as ImageJ 1.x.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Install the initial legacy hooks in a class initializer

This is more robust, e.g. when writing out a headless.jar; In such a
case, there is no runtime patching, and therefore no other way to
initialize the hooks than to put that code into the class initializer
(suggesting that this is the proper way to do it anyway, separating
the concern of installing the hooks from patching the code).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Make sure that all tests in ij-legacy pre-init the legacy injector

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a default legacy initializer class

This class will be called by default when the system property
ij1.patcher.initializer is unset.

The purpose is to make sure that ImageJ2's context and all the legacy
hooks are properly installed once ImageJ 1.x spun up.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a class loader specifically for loading a patched ImageJ 1.x

This class loader will only find the ImageJ 1.x classes, plus
LegacyHooks and EssentialLegacyHooks. It will also patch the ImageJ
1.x classes appropriately and install an EssentialLegacyHooks instance.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a class to encapsulate ImageJ 1.x instances

Often it is desirable to run ImageJ 1.x functionality in an encapsulated
way, e.g. to prevent race conditions between processes that should be
independent (and therefore should have independent ImageJ 1.x contexts,
something ImageJ 1.x does not provide).

This change adds a class that creates a new special-purpose class
loader, loads a patched ImageJ 1.x into it and then offers to run macros
or plugins inside that ImageJ instance.

Naturaly, multiple such legacy environments are guaranteed to be
encapsulated from each other.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a test for calling a fresh headless ImageJ 1.x

The legacy environment will hopefully be very useful for a lot of users.
Better make sure that it works as advertised.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Move more tests from ij-legacy into ij1-patcher

All present tests that do not depend on ij-legacy should go into
ij1-patcher, of course, to guarantee that ij1-patcher's functionality
is maintained.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
ij1-patcher: aggregate test utility functions into the TestUtils class

This is needed because we want to move more tests from ij-legacy into
ij1-patcher (and that project is supposed not to depend on anything
except Javassist).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
ExtraPluginDirsTest: use LegacyEnvironment

Better use the proposed solution ourselves...

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Move ExtraPluginDirsTest into ij1-patcher

It really tests only ij1-patcher functionality.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>