/*
* 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.pipeline;
import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.DebugASTDump;
import com.google.devtools.j2objc.gen.GenerationUnit;
import com.google.devtools.j2objc.gen.ObjectiveCHeaderGenerator;
import com.google.devtools.j2objc.gen.ObjectiveCImplementationGenerator;
import com.google.devtools.j2objc.gen.ObjectiveCSegmentedHeaderGenerator;
import com.google.devtools.j2objc.translate.AbstractMethodRewriter;
import com.google.devtools.j2objc.translate.AnnotationRewriter;
import com.google.devtools.j2objc.translate.ArrayRewriter;
import com.google.devtools.j2objc.translate.Autoboxer;
import com.google.devtools.j2objc.translate.CastResolver;
import com.google.devtools.j2objc.translate.ComplexExpressionExtractor;
import com.google.devtools.j2objc.translate.ConstantBranchPruner;
import com.google.devtools.j2objc.translate.DeadCodeEliminator;
import com.google.devtools.j2objc.translate.DefaultMethodShimGenerator;
import com.google.devtools.j2objc.translate.DestructorGenerator;
import com.google.devtools.j2objc.translate.EnhancedForRewriter;
import com.google.devtools.j2objc.translate.EnumRewriter;
import com.google.devtools.j2objc.translate.Functionizer;
import com.google.devtools.j2objc.translate.GwtConverter;
import com.google.devtools.j2objc.translate.InitializationNormalizer;
import com.google.devtools.j2objc.translate.InnerClassExtractor;
import com.google.devtools.j2objc.translate.JavaCloneWriter;
import com.google.devtools.j2objc.translate.JavaToIOSMethodTranslator;
import com.google.devtools.j2objc.translate.LabelRewriter;
import com.google.devtools.j2objc.translate.LambdaRewriter;
import com.google.devtools.j2objc.translate.LambdaTypeElementAdder;
import com.google.devtools.j2objc.translate.MetadataWriter;
import com.google.devtools.j2objc.translate.NilCheckResolver;
import com.google.devtools.j2objc.translate.NumberMethodRewriter;
import com.google.devtools.j2objc.translate.OcniExtractor;
import com.google.devtools.j2objc.translate.OperatorRewriter;
import com.google.devtools.j2objc.translate.OuterReferenceResolver;
import com.google.devtools.j2objc.translate.PackageInfoRewriter;
import com.google.devtools.j2objc.translate.PrivateDeclarationResolver;
import com.google.devtools.j2objc.translate.Rewriter;
import com.google.devtools.j2objc.translate.StaticVarRewriter;
import com.google.devtools.j2objc.translate.SuperMethodInvocationRewriter;
import com.google.devtools.j2objc.translate.SwitchRewriter;
import com.google.devtools.j2objc.translate.UnsequencedExpressionRewriter;
import com.google.devtools.j2objc.translate.VarargsRewriter;
import com.google.devtools.j2objc.translate.VariableRenamer;
import com.google.devtools.j2objc.types.HeaderImportCollector;
import com.google.devtools.j2objc.types.ImplementationImportCollector;
import com.google.devtools.j2objc.types.Import;
import com.google.devtools.j2objc.util.CodeReferenceMap;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.Parser;
import com.google.devtools.j2objc.util.TimeTracker;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Processes source files by translating each source into an Objective-C header
* and an Objective-C source file.
*
* @author Tom Ball, Keith Stanger, Mike Thvedt
*/
public class TranslationProcessor extends FileProcessor {
private static final Logger logger = Logger.getLogger(TranslationProcessor.class.getName());
private final CodeReferenceMap deadCodeMap;
private int processedCount = 0;
public TranslationProcessor(Parser parser, CodeReferenceMap deadCodeMap) {
super(parser);
this.deadCodeMap = deadCodeMap;
}
@Override
protected void processConvertedTree(ProcessingContext input, CompilationUnit unit) {
String unitName = input.getOriginalSourcePath();
if (logger.isLoggable(Level.INFO)) {
System.out.println("translating " + unitName);
}
TimeTracker ticker = TimeTracker.getTicker(unitName, options.timingLevel());
if (options.dumpAST()) {
// Dump compilation unit to an .ast output file instead of translating.
DebugASTDump.dumpUnit(unit);
} else {
applyMutations(unit, deadCodeMap, ticker);
ticker.tick("Tree mutations");
ticker.printResults(System.out);
GenerationUnit genUnit = input.getGenerationUnit();
genUnit.addCompilationUnit(unit);
// Add out-of-date dependencies to translation list.
if (closureQueue != null) {
checkDependencies(unit);
}
if (genUnit.isFullyParsed()) {
generateObjectiveCSource(genUnit);
}
}
processedCount++;
}
/**
* Translates a parsed source file, modifying the compilation unit by
* substituting core Java type and method references with iOS equivalents.
* For example, <code>java.lang.Object</code> maps to <code>NSObject</code>,
* and <code>java.lang.String</code> to <code>NSString</code>. The source is
* also modified to add support for iOS memory management, extract inner
* classes, etc.
*/
public static void applyMutations(CompilationUnit unit, CodeReferenceMap deadCodeMap,
TimeTracker ticker) {
ticker.push();
// Before: OuterReferenceResolver - OuterReferenceResolver needs the bindings fixed.
new LambdaTypeElementAdder(unit).run();
ticker.tick("LambdaTypeElementAdder");
if (deadCodeMap != null) {
new DeadCodeEliminator(unit, deadCodeMap).run();
ticker.tick("DeadCodeEliminator");
}
new OuterReferenceResolver(unit).run();
ticker.tick("OuterReferenceResolver");
// Update code that has GWT references.
new GwtConverter(unit).run();
ticker.tick("GwtConverter");
// Add default equals/hashCode methods to Number subclasses, if necessary.
new NumberMethodRewriter(unit).run();
ticker.tick("NumberMethodRewriter");
// Before: Rewriter - Pruning unreachable statements must happen before
// rewriting labeled break statements.
// Before: InnerClassExtractor - Removes unreachable local classes.
new ConstantBranchPruner(unit).run();
ticker.tick("ConstantBranchPruner");
// Modify AST to be more compatible with Objective C
new Rewriter(unit).run();
ticker.tick("Rewriter");
// Add abstract method stubs.
new AbstractMethodRewriter(unit, deadCodeMap).run();
ticker.tick("AbstractMethodRewriter");
new VariableRenamer(unit).run();
ticker.tick("VariableRenamer");
// Rewrite enhanced for loops into correct C code.
new EnhancedForRewriter(unit).run();
ticker.tick("EnhancedForRewriter");
// Before: Autoboxer - Must generate implementations so autoboxing can be applied to result.
new LambdaRewriter(unit).run();
ticker.tick("LambdaRewriter");
// Add auto-boxing conversions.
new Autoboxer(unit).run();
ticker.tick("Autoboxer");
new InnerClassExtractor(unit).run();
ticker.tick("InnerClassExtractor");
// Generate method shims for classes implementing interfaces that have default methods
new DefaultMethodShimGenerator(unit, deadCodeMap).run();
ticker.tick("DefaultMethodShimGenerator");
// Normalize init statements
new InitializationNormalizer(unit).run();
ticker.tick("InitializationNormalizer");
// Adds nil_chk calls wherever an expression is dereferenced.
// After: InnerClassExtractor - Cannot handle local classes.
// After: InitializationNormalizer
// Before: LabelRewriter - Control flow analysis requires original Java
// labels.
new NilCheckResolver(unit).run();
ticker.tick("NilCheckResolver");
// Rewrites expressions that would cause unsequenced compile errors.
if (unit.getEnv().options().extractUnsequencedModifications()) {
new UnsequencedExpressionRewriter(unit).run();
ticker.tick("UnsequencedExpressionRewriter");
}
// Rewrites labeled break and continue statements.
unit.accept(new LabelRewriter());
ticker.tick("LabelRewriter");
// Before: ArrayRewriter - Adds ArrayCreation nodes.
// Before: Functionizer - Can't rewrite function arguments.
new VarargsRewriter(unit).run();
ticker.tick("VarargsRewriter");
new JavaCloneWriter(unit).run();
ticker.tick("JavaCloneWriter");
new OcniExtractor(unit, deadCodeMap).run();
ticker.tick("OcniExtractor");
// Before: AnnotationRewriter - Needs AnnotationRewriter to add the
// annotation metadata to the generated package-info type.
PackageInfoRewriter.run(unit);
ticker.tick("PackageInfoRewriter");
// Before: DestructorGenerator - Annotation types need a destructor to
// release the added fields.
new AnnotationRewriter(unit).run();
ticker.tick("AnnotationRewriter");
// Before: Functionizer - Edits constructor invocations before they are
// functionized.
new EnumRewriter(unit).run();
ticker.tick("EnumRewriter");
// Add dealloc/finalize method(s), if necessary. This is done
// after inner class extraction, so that each class releases
// only its own instance variables.
new DestructorGenerator(unit).run();
ticker.tick("DestructorGenerator");
// Before: StaticVarRewriter - Generates static variable access expressions.
new MetadataWriter(unit).run();
ticker.tick("MetadataWriter");
// Before: Functionizer - Needs to rewrite some ClassInstanceCreation nodes
// before Functionizer does.
// Before: StaticVarRewriter, OperatorRewriter - Doesn't know how to handle
// the hasRetainedResult flag on ClassInstanceCreation nodes.
new JavaToIOSMethodTranslator(unit).run();
ticker.tick("JavaToIOSMethodTranslator");
// After: OcniExtractor - So that native methods can be correctly
// functionized.
new Functionizer(unit).run();
ticker.tick("Functionizer");
// After: Functionizer - Edits the qualifier on SuperMethodInvocation nodes.
new SuperMethodInvocationRewriter(unit).run();
ticker.tick("SuperMethodInvocationRewriter");
new OperatorRewriter(unit).run();
ticker.tick("OperatorRewriter");
// After: OperatorRewriter - Static load rewriting needs to happen after
// operator rewriting.
new StaticVarRewriter(unit).run();
ticker.tick("StaticVarRewriter");
// After: StaticVarRewriter, OperatorRewriter - They set the
// hasRetainedResult on ArrayCreation nodes.
new ArrayRewriter(unit).run();
ticker.tick("ArrayRewriter");
new SwitchRewriter(unit).run();
ticker.tick("SwitchRewriter");
// Breaks up deeply nested expressions such as chained method calls.
// Should be one of the last translations because other mutations will
// affect how deep the expressions are.
unit.accept(new ComplexExpressionExtractor());
ticker.tick("ComplexExpressionExtractor");
// Should be one of the last translations because methods and functions
// added in other phases may need added casts.
new CastResolver(unit).run();
ticker.tick("CastResolver");
// After: InnerClassExtractor, Functionizer - Expects all types to be
// top-level and functionizing to have occured.
new PrivateDeclarationResolver(unit).run();
ticker.tick("PrivateDeclarationResolver");
// Make sure we still have a valid AST.
unit.validate();
ticker.pop();
}
@VisibleForTesting
public static void generateObjectiveCSource(GenerationUnit unit) {
assert unit.getOutputPath() != null;
assert unit.isFullyParsed();
TimeTracker ticker = TimeTracker.getTicker(unit.getSourceName(), unit.options().timingLevel());
logger.fine("Generating " + unit.getOutputPath());
logger.finest("writing output file(s) to "
+ unit.options().fileUtil().getOutputDirectory().getAbsolutePath());
ticker.push();
// write header
if (unit.options().generateSegmentedHeaders()) {
ObjectiveCSegmentedHeaderGenerator.generate(unit);
} else {
ObjectiveCHeaderGenerator.generate(unit);
}
ticker.tick("Header generation");
// write implementation file
ObjectiveCImplementationGenerator.generate(unit);
ticker.tick("Implementation generation");
unit.finished();
ticker.pop();
ticker.tick("Source generation");
ticker.printResults(System.out);
}
@Override
protected void handleError(ProcessingContext input) {
// Causes the generation unit to release any trees it was holding.
input.getGenerationUnit().failed();
}
public void postProcess() {
if (logger.isLoggable(Level.INFO)) {
int nFiles = processedCount;
System.out.println(String.format(
"Translated %d %s: %d errors, %d warnings",
nFiles, nFiles == 1 ? "file" : "files", ErrorUtil.errorCount(),
ErrorUtil.warningCount()));
}
if (logger.isLoggable(Level.FINE)) {
System.out.println(String.format("Translated %d methods as functions",
ErrorUtil.functionizedMethodCount()));
}
}
private void checkDependencies(CompilationUnit unit) {
HeaderImportCollector hdrCollector =
new HeaderImportCollector(unit, HeaderImportCollector.Filter.INCLUDE_ALL);
hdrCollector.run();
ImplementationImportCollector implCollector = new ImplementationImportCollector(unit);
implCollector.run();
Set<Import> imports = hdrCollector.getForwardDeclarations();
imports.addAll(hdrCollector.getSuperTypes());
imports.addAll(implCollector.getImports());
for (Import imp : imports) {
String qualifiedName = imp.getJavaQualifiedName();
if (qualifiedName != null) {
closureQueue.addName(qualifiedName);
}
}
}
}