/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.jdt;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.j2objc.Options;
import com.google.devtools.j2objc.file.RegularInputFile;
import com.google.devtools.j2objc.gen.GenerationUnit;
import com.google.devtools.j2objc.pipeline.ProcessingContext;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.FileUtil;
import com.google.devtools.j2objc.util.PathClassLoader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.annotation.processing.Processor;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import org.eclipse.jdt.internal.compiler.AbstractAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchFilerImpl;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchProcessingEnvImpl;
import org.eclipse.jdt.internal.compiler.batch.Main;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
/**
* Preprocesses all input with annotation processors, if any,
* and adds any .java files generated by those annotation processors
* to the given GenerationBatch.
*/
class AnnotationPreProcessor {
private File tmpDirectory;
private final List<ProcessingContext> generatedInputs = Lists.newArrayList();
private final Options options;
public AnnotationPreProcessor(Options options){
this.options = options;
}
public File getTemporaryDirectory() {
return tmpDirectory;
}
/**
* Process the given input files, given in the same format as J2ObjC command line args.
*
* @return the list of processor-generated sources
*/
public List<ProcessingContext> process(Iterable<String> fileArgs,
List<ProcessingContext> inputs) {
assert tmpDirectory == null; // Shouldn't run an instance more than once.
if (!hasAnnotationProcessors()) {
return generatedInputs;
}
try {
tmpDirectory = FileUtil.createTempDir("annotations");
} catch (IOException e) {
ErrorUtil.error("failed creating temporary directory: " + e);
return generatedInputs;
}
String tmpDirPath = tmpDirectory.getAbsolutePath();
List<String> compileArgs = Lists.newArrayList();
Joiner pathJoiner = Joiner.on(":");
List<String> sourcePath = options.fileUtil().getSourcePathEntries();
sourcePath.add(tmpDirPath);
compileArgs.add("-sourcepath");
compileArgs.add(pathJoiner.join(sourcePath));
compileArgs.add("-classpath");
List<String> classPath = options.fileUtil().getClassPathEntries();
compileArgs.add(pathJoiner.join(classPath));
compileArgs.add("-encoding");
compileArgs.add(options.fileUtil().getCharset().name());
compileArgs.add("-source");
compileArgs.add(options.getSourceVersion().flag());
compileArgs.add("-s");
compileArgs.add(tmpDirPath);
compileArgs.add("-d");
compileArgs.add(tmpDirPath);
List<String> processorPath = options.getProcessorPathEntries();
if (!processorPath.isEmpty()) {
compileArgs.add("-processorpath");
compileArgs.add(pathJoiner.join(processorPath));
}
String processorClasses = options.getProcessors();
if (processorClasses != null) {
compileArgs.add("-processor");
compileArgs.add(processorClasses);
}
if (options.isVerbose()) {
compileArgs.add("-XprintProcessorInfo");
compileArgs.add("-XprintRounds");
}
for (String fileArg : fileArgs) {
compileArgs.add(fileArg);
}
Map<String, String> batchOptions = Maps.newHashMap();
batchOptions.put(CompilerOptions.OPTION_Process_Annotations, CompilerOptions.ENABLED);
batchOptions.put(CompilerOptions.OPTION_GenerateClassFiles, CompilerOptions.DISABLED);
AnnotationCompiler batchCompiler = new AnnotationCompiler(compileArgs, batchOptions,
new PrintWriter(System.out), new PrintWriter(System.err), inputs, options);
if (!batchCompiler.compile(compileArgs.toArray(new String[0]))) {
// Any compilation errors will already by displayed.
ErrorUtil.error("failed batch processing sources");
}
if (!options.getHeaderMap().includeGeneratedSources() && tmpDirectory != null) {
collectGeneratedInputs(tmpDirectory, "", inputs);
}
return generatedInputs;
}
private void collectGeneratedInputs(
File dir, String currentRelativePath, List<ProcessingContext> inputs) {
assert dir.exists() && dir.isDirectory();
for (File f : dir.listFiles()) {
String relativeName = currentRelativePath + File.separatorChar + f.getName();
if (f.isDirectory()) {
collectGeneratedInputs(f, relativeName, inputs);
} else {
if (f.getName().endsWith(".java")) {
inputs.add(ProcessingContext.fromFile(
new RegularInputFile(f.getPath(), relativeName), options));
}
}
}
}
/**
* Check whether any javax.annotation.processing.Processor services are defined on
* the declared classpath. This is checked here to avoid batch compiling sources
* in case any might have annotations that should be processed.
*/
private boolean hasAnnotationProcessors() {
PathClassLoader loader = new PathClassLoader(options.fileUtil().getClassPathEntries());
loader.addPaths(options.getProcessorPathEntries());
ServiceLoader<Processor> serviceLoader = ServiceLoader.load(Processor.class, loader);
Iterator<Processor> iterator = serviceLoader.iterator();
return iterator.hasNext();
}
// Custom javax.annotation.processing.Filer implementation, used to filter new files
// created by annotation processors.
private static class AnnotationProcessorFiler extends BatchFilerImpl {
private final List<ProcessingContext> inputs;
private final Options options;
AnnotationProcessorFiler(BaseAnnotationProcessorManager dispatchManager,
BatchProcessingEnvImpl env, List<ProcessingContext> inputs, Options options) {
super(dispatchManager, env);
this.inputs = inputs;
this.options = options;
}
@Override
public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements)
throws IOException {
if (!options.getHeaderMap().includeGeneratedSources()) {
return super.createSourceFile(name, originatingElements);
}
String referenceFile = null;
TypeElement outerType = null;
for (Element e : originatingElements) {
while (e instanceof TypeElement) {
outerType = (TypeElement) e;
e = outerType.getEnclosingElement();
}
if (outerType instanceof ReferenceBinding) {
referenceFile = new String(((ReferenceBinding) outerType).getFileName());
break;
}
}
if (referenceFile == null && outerType != null) {
referenceFile = outerType.getQualifiedName().toString().replace('.', '/') + ".java";
}
JavaFileObject newSourceFile = super.createSourceFile(name, originatingElements);
ProcessingContext generatedSource = null;
if (referenceFile != null) {
for (ProcessingContext context : inputs) {
if (context.getFile().getUnitName().endsWith(referenceFile)) {
GenerationUnit unit = context.getGenerationUnit();
generatedSource = new ProcessingContext(
new RegularInputFile(newSourceFile.toUri().getPath()), unit);
break;
}
}
}
if (generatedSource == null) {
String relativePath = name.toString().replace('.', '/') + ".java";
generatedSource = ProcessingContext.fromFile(
new RegularInputFile(newSourceFile.toUri().getPath(), relativePath), options);
}
inputs.add(generatedSource);
return newSourceFile;
}
}
// Override batch compiler classes to use custom Filer.
private static class AnnotationCompiler extends org.eclipse.jdt.internal.compiler.batch.Main {
private final String[] commandLine;
private final PrintWriter out;
private final PrintWriter err;
private final List<ProcessingContext> inputs;
private final Options options;
AnnotationCompiler(List<String> compileArgs, Map<String, String> batchOptions,
PrintWriter stdOut, PrintWriter stdErr, List<ProcessingContext> inputs, Options options) {
super(stdOut, stdErr, false, batchOptions, null);
commandLine = compileArgs.toArray(new String[0]);
out = stdOut;
err = stdErr;
this.inputs = inputs;
this.options = options;
}
@Override
protected void initializeAnnotationProcessorManager() {
AbstractAnnotationProcessorManager annotationManager = new BatchAnnotationProcessorManager() {
@Override
public void configure(Object batchCompiler, String[] commandLineArguments) {
super.configure(batchCompiler, commandLineArguments);
BatchProcessingEnvImpl processingEnv = new AnnotationProcessingEnv(this,
(Main) batchCompiler, commandLineArguments, inputs, options);
_processingEnv = processingEnv;
}
};
annotationManager.configure(this, commandLine);
annotationManager.setErr(this.err);
annotationManager.setOut(this.out);
batchCompiler.annotationProcessorManager = annotationManager;
}
}
private static class AnnotationProcessingEnv extends BatchProcessingEnvImpl {
private AnnotationProcessingEnv(BaseAnnotationProcessorManager dispatchManager,
Main batchCompiler, String[] commandLineArguments, List<ProcessingContext> inputs,
Options options) {
super(dispatchManager, batchCompiler, commandLineArguments);
_filer = new AnnotationProcessorFiler(_dispatchManager, this, inputs, options);
}
}
}