org.jboss.windup.decompiler.fernflower.FernflowerDecompiler Maven / Gradle / Ivy
package org.jboss.windup.decompiler.fernflower;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import org.jboss.windup.decompiler.api.ClassDecompileRequest;
import org.jboss.windup.decompiler.api.DecompilationException;
import org.jboss.windup.decompiler.api.DecompilationFailure;
import org.jboss.windup.decompiler.api.DecompilationListener;
import org.jboss.windup.decompiler.api.DecompilationResult;
import org.jboss.windup.decompiler.decompiler.AbstractDecompiler;
import org.jboss.windup.decompiler.util.Filter;
import org.jboss.windup.util.exception.WindupStopException;
import org.jetbrains.java.decompiler.main.Fernflower;
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
/**
* Decompiles Java classes with the Fernflower decompiler (https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine)
*
*
* @author Ondrej Zizka
* @author Lincoln Baxter, III
*/
public class FernflowerDecompiler extends AbstractDecompiler
{
private static final Logger LOG = Logger.getLogger(FernflowerDecompiler.class.getName());
public FernflowerDecompiler()
{
}
private Map getOptions()
{
Map options = new HashMap<>();
options.put(IFernflowerPreferences.MAX_PROCESSING_METHOD, 30);
options.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "1");
return options;
}
private IBytecodeProvider getByteCodeProvider()
{
return new IBytecodeProvider()
{
@Override
public byte[] getBytecode(String externalPath, String internalPath) throws IOException
{
return InterpreterUtil.getBytes(new File(externalPath));
}
};
}
private FernFlowerResultSaver getResultSaver(final List requests, File directory, final DecompilationListener listener)
{
return new FernFlowerResultSaver(requests, directory, listener);
}
@Override
public Logger getLogger()
{
return LOG;
}
public Collection> getDecompileTasks(final Map> requestMap,
final DecompilationListener listener)
{
Collection> tasks = new ArrayList<>(requestMap.size());
for (Map.Entry> entry : requestMap.entrySet())
{
final String key = entry.getKey();
final List requests = entry.getValue();
Callable task = new Callable()
{
@Override
public File call() throws Exception
{
if (listener.isCancelled())
return null;
ClassDecompileRequest firstRequest = requests.get(0);
List classFiles = pathsFromDecompilationRequests(requests);
FernFlowerResultSaver resultSaver = getResultSaver(
pathsFromDecompilationRequests(requests), firstRequest.getOutputDirectory().toFile(),
listener);
Fernflower fernflower = new Fernflower(getByteCodeProvider(), resultSaver, getOptions(), new FernflowerJDKLogger());
for (ClassDecompileRequest request : requests)
{
if (listener.isCancelled())
return null;
fernflower.getStructContext().addSpace(request.getClassFile().toFile(), true);
}
try
{
fernflower.decompileContext();
if (!resultSaver.isFileSaved())
listener.decompilationFailed(classFiles, "File was not decompiled!");
}
catch (WindupStopException stop)
{
throw new WindupStopException(stop);
}
catch (Throwable t)
{
listener.decompilationFailed(classFiles, "Decompilation failed due to: " + t.getMessage());
LOG.warning("Decompilation of " + key + " failed due to: " + t.getMessage());
}
return null;
}
};
tasks.add(task);
}
return tasks;
}
@Override
public DecompilationResult decompileClassFile(Path rootDir, Path classFilePath, Path outputDir) throws DecompilationException
{
final DecompilationResult result = new DecompilationResult();
DecompilationListener listener = new DecompilationListener()
{
private boolean cancelled;
@Override
public void fileDecompiled(List inputPath, String outputPath)
{
try
{
result.addDecompiled(inputPath, outputPath);
}
catch (WindupStopException stop)
{
this.cancelled = true;
throw new WindupStopException(stop);
}
}
@Override
public void decompilationFailed(List inputPath, String message)
{
result.addFailure(new DecompilationFailure(message, inputPath, null));
}
@Override
public void decompilationProcessComplete()
{
}
@Override
public boolean isCancelled()
{
return this.cancelled;
}
};
FernFlowerResultSaver resultSaver = getResultSaver(Collections.singletonList(classFilePath.toString()), outputDir.toFile(), listener);
Fernflower fernflower = new Fernflower(getByteCodeProvider(), resultSaver, getOptions(), new FernflowerJDKLogger());
fernflower.getStructContext().addSpace(classFilePath.toFile(), true);
fernflower.decompileContext();
if (!resultSaver.isFileSaved())
listener.decompilationFailed(Collections.singletonList(classFilePath.toString()), "File was not decompiled!");
return result;
}
private List pathsFromDecompilationRequests(List requests)
{
List result = new ArrayList<>();
for (ClassDecompileRequest request : requests)
{
result.add(request.getClassFile().toString());
}
return result;
}
@Override
public DecompilationResult decompileArchiveImpl(Path archive, Path outputDir, Filter filter, final DecompilationListener delegate)
throws DecompilationException
{
final DecompilationResult result = new DecompilationResult();
DecompilationListener listener = new DecompilationListener()
{
private boolean cancelled;
@Override
public void fileDecompiled(List inputPaths, String outputPath)
{
try
{
result.addDecompiled(inputPaths, outputPath);
delegate.fileDecompiled(inputPaths, outputPath);
}
catch (WindupStopException stop)
{
this.cancelled = true;
throw new WindupStopException(stop);
}
}
@Override
public void decompilationFailed(List inputPath, String message)
{
result.addFailure(new DecompilationFailure(message, inputPath, null));
delegate.decompilationFailed(inputPath, message);
}
@Override
public void decompilationProcessComplete()
{
delegate.decompilationProcessComplete();
}
@Override
public boolean isCancelled()
{
return this.cancelled;
}
};
LOG.info("Decompiling archive '" + archive.toAbsolutePath() + "' to '" + outputDir.toAbsolutePath() + "'");
final JarFile jar;
try
{
jar = new JarFile(archive.toFile());
}
catch (IOException ex)
{
throw new DecompilationException("Can't load .jar: " + archive, ex);
}
try
{
final AtomicInteger jarEntryCount = new AtomicInteger(0);
Enumeration countEnum = jar.entries();
while (countEnum.hasMoreElements())
{
countEnum.nextElement();
jarEntryCount.incrementAndGet();
}
final Enumeration entries = jar.entries();
while (entries.hasMoreElements())
{
final JarEntry entry = entries.nextElement();
final String name = entry.getName();
if (!name.endsWith(".class"))
{
jarEntryCount.decrementAndGet();
continue;
}
if (entry.getName().contains("$"))
continue;
if (filter != null)
{
Filter.Result filterRes = filter.decide(entry);
if (filterRes == Filter.Result.REJECT)
{
jarEntryCount.decrementAndGet();
continue;
}
else if (filterRes == Filter.Result.STOP)
{
break;
}
}
IBytecodeProvider bytecodeProvider = new IBytecodeProvider()
{
@Override
public byte[] getBytecode(String externalPath, String internalPath) throws IOException
{
return InterpreterUtil.getBytes(jar, entry);
}
};
FernFlowerResultSaver resultSaver = getResultSaver(Collections.singletonList(entry.getName()), outputDir.toFile(), listener);
Fernflower fernflower = new Fernflower(bytecodeProvider, resultSaver, getOptions(), new FernflowerJDKLogger());
fernflower.getStructContext().addSpace(new File(entry.getName()), true);
fernflower.decompileContext();
if (!resultSaver.isFileSaved())
listener.decompilationFailed(Collections.singletonList(entry.getName()), "File was not decompiled!");
}
listener.decompilationProcessComplete();
return result;
}
finally
{
try
{
jar.close();
}
catch (IOException e)
{
LOG.warning("Failed to close jar file: " + jar.getName());
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy