/*
* 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.base.Preconditions;
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.util.ErrorUtil;
import com.google.devtools.j2objc.util.FileUtil;
import com.google.devtools.j2objc.util.Parser;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* Class for processing GenerationUnits in minimum increments of one GenerationUnit.
*
* @author Tom Ball, Keith Stanger, Mike Thvedt
*/
abstract class FileProcessor {
private static final Logger logger = Logger.getLogger(FileProcessor.class.getName());
private final Parser parser;
protected final BuildClosureQueue closureQueue;
protected final Options options;
private final int batchSize;
private final Set<ProcessingContext> batchInputs = new HashSet<>();
private final boolean doBatching;
public FileProcessor(Parser parser) {
this.parser = Preconditions.checkNotNull(parser);
this.options = parser.options();
batchSize = options.batchTranslateMaximum();
doBatching = batchSize > 0;
if (options.buildClosure()) {
// Should be an error if the user specifies this with --build-closure
assert !options.getHeaderMap().useSourceDirectories();
closureQueue = new BuildClosureQueue(options);
} else {
closureQueue = null;
}
}
public void processInputs(Iterable<ProcessingContext> inputs) {
for (ProcessingContext input : inputs) {
processInput(input);
}
processBatch();
}
public void processBuildClosureDependencies() {
if (closureQueue != null) {
while (true) {
InputFile file = closureQueue.getNextFile();
if (file == null) {
processBatch();
file = closureQueue.getNextFile();
}
if (file == null) {
break;
}
processInput(ProcessingContext.fromFile(file, options));
}
}
}
private void processInput(ProcessingContext input) {
try {
InputFile file = input.getFile();
if (isBatchable(file)) {
batchInputs.add(input);
if (batchInputs.size() == batchSize) {
processBatch();
}
return;
}
logger.finest("parsing " + file);
CompilationUnit compilationUnit = parser.parse(file);
if (compilationUnit == null) {
handleError(input);
return;
}
processCompiledSource(input, compilationUnit);
} catch (RuntimeException | Error e) {
ErrorUtil.fatalError(e, input.getOriginalSourcePath());
}
}
protected boolean isBatchable(InputFile file) {
return doBatching && file.getAbsolutePath().endsWith(".java");
}
private void processBatch() {
if (batchInputs.isEmpty()) {
return;
}
List<String> paths = Lists.newArrayListWithCapacity(batchInputs.size());
final Map<String, ProcessingContext> inputMap = new CanonicalPathMap(batchInputs.size());
for (ProcessingContext input : batchInputs) {
String path = input.getFile().getAbsolutePath();
paths.add(path);
inputMap.put(path, input);
}
Parser.Handler handler = new Parser.Handler() {
@Override
public void handleParsedUnit(String path, CompilationUnit unit) {
ProcessingContext input = inputMap.get(path);
processCompiledSource(input, unit);
batchInputs.remove(input);
}
};
logger.finest("Processing batch of size " + batchInputs.size());
parser.parseFiles(paths, handler, options.getSourceVersion());
// Any remaining files in batchFiles has some kind of error.
for (ProcessingContext input : batchInputs) {
handleError(input);
}
batchInputs.clear();
}
private void processCompiledSource(ProcessingContext input,
com.google.devtools.j2objc.ast.CompilationUnit unit) {
InputFile file = input.getFile();
if (closureQueue != null) {
closureQueue.addProcessedName(FileUtil.getQualifiedMainTypeName(file, unit));
}
try {
processConvertedTree(input, unit);
} catch (Throwable t) {
// Report any uncaught exceptions.
ErrorUtil.fatalError(t, input.getOriginalSourcePath());
} finally {
unit.getEnv().reset();
}
}
protected abstract void processConvertedTree(
ProcessingContext input, com.google.devtools.j2objc.ast.CompilationUnit unit);
protected abstract void handleError(ProcessingContext input);
/**
* Maps processing contexts using their canonical paths. This allows a
* front-end to refer to a source file using a different but equivalent
* path, without changing what path was specified.
*/
@SuppressWarnings("serial")
private static class CanonicalPathMap extends HashMap<String, ProcessingContext> {
public CanonicalPathMap(int initialSize) {
super(initialSize);
}
@Override
public ProcessingContext get(Object key) {
return super.get(canonicalizePath((String) key));
}
@Override
public ProcessingContext put(String key, ProcessingContext value) {
return super.put(canonicalizePath((String) key), value);
}
private String canonicalizePath(String path) {
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
// Shouldn't happen, but returning the unchanged path is safe.
return path;
}
}
}
}