/* * 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.gen; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.Javadoc; import com.google.devtools.j2objc.ast.NativeDeclaration; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.file.InputFile; import com.google.devtools.j2objc.util.ElementUtil; import java.io.File; import java.util.Collection; import java.util.TreeMap; import javax.annotation.Nullable; /** * A single unit of generated code, to be turned into a single pair of .h and .m files. * <p/> * Some attributes, like the name and output path, might not be known before parsing. * These are set by a {@link com.google.devtools.j2objc.FileProcessor}. * * @author Mike Thvedt */ public class GenerationUnit { private String outputPath; private int numUnits = 0; private int receivedUnits = 0; // It is useful for the generated code to be consistent. Therefore, the // ordering of generated code within this unit should be consistent. For this // we map units of generated code keyed by the Java class they come from, // using map implementations with ordered keys. private TreeMap<String, String> javadocBlocks = new TreeMap<>(); private TreeMap<String, String> nativeHeaderBlocks = new TreeMap<>(); private TreeMap<String, String> nativeImplementationBlocks = new TreeMap<>(); private ListMultimap<String, GeneratedType> generatedTypes = MultimapBuilder.treeKeys().arrayListValues().build(); private final String sourceName; private State state = State.ACTIVE; private boolean hasIncompleteProtocol = false; private boolean hasIncompleteImplementation = false; private boolean hasNullabilityAnnotations = false; private final Options options; private enum State { ACTIVE, // Initial state, still collecting CompilationUnits. FAILED, // One or more input files failed to compile. FINISHED // Finished, object is now invalid. } @VisibleForTesting public GenerationUnit(String sourceName, Options options) { this.sourceName = sourceName; this.options = options; } public static GenerationUnit newSingleExtractedJarEntryUnit( InputFile file, String sourceName, Options options) { GenerationUnit unit = new GenerationUnit(sourceName, options); if (options.getHeaderMap().useSourceDirectories()) { unit.useSourceDirectoryForOutput(file); } return unit; } public static GenerationUnit newSingleFileUnit(InputFile file, Options options) { GenerationUnit unit = new GenerationUnit(file.getOriginalLocation(), options); if (options.getHeaderMap().useSourceDirectories()) { unit.useSourceDirectoryForOutput(file); } return unit; } public static GenerationUnit newCombinedJarUnit(String filename, Options options) { String outputPath = filename; if (outputPath.lastIndexOf(File.separatorChar) < outputPath.lastIndexOf(".")) { outputPath = outputPath.substring(0, outputPath.lastIndexOf(".")); } GenerationUnit unit = new GenerationUnit(filename, options); unit.outputPath = outputPath; return unit; } /** * Gets the 'source name' of this GenerationUnit. Might not be a .java file, * but if given, should probably be an actual file somewhere, like a .jar. */ public String getSourceName() { return sourceName; } public boolean hasIncompleteProtocol() { return hasIncompleteProtocol; } public boolean hasIncompleteImplementation() { return hasIncompleteImplementation; } public boolean hasNullabilityAnnotations() { return hasNullabilityAnnotations; } public Collection<String> getJavadocBlocks() { return javadocBlocks.values(); } public Collection<String> getNativeHeaderBlocks() { return nativeHeaderBlocks.values(); } public Collection<String> getNativeImplementationBlocks() { return nativeImplementationBlocks.values(); } public Collection<GeneratedType> getGeneratedTypes() { return generatedTypes.values(); } /** * Increments the number of inputs for this GenerationUnit. This is called * for each new ProcessingContext created with this GenerationUnit. */ public void incrementInputs() { numUnits++; } public void addCompilationUnit(CompilationUnit unit) { assert state != State.FINISHED : "Adding to a finished GenerationUnit."; if (state != State.ACTIVE) { return; // Ignore any added units. } assert receivedUnits < numUnits; receivedUnits++; if (outputPath == null) { // The outputPath is only null for units derived from Java source files, // not source jars. Since a Java source can contain annotations that // generate other sources associated with it, the output path must be // determined from the initial source file. Since processor-generated // sources are appended to the list of source files, their units are // returned after the initial sources have been compiled. // // NOTE: THIS IS NOT THREADSAFE! It requires that all files in a batch // be compiled and translated as a single task. When we support // parallelization, each parallel task needs to be constrained this way. assert receivedUnits == 1; outputPath = options.getHeaderMap().getOutputPath(unit); } hasIncompleteProtocol = hasIncompleteProtocol || unit.hasIncompleteProtocol(); hasIncompleteImplementation = hasIncompleteImplementation || unit.hasIncompleteImplementation(); if (unit.hasNullabilityAnnotations()) { hasNullabilityAnnotations = true; } String qualifiedMainType = TreeUtil.getQualifiedMainTypeName(unit); addPackageJavadoc(unit, qualifiedMainType); addNativeBlocks(unit, qualifiedMainType); for (AbstractTypeDeclaration type : unit.getTypes()) { generatedTypes.put(qualifiedMainType, GeneratedType.fromTypeDeclaration(type)); if (ElementUtil.isEnum(type.getTypeElement())) { hasNullabilityAnnotations = true; } } } // Collect javadoc from the package declarations to display in the header. private void addPackageJavadoc(CompilationUnit unit, String qualifiedMainType) { Javadoc javadoc = unit.getPackage().getJavadoc(); if (javadoc == null) { return; } SourceBuilder builder = new SourceBuilder(false); JavadocGenerator.printDocComment(builder, javadoc); javadocBlocks.put(qualifiedMainType, builder.toString()); } private void addNativeBlocks(CompilationUnit unit, String qualifiedMainType) { SourceBuilder headerBuilder = new SourceBuilder(false); SourceBuilder implBuilder = new SourceBuilder(false); for (NativeDeclaration decl : unit.getNativeBlocks()) { String headerCode = decl.getHeaderCode(); if (headerCode != null) { headerBuilder.newline(); headerBuilder.println(headerBuilder.reindent(headerCode)); } String implCode = decl.getImplementationCode(); if (implCode != null) { implBuilder.newline(); implBuilder.println(implBuilder.reindent(implCode)); } } if (headerBuilder.length() > 0) { nativeHeaderBlocks.put(qualifiedMainType, headerBuilder.toString()); } if (implBuilder.length() > 0) { nativeImplementationBlocks.put(qualifiedMainType, implBuilder.toString()); } } private void useSourceDirectoryForOutput(InputFile sourceFile) { String sourceDir = sourceFile.getUnitName(); sourceDir = sourceDir.substring(0, sourceDir.lastIndexOf(".java")); outputPath = sourceDir; } public boolean isFullyParsed() { return receivedUnits == numUnits; } public void failed() { state = State.FAILED; } public void finished() { state = State.FINISHED; } /** * Gets the output path for this GenerationUnit. */ @Nullable public String getOutputPath() { return outputPath; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (sourceName != null) { sb.append(sourceName); sb.append(' '); } sb.append(generatedTypes); return sb.toString(); } public Options options() { return options; } }