package org.jboss.windup.ast.java;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.jboss.windup.util.threading.WindupExecutors;
/**
* Processes multiple files at a time in order to improve performance.
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*/
public class BatchASTProcessor
{
private static final int BATCH_SIZE = 1000 / Runtime.getRuntime().availableProcessors();
/**
* Process the given batch of files and pass the results back to the listener as each file is processed.
*/
public static BatchASTFuture analyze(final BatchASTListener listener, final WildcardImportResolver importResolver,
final Set<String> libraryPaths,
final Set<String> sourcePaths, Set<Path> sourceFiles)
{
final String[] encodings = null;
final String[] bindingKeys = new String[0];
final ExecutorService executor = WindupExecutors.newFixedThreadPool(WindupExecutors.getDefaultThreadCount());
final FileASTRequestor requestor = new FileASTRequestor()
{
@Override
public void acceptAST(String sourcePath, CompilationUnit ast)
{
try
{
/*
* This super() call doesn't do anything, but we call it just to be nice, in case that ever changes.
*/
super.acceptAST(sourcePath, ast);
ReferenceResolvingVisitor visitor = new ReferenceResolvingVisitor(importResolver, ast, sourcePath);
ast.accept(visitor);
listener.processed(Paths.get(sourcePath), visitor.getJavaClassReferences());
}
catch (Throwable t)
{
listener.failed(Paths.get(sourcePath), t);
}
}
};
List<List<String>> batches = createBatches(sourceFiles);
for (final List<String> batch : batches)
{
executor.submit(new Callable<Void>()
{
@Override
public Void call() throws Exception
{
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setBindingsRecovery(false);
parser.setResolveBindings(true);
Map<Object, Object> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
// these options seem to slightly reduce the number of times that JDT aborts on compilation errors
options.put(JavaCore.CORE_INCOMPLETE_CLASSPATH, "warning");
options.put(JavaCore.COMPILER_PB_ENUM_IDENTIFIER, "warning");
options.put(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, "warning");
options.put(JavaCore.CORE_CIRCULAR_CLASSPATH, "warning");
options.put(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, "warning");
options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, "warning");
options.put(JavaCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, "ignore");
options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, "warning");
options.put(JavaCore.CORE_OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, "warning");
options.put(JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE, "warning");
parser.setCompilerOptions(options);
parser.setCompilerOptions(options);
parser.setEnvironment(libraryPaths.toArray(new String[libraryPaths.size()]),
sourcePaths.toArray(new String[sourcePaths.size()]),
null,
true);
parser.createASTs(batch.toArray(new String[batch.size()]), encodings, bindingKeys, requestor, null);
return null;
}
});
}
executor.shutdown();
return new BatchASTFuture()
{
@Override
public boolean isDone()
{
return executor.isTerminated();
}
};
}
private static List<List<String>> createBatches(Set<Path> sourceSet)
{
List<List<String>> result = new ArrayList<>();
List<Path> sourceFiles = new ArrayList<>(sourceSet);
while (!sourceFiles.isEmpty())
{
ListIterator<Path> sourceFileIterator = sourceFiles.listIterator();
Set<String> batchDupeCheck = new HashSet<>();
List<String> batch = new ArrayList<>(BATCH_SIZE);
result.add(batch);
while (sourceFileIterator.hasNext())
{
if (batch.size() == BATCH_SIZE)
{
batch = new ArrayList<>(BATCH_SIZE);
result.add(batch);
batchDupeCheck.clear();
}
Path path = sourceFileIterator.next();
if (!batchDupeCheck.contains(path.getFileName().toString()))
{
batch.add(path.toAbsolutePath().toString());
batchDupeCheck.add(path.getFileName().toString());
sourceFileIterator.remove();
}
}
}
return result;
}
}