package org.jboss.windup.decompiler.decompiler; import org.jboss.windup.decompiler.api.ClassDecompileRequest; import org.jboss.windup.decompiler.api.DecompilationException; import org.jboss.windup.decompiler.api.DecompilationListener; import org.jboss.windup.decompiler.api.DecompilationResult; import org.jboss.windup.decompiler.api.Decompiler; import org.jboss.windup.decompiler.util.Filter; import org.jboss.windup.util.Checks; import org.jboss.windup.util.threading.WindupExecutors; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import java.util.zip.ZipEntry; /** * An abstract class encapsulating the common logic from the {@link org.jboss.windup.decompiler.api.Decompiler} implementations. * @author <a href="mailto:mbriskar@redhat.com">Matej Briskar</a> */ public abstract class AbstractDecompiler implements Decompiler { private ExecutorService executorService = WindupExecutors.newSingleThreadExecutor(); private int numberOfThreads = 1; public abstract Logger getLogger(); public void setExecutorService(ExecutorService service, int numberOfThreads) { this.executorService.shutdown(); try { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { throw new IllegalStateException("Was not able to decompile in the given time limit."); } this.numberOfThreads = numberOfThreads; this.executorService = service; } protected Map<String, List<ClassDecompileRequest>> groupDecompileRequests(final Collection<ClassDecompileRequest> requests) { Map<String, List<ClassDecompileRequest>> requestMap = new HashMap<>(); for (ClassDecompileRequest request : requests) { /* * Combine requests that are related (for example Foo.class and Foo$1.class), as this helps fernflower to resolve inner classes. */ String filename = request.getClassFile().getFileName().toString(); String key; boolean mainClassFile = false; if (filename.matches(".*\\$.*.class")) { key = request.getClassFile().getParent().resolve(filename.substring(0, filename.indexOf("$")) + ".class").toString(); } else { mainClassFile=true; key = request.getClassFile().toString(); } List<ClassDecompileRequest> list = requestMap.get(key); if (list == null) { list = new ArrayList<>(); requestMap.put(key, list); } if(mainClassFile) { list.add(0,request); } else { list.add(request); } } return requestMap; } public abstract Collection<Callable<File>> getDecompileTasks(Map<String, List<ClassDecompileRequest>> requestMap,DecompilationListener listener); @Override public void decompileClassFiles(Collection<ClassDecompileRequest> requests, DecompilationListener listener) { Map<String, List<ClassDecompileRequest>> requestMap = groupDecompileRequests(requests); Collection<Callable<File>> tasks = getDecompileTasks(requestMap,listener); try { executorService.invokeAll(tasks); } catch (InterruptedException e) { throw new IllegalStateException("Decompilation was interrupted."); } finally { listener.decompilationProcessComplete(); } } @Override public void close() { this.executorService.shutdown(); try { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException e) { throw new IllegalStateException("Was not able to decompile in the given time limit."); } } /** * Decompiles all .class files and nested archives in the given archive. * <p> * Nested archives will be decompiled into directories matching the name of the archive, e.g. * <code>foo.ear/bar.jar/src/com/foo/bar/Baz.java</code>. * <p> * Required directories will be created as needed. * * @param archive The archive containing source files and archives. * @param outputDir The directory where decompiled .java files will be placed. * * @returns Result with all decompilation failures. Never throws. */ @Override public DecompilationResult decompileArchive(Path archive, Path outputDir, DecompilationListener listener) throws DecompilationException { return decompileArchive(archive, outputDir, null, listener); } @Override public DecompilationResult decompileArchive(Path archive, Path outputDir, Filter<ZipEntry> filter, DecompilationListener listener) throws DecompilationException { Checks.checkFileToBeRead(archive.toFile(), "Archive to decompile"); Checks.checkDirectoryToBeFilled(outputDir.toFile(), "Output directory"); return decompileArchiveImpl(archive, outputDir, filter, listener); } public abstract DecompilationResult decompileArchiveImpl(Path archive, Path outputDir, Filter<ZipEntry> filter, DecompilationListener listener); public ExecutorService getExecutorService() { return executorService; } public int getNumberOfThreads() { return numberOfThreads; } }