Fix (ij-legacy tests: make sure that a main class is found)

For some reason, Jenkins on Windows did not fail without this change.
That was just a matter of luck, though. We really need to make sure
that the name of the 'main' thread is re-instated so that AppUtils
find the correct main-class.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a simple Java agent to identify when ImageJ 1.x classes are loaded

When ImageJ 1.x' classes are loaded before the ij-legacy patching had
a chance, we have a problem. The biggest problem is that we only have
a chance to detect that situation when the ij-legacy patching takes
place, so there is no way to know which code path loaded the unpatched
ImageJ 1.x classes.

To help with that, this commit adds a Java agent that reports whenever
classes in the "ij" package (or any of its nested packages) is loaded.

We will refine this agent in subsequent commits.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: show only the relevant part of the stack trace

We do not need to show any part that is concerned with (possibly
nested) class loading, just the part in the user code that wants
to refer to ImageJ 1.x classes. So we hide anything inside the
bootstrap class loader (actually, the ext class loader, but that is
an implementation detail) and anything inside the LegacyJavaAgent
class itself.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: Add documentation to the premain() method

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: Add code to write out a minimal .jar file

When debugging ij-legacy itself, we need a minimal .jar file that
does not hide the classes we try to debug.

The newly introduced main() method does exactly that, copying the
LegacyJavaAgent .class file into a new .jar file.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add a Premain-Class: entry so that the ij-legacy agent can be started

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: Offer an API to switch the agent off

When the legacy patching is done, we should not warn any longer.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
ij-legacy: shut up the LegacyJavaAgent just before patching

The legacy java agent is supposed to warn when ImageJ 1.x classes
were used before patching, but it should no longer warn when that
patching starts (or afterwards).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add the preinit() method to DefaultLegacyService

This method is supposed to perform the ImageJ 1.x class patching
required for the LegacyService to work correctly.

Previously, we tried to patch the classes when the legacy service
is initialized, but that is too late if the ImageJ 1.x classes are
used before.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Use DefaultLegacyService's preinit() method to force IJ1 patching

We used to abuse the getInstance() method for that task, but that is
inappropriate: we do actually not want an instance here. We want the
legacy service to run the things necessary before loading ImageJ 1.x
classes so that the LegacyService can be initialized properly later,
if at all.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
CodeHacker: provide better help when patching failed

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
CodeHacker: do not hide warning on stderr

When code patching failed, we should have a proper exception, not a
warning issued on stderr and a linkage error.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: optionally overwrite the minimal agent .jar

When the main method runs with --force, it will overwrite the
specified target file.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: add a usage

When specifying -javaagent:/path/to/agent.jar=help, it will show a
description of the agent and the options offered by it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: add the 'init' option

In the mode triggered by the 'init' option, the agent will simply force
the code patching of the ImageJ 1.x classes no matter what. This is the
safest and easiest method to fix issues when the CodeHacker complains
that the ImageJ 1.x classes were loaded before they could be patched.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
CodeHacker: mention the agent option 'init'

This is by far the easiest (if not the cleanest) way to solve the
problems...

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: add the 'noop' option

This option is only there for easier testing (allowing to switch off
the agent easily in Eclipse's run configurations).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: describe the default mode in the usage

This also adds the synonym 'debug' and warns about unhandled agent
options.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: in debug mode, rewrite the class

There is one problem with the legacy Java agent: it reports all the
problematic code paths, but it does not stop there.

We cannot simply throw an exception in the ClassFileTransformer
because -- as documented in ClassFileTransformer's JavaDoc -- an
exception thrown in the transform() method is treated as if null
was returned, i.e. the next ClassFileTransformer is given a chance.

This change is an attempt to rewrite the classes with a static class
initializer that throws the exception when the class is loaded.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
LegacyJavaAgent: use Javassist instead

In an earlier attempt, we tried to write the class file ourselves.
The rationale: we would like to be able to run a minimal
ij-legacy-agent.jar to be able to debug ij-legacy itself and
therefore would like to keep dependencies to a minimum.

However, the earlier approach was too simplistic: it is not enough
to rewrite the class to have a class initializer that throws our
desired exception. If a static method such as IJ.log() is called,
Java will happily match the method signature *before* initializing
the class. So we would have to add *all* valid methods to our
rewritten class.

So let's give up on that track and use Javassist instead.

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