/*
* Copyright (C) 2008 Google Inc.
*
* 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.gxp.compiler;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gxp.compiler.alerts.AlertPolicy;
import com.google.gxp.compiler.alerts.AlertSet;
import com.google.gxp.compiler.alerts.AlertSetBuilder;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.alerts.UniquifyingAlertSink;
import com.google.gxp.compiler.alerts.common.IOError;
import com.google.gxp.compiler.base.OutputLanguage;
import com.google.gxp.compiler.codegen.CodeGeneratorFactory;
import com.google.gxp.compiler.depend.DependencyGraph;
import com.google.gxp.compiler.dot.DotWriter;
import com.google.gxp.compiler.dot.GraphRenderer;
import com.google.gxp.compiler.dot.ReflectiveGraphRenderer;
import com.google.gxp.compiler.fs.FileRef;
import com.google.gxp.compiler.parser.Parser;
import com.google.gxp.compiler.parser.SaxXmlParser;
import com.google.gxp.compiler.parser.SourceEntityResolver;
import com.google.gxp.compiler.schema.BuiltinSchemaFactory;
import com.google.gxp.compiler.schema.DelegatingSchemaFactory;
import com.google.gxp.compiler.schema.FileBackedSchemaFactory;
import com.google.gxp.compiler.schema.SchemaFactory;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* The core GXP Compiler class. Takes a {@link Configuration} and can then
* be called to execute a compile cycle.
*/
public class Compiler {
private final ImmutableSet<FileRef> sourceFiles;
private final ImmutableSet<FileRef> schemaFiles;
private final ImmutableSet<OutputLanguage> outputLanguages;
private final long compilationVersion;
private final CodeGeneratorFactory codeGeneratorFactory;
private final ImmutableSet<FileRef> allowedOutputs;
private final FileRef dependencyFile;
private final FileRef propertiesFile;
private final AlertPolicy alertPolicy;
private final ImmutableSet<Phase> dotPhases;
private final SourceEntityResolver entityResolver;
public Compiler(Configuration config) throws InvalidConfigException {
// TODO(laurence): use config.isDebugEnabled()
validateAllowedOutputs(config.getAllowedOutputFiles(),
config.getSourceFiles(),
config.getOutputLanguages(),
config.getCompilationVersion());
sourceFiles = ImmutableSet.copyOf(config.getSourceFiles());
schemaFiles = ImmutableSet.copyOf(config.getSchemaFiles());
outputLanguages = ImmutableSet.copyOf(config.getOutputLanguages());
compilationVersion = config.getCompilationVersion();
codeGeneratorFactory = config.getCodeGeneratorFactory();
allowedOutputs = ImmutableSet.copyOf(config.getAllowedOutputFiles());
dependencyFile = config.getDependencyFile();
propertiesFile = config.getPropertiesFile();
alertPolicy = config.getAlertPolicy();
dotPhases = ImmutableSet.copyOf(config.getDotPhases());
entityResolver = config.getEntityResolver();
}
/**
* Executes compilation and returns the @code Alert}s generated by
* compile as an {@link AlertSet}
*/
public AlertSet call() {
AlertSetBuilder alertSetBuilder = new AlertSetBuilder();
call(alertSetBuilder);
return alertSetBuilder.buildAndClear();
}
/**
* Executes compilation and passes the {@code Alert}s generated by
* compile to the {@link AlertSink}
*/
public void call(AlertSink alertSink) {
// Make sure that any given alert is only sent to the sink once
alertSink = new UniquifyingAlertSink(alertSink);
// build up a schema factory
SchemaFactory schemaFactory = new DelegatingSchemaFactory(
new FileBackedSchemaFactory(alertSink, schemaFiles),
new BuiltinSchemaFactory(alertSink));
Parser parser = new Parser(schemaFactory, SaxXmlParser.INSTANCE, entityResolver);
CompilationManager manager = readCompilationManager();
CompilationSet.Builder compilationSetBuilder =
new CompilationSet.Builder(parser, codeGeneratorFactory, manager)
.setCompilationVersion(compilationVersion)
.setPropertiesFile(propertiesFile);
CompilationSet compilationSet = compilationSetBuilder.build(sourceFiles);
Predicate<FileRef> shouldCompileFilePredicate = allowedOutputs.isEmpty()
? Predicates.<FileRef>alwaysTrue()
: Predicates.<FileRef>in(allowedOutputs);
compilationSet.compile(alertSink, alertPolicy, outputLanguages, shouldCompileFilePredicate);
writeDotFiles(compilationSet, alertSink);
writeCompilationManager(new DependencyGraph(compilationSet));
}
private void writeDotFiles(CompilationSet compilationSet, AlertSink alertSink) {
List<CompilationUnit> compilationUnits = compilationSet.getCompilationUnits();
int i = 0;
for (Phase phase : Phase.values()) {
i++;
if (dotPhases.contains(phase)) {
String suffix = String.format(".%02d.%s.dot", i,
phase.name().toLowerCase().replace("_", "-"));
for (CompilationUnit compilationUnit : compilationUnits) {
FileRef fileRef = compilationUnit.getSourceFileRef().removeExtension().addSuffix(suffix);
try {
Writer writer = fileRef.openWriter(Charsets.US_ASCII);
try {
DotWriter out = new DotWriter(writer);
GraphRenderer<Object> renderer =
new ReflectiveGraphRenderer(phase.name().toLowerCase());
renderer.renderGraph(out, phase.getForest(compilationUnit).getChildren());
} finally {
writer.close();
}
} catch (IOException iox) {
alertSink.add(new IOError(fileRef, iox));
}
}
}
}
}
private CompilationManager readCompilationManager() {
CompilationManager manager = SimpleCompilationManager.INSTANCE;
if (dependencyFile != null) {
try {
ObjectInputStream ois = new ObjectInputStream(dependencyFile.openInputStream());
Object read = ois.readObject();
if (read instanceof CompilationManager) {
manager = (CompilationManager) read;
}
ois.close();
} catch (Exception e) {
// use the default, fresh manager
}
}
return manager;
}
private void writeCompilationManager(CompilationManager manager) {
if (dependencyFile != null) {
try {
ObjectOutputStream oos = new ObjectOutputStream(dependencyFile.openOutputStream());
oos.writeObject(manager);
oos.close();
} catch (IOException e) {
// Fail silently. The compilation manager is only an optimization, so
// it is ok if it gets lost
}
}
}
/**
* Checks that all of {@code allowedOutputs} can actually be created by the
* specified {@code CompilationUnit}s.
*
* @throws InvalidConfigException if impossible allowed output is found.
*/
private static void validateAllowedOutputs(Set<FileRef> allowedOutputs,
Iterable<FileRef> sourceFileRefs,
Iterable<OutputLanguage> outputLanguages,
long compilationVersion)
throws InvalidConfigException {
if (!allowedOutputs.isEmpty()) {
Set<FileRef> possibleOutputs = computePossibleOutputs(sourceFileRefs, outputLanguages,
compilationVersion);
List<String> impossibleOutputs = Lists.newArrayList();
for (FileRef allowedOutput : allowedOutputs) {
if (!possibleOutputs.contains(allowedOutput)) {
impossibleOutputs.add(allowedOutput.toFilename());
}
}
if (!impossibleOutputs.isEmpty()) {
Collections.sort(impossibleOutputs);
throw new InvalidConfigException(
"The following are listed as allowed output files but are not"
+ " possible given the specified inputs: "
+ Joiner.on(", ").join(impossibleOutputs));
}
}
}
/**
* Compute the outputs that are possible based on the input files and
* requested output languages.
*/
private static Set<FileRef> computePossibleOutputs(Iterable<FileRef> sourceFileRefs,
Iterable<OutputLanguage> outputLanguages,
long compilationVersion) {
Set<FileRef> result = Sets.newHashSet();
for (FileRef sourceFileRef : sourceFileRefs) {
for (OutputLanguage language : outputLanguages) {
String suffix = language.getSuffix(compilationVersion);
FileRef outputFileRef = sourceFileRef.removeExtension().addSuffix(suffix);
result.add(outputFileRef);
}
}
return result;
}
}