/*
* 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.javac;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.Options;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.file.InputFile;
import com.google.devtools.j2objc.file.RegularInputFile;
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.Parser;
import com.google.devtools.j2objc.util.PathClassLoader;
import com.google.devtools.j2objc.util.SourceVersion;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.tree.JCTree;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
/**
* Parser front-end that uses javac.
*
* @author Tom Ball
*/
public class JavacParser extends Parser {
private JavacFileManager fileManager;
public JavacParser(Options options){
super(options);
}
@Override
public void setEnableDocComments(boolean enable) {
// Ignore, since JavacTaskImpl always enables them.
}
@Override
public CompilationUnit parse(InputFile file) {
String source = null;
try {
source = options.fileUtil().readFile(file);
return parse(null, file.getUnitName(), source);
} catch (IOException e) {
ErrorUtil.error(e.getMessage());
return null;
}
}
@Override
public CompilationUnit parse(String mainType, String path, String source) {
try {
JavacEnvironment parserEnv = createEnvironment(path, source);
JavacTaskImpl task = parserEnv.task();
JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit) task.parse().iterator().next();
task.analyze();
processDiagnostics(parserEnv.diagnostics());
return TreeConverter.convertCompilationUnit(options, parserEnv, unit);
} catch (IOException e) {
ErrorUtil.fatalError(e, path);
}
return null;
}
private JavacFileManager getFileManager(JavaCompiler compiler,
DiagnosticCollector<JavaFileObject> diagnostics) throws IOException {
fileManager = (JavacFileManager)
compiler.getStandardFileManager(diagnostics, null, options.fileUtil().getCharset());
addPaths(StandardLocation.CLASS_PATH, classpathEntries, fileManager);
addPaths(StandardLocation.SOURCE_PATH, sourcepathEntries, fileManager);
addPaths(StandardLocation.PLATFORM_CLASS_PATH, options.getBootClasspath(), fileManager);
List<String> processorPathEntries = options.getProcessorPathEntries();
if (!processorPathEntries.isEmpty()) {
addPaths(StandardLocation.ANNOTATION_PROCESSOR_PATH, processorPathEntries, fileManager);
}
fileManager.setLocation(StandardLocation.CLASS_OUTPUT,
Lists.newArrayList(options.fileUtil().getOutputDirectory()));
fileManager.setLocation(StandardLocation.SOURCE_OUTPUT,
Lists.newArrayList(FileUtil.createTempDir("annotations")));
return fileManager;
}
private void addPaths(Location location, List<String> paths, JavacFileManager fileManager)
throws IOException {
List<File> filePaths = new ArrayList<>();
for (String path : paths) {
filePaths.add(new File(path));
}
fileManager.setLocation(location, filePaths);
}
private List<String> getJavacOptions(boolean processAnnotations) {
List<String> javacOptions = new ArrayList<>();
String encoding = options.fileUtil().getFileEncoding();
if (encoding != null) {
javacOptions.add("-encoding");
javacOptions.add(encoding);
}
SourceVersion javaLevel = options.getSourceVersion();
if (javaLevel != null) {
javacOptions.add("-source");
javacOptions.add(javaLevel.flag());
javacOptions.add("-target");
javacOptions.add(javaLevel.flag());
}
String lintArgument = options.lintArgument();
if (lintArgument != null) {
javacOptions.add(lintArgument);
}
if (processAnnotations) {
javacOptions.add("-proc:only");
} else {
javacOptions.add("-proc:none");
}
return javacOptions;
}
@Override
public void parseFiles(Collection<String> paths, Handler handler, SourceVersion sourceVersion) {
List<File> files = new ArrayList<>();
for (String path : paths) {
files.add(new File(path));
}
try {
JavacEnvironment env = createEnvironment(files, null, false);
List<CompilationUnitTree> units = new ArrayList<>();
for (CompilationUnitTree unit : env.task().parse()) {
units.add(unit);
}
env.task().analyze();
processDiagnostics(env.diagnostics());
if (ErrorUtil.errorCount() == 0) {
for (CompilationUnitTree ast : units) {
com.google.devtools.j2objc.ast.CompilationUnit unit = TreeConverter
.convertCompilationUnit(options, env, (JCTree.JCCompilationUnit) ast);
processDiagnostics(env.diagnostics());
handler.handleParsedUnit(unit.getSourceFilePath(), unit);
}
}
} catch (IOException e) {
ErrorUtil.fatalError(e, "javac file manager error");
}
}
// Creates a javac environment from a memory source.
private JavacEnvironment createEnvironment(String path, String source) throws IOException {
List<JavaFileObject> inputFiles = new ArrayList<>();
inputFiles.add(MemoryFileObject.createJavaFile(path, source));
return createEnvironment(Collections.emptyList(), inputFiles, false);
}
// Creates a javac environment from a collection of files and/or file objects.
private JavacEnvironment createEnvironment(List<File> files, List<JavaFileObject> fileObjects,
boolean processAnnotations) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
JavacFileManager fileManager = getFileManager(compiler, diagnostics);
List<String> javacOptions = getJavacOptions(processAnnotations);
if (fileObjects == null) {
fileObjects = new ArrayList<>();
}
for (JavaFileObject jfo : fileManager.getJavaFileObjectsFromFiles(files)) {
fileObjects.add(jfo);
}
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fileManager, diagnostics,
javacOptions, null, fileObjects);
return new JavacEnvironment(task, fileManager, diagnostics);
}
private void processDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
ErrorUtil.parserDiagnostic(diagnostic);
}
}
@Override
public Parser.ParseResult parseWithoutBindings(InputFile file, String source) {
String path = file.getUnitName();
try {
JavacEnvironment parserEnv = createEnvironment(path, source);
JavacTaskImpl task = parserEnv.task();
JCTree.JCCompilationUnit unit = (JCTree.JCCompilationUnit) task.parse().iterator().next();
processDiagnostics(parserEnv.diagnostics());
return new JavacParseResult(file, source, unit);
} catch (IOException e) {
ErrorUtil.fatalError(e, path);
}
return null;
}
@Override
public ProcessingResult processAnnotations(Iterable<String> fileArgs,
List<ProcessingContext> inputs) {
final List<ProcessingContext> generatedInputs = Lists.newArrayList();
PathClassLoader loader = new PathClassLoader(options.fileUtil().getClassPathEntries());
loader.addPaths(options.getProcessorPathEntries());
Iterator<Processor> serviceIterator = ServiceLoader.load(Processor.class, loader).iterator();
if (serviceIterator.hasNext()) {
List<File> inputFiles = new ArrayList<>();
for (ProcessingContext input : inputs) {
inputFiles.add(new File(input.getFile().getAbsolutePath()));
}
try {
JavacEnvironment env = createEnvironment(inputFiles, null, true);
List<CompilationUnitTree> units = new ArrayList<>();
for (CompilationUnitTree unit : env.task().parse()) {
units.add(unit);
}
// JavacTaskImpl.enter() parses and runs annotation processing, but
// not type checking and attribution (that's done by analyze()).
env.task().enter();
processDiagnostics(env.diagnostics());
// The source output directory is created and set in createEnvironment().
File sourceOutputDirectory =
env.fileManager().getLocation(StandardLocation.SOURCE_OUTPUT).iterator().next();
collectGeneratedInputs(sourceOutputDirectory, "", generatedInputs);
return new JavacProcessingResult(generatedInputs, sourceOutputDirectory);
} catch (IOException e) {
ErrorUtil.fatalError(e, "javac file manager error");
}
}
// No annotation processors on classpath, or processing errors reported.
return new JavacProcessingResult(generatedInputs, null);
}
@Override
public void close() throws IOException {
if (fileManager != null) {
try {
fileManager.close();
} finally {
fileManager = null;
}
}
}
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));
}
}
}
}
private static class JavacParseResult implements Parser.ParseResult {
private final InputFile file;
private String source;
private final JCTree.JCCompilationUnit unit;
private JavacParseResult(InputFile file, String source, JCTree.JCCompilationUnit unit) {
this.file = file;
this.source = source;
this.unit = unit;
}
@Override
public void stripIncompatibleSource() {
source = JavacJ2ObjCIncompatibleStripper.strip(source, unit);
}
@Override
public String getSource() {
return source;
}
@Override
public String mainTypeName() {
String qualifiedName = FileUtil.getMainTypeName(file);
ExpressionTree packageDecl = unit.getPackageName();
if (packageDecl != null) {
qualifiedName = packageDecl.toString() + "." + qualifiedName;
}
return qualifiedName;
}
@Override
public String toString() {
return unit.toString();
}
}
private static class JavacProcessingResult implements Parser.ProcessingResult {
private final List<ProcessingContext> generatedSources;
private final File sourceOutputDirectory;
public JavacProcessingResult(List<ProcessingContext> generatedSources,
File sourceOutputDirectory) {
this.generatedSources = generatedSources;
this.sourceOutputDirectory = sourceOutputDirectory;
}
@Override
public List<ProcessingContext> getGeneratedSources() {
return generatedSources;
}
@Override
public File getSourceOutputDirectory() {
return sourceOutputDirectory;
}
}
}