/* * 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.collect.ImmutableMap; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.Options.LintOption; 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.ParserEnvironment; import com.google.devtools.j2objc.util.SourceVersion; import com.google.devtools.j2objc.util.TranslationEnvironment; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.lang.model.element.Element; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.eclipse.jdt.core.compiler.IProblem; 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.eclipse.jdt.core.dom.ITypeBinding; /** * Adapts JDT's ASTParser to a more convenient interface for parsing source * strings into CompilationUnit trees. * * @author Tom Ball, Keith Stanger */ public class JdtParser extends Parser { private static final Logger logger = Logger.getLogger(JdtParser.class.getName()); private Map<String, String> compilerOptions = initCompilerOptions(options.getSourceVersion(), options.lintOptions()); private static Map<String, String> initCompilerOptions( SourceVersion sourceVersion, EnumSet<LintOption> lintOptions) { Map<String, String> compilerOptions = new HashMap<>(); String version = sourceVersion.flag(); compilerOptions.put(org.eclipse.jdt.core.JavaCore.COMPILER_SOURCE, version); compilerOptions.put(org.eclipse.jdt.core.JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, version); compilerOptions.put(org.eclipse.jdt.core.JavaCore.COMPILER_COMPLIANCE, version); // Turn on any specified lint warnings. for (Options.LintOption lintOption : lintOptions) { compilerOptions.put(lintOption.jdtFlag(), "warning"); } // Turn off any warnings on by default but not requested. for (Options.LintOption lintOption : EnumSet.complementOf(lintOptions)) { compilerOptions.put(lintOption.jdtFlag(), "ignore"); } return compilerOptions; } public JdtParser(Options options){ super(options); } @Override public void addClasspathEntry(String entry) { if (isValidPathEntry(entry)) { classpathEntries.add(entry); } } @Override public void addSourcepathEntry(String entry) { if (isValidPathEntry(entry)) { sourcepathEntries.add(entry); } } @Override public void prependSourcepathEntry(String entry) { if (isValidPathEntry(entry)) { sourcepathEntries.add(0, entry); } } @Override public void setEnableDocComments(boolean enable) { // BodyDeclaration.getJavadoc() always returns null without this option enabled, // so by default no doc comments are generated. compilerOptions.put( org.eclipse.jdt.core.JavaCore.COMPILER_DOC_COMMENT_SUPPORT, enable ? "enabled" : "disabled"); } @Override public Parser.ParseResult parseWithoutBindings(InputFile file, String source) { CompilationUnit unit = parse(file.getUnitName(), source, false); return new JdtParseResult(file, source, unit); } @Override public com.google.devtools.j2objc.ast.CompilationUnit parse(InputFile file) { String source = null; try { source = options.fileUtil().readFile(file); return parse(FileUtil.getMainTypeName(file), file.getUnitName(), source); } catch (IOException e) { ErrorUtil.error(e.getMessage()); return null; } } @Override public com.google.devtools.j2objc.ast.CompilationUnit parse(String mainTypeName, String path, String source) { int errors = ErrorUtil.errorCount(); org.eclipse.jdt.core.dom.CompilationUnit unit = parse(path, source, true); if (ErrorUtil.errorCount() > errors) { return null; } if (mainTypeName == null) { RegularInputFile file = new RegularInputFile(path); mainTypeName = FileUtil.getQualifiedMainTypeName(file, unit); } ParserEnvironment parserEnv = new JdtParserEnvironment(unit.getAST()); TranslationEnvironment env = new TranslationEnvironment(options, parserEnv); return TreeConverter.convertCompilationUnit(env, unit, path, mainTypeName, source); } private CompilationUnit parse(String unitName, String source, boolean resolveBindings) { ASTParser parser = newASTParser(resolveBindings, options.getSourceVersion()); parser.setUnitName(unitName); parser.setSource(source.toCharArray()); CompilationUnit unit = (CompilationUnit) parser.createAST(null); if (checkCompilationErrors(unitName, unit)) { return unit; } else { return null; } } @Override public void parseFiles(Collection<String> paths, final Handler handler, SourceVersion sourceVersion) { ASTParser parser = newASTParser(true, sourceVersion); FileASTRequestor astRequestor = new FileASTRequestor() { @Override public void acceptAST(String sourceFilePath, CompilationUnit ast) { logger.fine("acceptAST: " + sourceFilePath); if (checkCompilationErrors(sourceFilePath, ast)) { RegularInputFile file = new RegularInputFile(sourceFilePath); try { String source = options.fileUtil().readFile(file); ParserEnvironment parserEnv = new JdtParserEnvironment(ast.getAST()); TranslationEnvironment env = new TranslationEnvironment(options, parserEnv); com.google.devtools.j2objc.ast.CompilationUnit unit = TreeConverter.convertCompilationUnit( env, ast, sourceFilePath, FileUtil.getMainTypeName(file), source); handler.handleParsedUnit(sourceFilePath, unit); } catch (IOException e) { ErrorUtil.error( "Error reading file " + file.getOriginalLocation() + ": " + e.getMessage()); } } } }; // JDT fails to resolve all secondary bindings unless there are the same // number of "binding key" strings as source files. It doesn't appear to // matter what the binding key strings should be (as long as they're non- // null), so the paths array is reused. String[] pathsArray = paths.toArray(new String[paths.size()]); parser.createASTs(pathsArray, getEncodings(pathsArray.length), pathsArray, astRequestor, null); } @Override public ProcessingResult processAnnotations(Iterable<String> fileArgs, List<ProcessingContext> inputs) { AnnotationPreProcessor preProcessor = new AnnotationPreProcessor(options); final List<ProcessingContext> generatedInputs = preProcessor.process(fileArgs, inputs); return new Parser.ProcessingResult() { @Override public File getSourceOutputDirectory() { return preProcessor.getTemporaryDirectory(); } @Override public List<ProcessingContext> getGeneratedSources() { return generatedInputs; } }; } @SuppressWarnings("deprecation") private ASTParser newASTParser(boolean resolveBindings, SourceVersion sourceVersion) { ASTParser parser; if (SourceVersion.java8Minimum(sourceVersion)) { parser = ASTParser.newParser(AST.JLS8); } else { parser = ASTParser.newParser(AST.JLS4); // Java 7 } parser.setCompilerOptions(compilerOptions); parser.setResolveBindings(resolveBindings); parser.setEnvironment( toArray(classpathEntries), toArray(sourcepathEntries), getEncodings(sourcepathEntries.size()), includeRunningVMBootclasspath); return parser; } private String[] toArray(List<String> list) { return list.toArray(new String[list.size()]); } private boolean isValidPathEntry(String path) { // JDT requires that all path elements exist and can hold class files. File f = new File(path); return f.exists() && (f.isDirectory() || path.endsWith(".jar")); } private String[] getEncodings(int length) { String encoding = options.fileUtil().getFileEncoding(); if (encoding == null) { return null; } String[] encodings = new String[length]; Arrays.fill(encodings, encoding); return encodings; } private boolean checkCompilationErrors(String filename, CompilationUnit unit) { boolean hasErrors = false; for (IProblem problem : unit.getProblems()) { if (problem.isError()) { ErrorUtil.error(String.format( "%s:%s: %s", filename, problem.getSourceLineNumber(), problem.getMessage())); hasErrors = true; } if (problem.isWarning()) { ErrorUtil.warning(String.format( "%s:%s: warning: %s", filename, problem.getSourceLineNumber(), problem.getMessage())); } } return !hasErrors; } private static class JdtParserEnvironment implements ParserEnvironment { private final AST ast; private final Types types; private final Map<String, Element> knownTypes; JdtParserEnvironment(AST ast) { this.ast = ast; types = new JdtTypes(ast); ITypeBinding javaLangInteger = ast.resolveWellKnownType("java.lang.Integer"); knownTypes = ImmutableMap.of( "java.lang.Number", BindingConverter.getTypeElement(javaLangInteger.getSuperclass())); } @Override public Element resolve(String name) { Element result = knownTypes.get(name); return result != null ? result : BindingConverter.getElement(ast.resolveWellKnownType(name)); } @Override public Elements elementUtilities() { return JdtElements.INSTANCE; } @Override public Types typeUtilities() { return types; } @Override public void reset() { BindingConverter.reset(); } } private static class JdtParseResult implements Parser.ParseResult { private final InputFile file; private String source; private final CompilationUnit unit; private JdtParseResult(InputFile file, String source, CompilationUnit unit) { super(); this.file = file; this.source = source; this.unit = unit; } @Override public void stripIncompatibleSource() { source = JdtJ2ObjCIncompatibleStripper.strip(source, unit); } @Override public String getSource() { return source; } @Override public String mainTypeName() { String qualifiedName = FileUtil.getMainTypeName(file); org.eclipse.jdt.core.dom.PackageDeclaration packageDecl = unit.getPackage(); if (packageDecl != null) { String packageName = packageDecl.getName().getFullyQualifiedName(); qualifiedName = packageName + "." + qualifiedName; } return qualifiedName; } @Override public String toString() { return unit.toString(); } } }