blob: 6e008cf499a2120914ea769088b745a7d7195c99 [file] [log] [blame]
/*
* Copyright (C) 2010 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.clearsilver.jsilver.compiler;
import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
import com.google.clearsilver.jsilver.autoescape.EscapeMode;
import com.google.clearsilver.jsilver.functions.FunctionExecutor;
import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
import com.google.clearsilver.jsilver.template.Template;
import com.google.clearsilver.jsilver.template.TemplateLoader;
import java.io.StringWriter;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;
/**
* Takes a template AST and compiles it into a Java class, which executes much faster than the
* intepreter.
*/
public class TemplateCompiler implements DelegatingTemplateLoader {
private static final Logger logger = Logger.getLogger(TemplateCompiler.class.getName());
private static final String PACKAGE_NAME = "com.google.clearsilver.jsilver.compiler";
// Because each template is isolated in its own ClassLoader, it doesn't
// matter if there are naming clashes between templates.
private static final String CLASS_NAME = "$CompiledTemplate";
private final TemplateFactory templateFactory;
private final FunctionExecutor globalFunctionExecutor;
private final AutoEscapeOptions autoEscapeOptions;
private TemplateLoader templateLoaderDelegate = this;
public TemplateCompiler(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor,
AutoEscapeOptions autoEscapeOptions) {
this.templateFactory = templateFactory;
this.globalFunctionExecutor = globalFunctionExecutor;
this.autoEscapeOptions = autoEscapeOptions;
}
@Override
public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
this.templateLoaderDelegate = templateLoaderDelegate;
}
@Override
public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
return compile(templateFactory.find(templateName, resourceLoader, escapeMode), templateName,
escapeMode);
}
@Override
public Template createTemp(String name, String content, EscapeMode escapeMode) {
return compile(templateFactory.createTemp(content, escapeMode), name, escapeMode);
}
/**
* Compile AST into Java class.
*
* @param ast A template AST.
* @param templateName Name of template (e.g. "foo.cs"). Used for error reporting. May be null,
* @return Template that can be executed (again and again).
*/
private Template compile(TemplateSyntaxTree ast, String templateName, EscapeMode mode) {
CharSequence javaSource = translateAstToJavaSource(ast, mode);
String errorMessage = "Could not compile template: " + templateName;
Class<?> templateClass = compileAndLoad(javaSource, errorMessage);
try {
BaseCompiledTemplate compiledTemplate = (BaseCompiledTemplate) templateClass.newInstance();
compiledTemplate.setFunctionExecutor(globalFunctionExecutor);
compiledTemplate.setTemplateName(templateName);
compiledTemplate.setTemplateLoader(templateLoaderDelegate);
compiledTemplate.setEscapeMode(mode);
compiledTemplate.setAutoEscapeOptions(autoEscapeOptions);
return compiledTemplate;
} catch (InstantiationException e) {
throw new Error(e); // Should not be possible. Throw Error if it does.
} catch (IllegalAccessException e) {
throw new Error(e); // Should not be possible. Throw Error if it does.
}
}
private CharSequence translateAstToJavaSource(TemplateSyntaxTree ast, EscapeMode mode) {
StringWriter sourceBuffer = new StringWriter(256);
boolean propagateStatus =
autoEscapeOptions.getPropagateEscapeStatus() && mode.isAutoEscapingMode();
ast.apply(new TemplateTranslator(PACKAGE_NAME, CLASS_NAME, sourceBuffer, propagateStatus));
StringBuffer javaSource = sourceBuffer.getBuffer();
logger.log(Level.FINEST, "Compiled template:\n{0}", javaSource);
return javaSource;
}
private Class<?> compileAndLoad(CharSequence javaSource, String errorMessage)
throws JSilverCompilationException {
// Need a parent class loader to load dependencies from.
// This does not use any libraries outside of JSilver (e.g. custom user
// libraries), so using this class's ClassLoader should be fine.
ClassLoader parentClassLoader = getClass().getClassLoader();
// Collect any compiler errors/warnings.
DiagnosticCollector<JavaFileObject> diagnosticCollector =
new DiagnosticCollector<JavaFileObject>();
try {
// Magical ClassLoader that compiles source code on the fly.
CompilingClassLoader templateClassLoader =
new CompilingClassLoader(parentClassLoader, CLASS_NAME, javaSource, diagnosticCollector);
return templateClassLoader.loadClass(PACKAGE_NAME + "." + CLASS_NAME);
} catch (Exception e) {
// Ordinarily, this shouldn't happen as the code is generated. However,
// in case there's a bug in JSilver, it will be helpful to have as much
// info as possible in the exception to diagnose the problem.
throwExceptionWithLotsOfDiagnosticInfo(javaSource, errorMessage, diagnosticCollector
.getDiagnostics(), e);
return null; // Keep compiler happy.
}
}
private void throwExceptionWithLotsOfDiagnosticInfo(CharSequence javaSource, String errorMessage,
List<Diagnostic<? extends JavaFileObject>> diagnostics, Exception cause)
throws JSilverCompilationException {
// Create exception with lots of info in it.
StringBuilder message = new StringBuilder(errorMessage).append('\n');
message.append("------ Source code ------\n").append(javaSource);
message.append("------ Compiler messages ------\n");
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
message.append(diagnostic).append('\n');
}
message.append("------ ------\n");
throw new JSilverCompilationException(message.toString(), cause);
}
}