-
Notifications
You must be signed in to change notification settings - Fork 118
Decompiler API
The simplest way to integrate with the Procyon Decompiler is through the Decompiler
utility class. Unless otherwise noted, all classes described on this page may be found in the com.strobel.decompiler
package of the procyon-compilertools
library. More elaborate hosting scenarios are not documented at this time.
The Decompiler
class provides two decompile()
methods:
:::java
public static void decompile(
final String internalName,
final ITextOutput output
);
public static void decompile(
final String internalName,
final ITextOutput output
final DecompilerSettings settings
);
A single call to either of these methods is required to decompile a class; decompiling multiple classes requires multiple calls.
The first argument, internalName
, should be the type descriptor of the class to decompile, e.g., "java/lang/Object"
. By default, internalName
may alternatively be a relative or absolute path to a .class
file on disk, though this is dependent on the default settings
being used (more on this below).
The output
argument will be used used as an output sink by the decompiler. Typically, one will want to capture the plain text output, and for this a PlainTextOutput
instance is sufficient. PlainTextOutput
wraps a java.io.Writer
. By default, it writes to a StringWriter
, and the results may be obtained by calling toString()
on the output object.
To write to disk, or to any other stream, you may wrap an OutputStreamWriter
, but be sure to close()
the writer afterwards, or the output may not get flushed:
:::java
final DecompilerSettings settings = DecompilerSettings.javaDefaults();
try (final FileOutputStream stream = new FileOutputStream("path/to/file");
final OutputStreamWriter writer = new OutputStreamWriter(stream)) {
Decompiler.decompile(
"java/lang/String",
new PlainTextOutput(writer),
settings
);
}
catch (final IOException e) {
// handle error
}
The example above uses Java 7's try-with-resources semantics. If you are using the Java 6 language level, you will need to close()
your writer manually:
:::java
try {
final FileOutputStream stream = new FileOutputStream("path/to/file");
try {
final OutputStreamWriter writer = new OutputStreamWriter(stream);
try {
Decompiler.decompile(
"java/lang/String",
new PlainTextOutput(writer),
DecompilerSettings.javaDefaults()
);
}
finally {
writer.close();
}
}
finally {
stream.close();
}
}
catch (final IOException e) {
// handle error
}
To use the default settings, simply pass DecompilerSettings.javaDefaults()
when calling decompile()
, or omit the settings
argument altogether. The options governed by DecompilerSettings
are mostly straightforward and roughly map to the command-line options supported by the decompiler front-end.
One member which requires some explanation is setTypeLoader()
. This method specifies which ITypeLoader
should be used to resolve and load .class
files. The interface has one method:
:::java
public boolean tryLoadType(final String internalName, final Buffer buffer)
Before a class can be decompiled, its .class
file must be found, loaded, and parsed. This method is responsible for finding the file and loading it into the provided buffer
. Throughout decompilation, many other classes will be loaded as well, many of which will be part of the core JRE. While these classes may not be fully decompiled, the decompiler will need to inspect any classes referenced by a decompiled class in order to determine things like inheritance hierarchies and method overloads. Therefore, the ITypeLoader
provided should be capable of resolving any referenced classes, as well as their super classes and super interfaces. If the decompiler cannot load and analyze referenced classes, the results may be sub-optimal, e.g., they may contain redundant casts and less specific typing than they would otherwise.
By default, an InputTypeLoader
is used. InputTypeLoader
is the type loader used to handle the input passed to the command-line decompiler, and it is the most flexible of the type loaders: it allows a relative or absolute .class
file path to be substituted for internalName
, and dotted names to be used in lieu of type descriptors (e.g., java.lang.String
instead of java/lang/String
). When locating classes by name or type descriptor, an InputTypeLoader
falls back to a wrapped defaultTypeLoader
. Unless otherwise specified, the default type loader is a ClasspathTypeLoader
, which, as the name suggests, uses standard classpath search semantics.
The behavior of InputTypeLoader
is as follows:
-
If
internalName
is a path to a.class
file, it will attempt to load that file directly. If found, the type loader will make a note of the file's package name and location on disk. It may use this information to help locate classes with similar package names in the future, e.g., when the decompiler attempts to load referenced classes. -
If
internalName
was an absolute/rooted path to a.class
file, and step 1 failed, then the type loading fails. -
If present, the
.class
file extension will be removed, any '.
' characters will be converted to '/
', and the result will be treated as a type descriptor. -
The package name will be extracted from the type descriptor, and the type loader will attempt to locate the class file in any of the previously recorded search paths from step 1.
-
Finally, if the type has still not been loaded, the type descriptor will be passed to the
defaultTypeLoader
. If loaded successfully, the method returns. -
If the class was not located, the type loader will attempt an inner class search by replacing one
/
character at a time with$
, from right to left, and repeating steps 4 and 5 each time.
InputTypeLoader
is meant to be flexible enough overriding the default ITypeLoader
should be unnecessary. However, some scenarios like loading a class file directly from a byte[]
array may require it. Several ITypeLoader
implementations are provided, which may be combined together as necessary to achieve the desired results:
-
InputTypeLoader
functions as described above. -
ClasspathTypeLoader
uses standard classpath-based search semantics. Unless otherwise specified, the classpath searched will be the union of thejava.class.path
andsun.boot.class.path
properties. -
JarTypeLoader
loads classes from within a.jar
file. -
ArrayTypeLoader
wraps abyte[]
array containing a single classfile structure. It is capable of loading only the type located within the array, and fails on all other types. -
CompositeTypeLoader
wraps multiple type loaders, and will attempt to load a class using each one, in order, until one of them succeeds.
If you do end up overriding the default ITypeLoader
, it is recommended that you use a CompositeTypeLoader
with an InputTypeLoader
as the last loader in the chain.
Note that with the exception of InputTypeLoader
, each of the ITypeLoader
implementations above requires internalName
to be an exact type descriptor. CompositeTypeLoader
does not care what internalName
contains, as it merely passes it on to the wrapped type loaders.