156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/*
256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Copyright (C) 2010 Google Inc.
356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Licensed under the Apache License, Version 2.0 (the "License");
556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * you may not use this file except in compliance with the License.
656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * You may obtain a copy of the License at
756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * http://www.apache.org/licenses/LICENSE-2.0
956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
1056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Unless required by applicable law or agreed to in writing, software
1156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * distributed under the License is distributed on an "AS IS" BASIS,
1256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * See the License for the specific language governing permissions and
1456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * limitations under the License.
1556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
1656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpackage com.google.clearsilver.jsilver.compiler;
1856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
2056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.autoescape.EscapeMode;
2156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.functions.FunctionExecutor;
2256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.interpreter.TemplateFactory;
2356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
2456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
2556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
2656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.template.Template;
2756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.clearsilver.jsilver.template.TemplateLoader;
2856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
2956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.io.StringWriter;
3056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.List;
3156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.logging.Level;
3256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.logging.Logger;
3356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
3456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport javax.tools.Diagnostic;
3556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport javax.tools.DiagnosticCollector;
3656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport javax.tools.JavaFileObject;
3756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
3856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/**
3956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Takes a template AST and compiles it into a Java class, which executes much faster than the
4056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * intepreter.
4156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
4256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpublic class TemplateCompiler implements DelegatingTemplateLoader {
4356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final Logger logger = Logger.getLogger(TemplateCompiler.class.getName());
4556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final String PACKAGE_NAME = "com.google.clearsilver.jsilver.compiler";
4756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // Because each template is isolated in its own ClassLoader, it doesn't
4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // matter if there are naming clashes between templates.
5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final String CLASS_NAME = "$CompiledTemplate";
5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private final TemplateFactory templateFactory;
5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private final FunctionExecutor globalFunctionExecutor;
5556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private final AutoEscapeOptions autoEscapeOptions;
5656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private TemplateLoader templateLoaderDelegate = this;
5756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public TemplateCompiler(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor,
5956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      AutoEscapeOptions autoEscapeOptions) {
6056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    this.templateFactory = templateFactory;
6156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    this.globalFunctionExecutor = globalFunctionExecutor;
6256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    this.autoEscapeOptions = autoEscapeOptions;
6356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
6456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
6556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  @Override
6656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    this.templateLoaderDelegate = templateLoaderDelegate;
6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  @Override
7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return compile(templateFactory.find(templateName, resourceLoader, escapeMode), templateName,
7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        escapeMode);
7456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
7556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
7656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  @Override
7756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public Template createTemp(String name, String content, EscapeMode escapeMode) {
7856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return compile(templateFactory.createTemp(content, escapeMode), name, escapeMode);
7956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
8056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
8156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
8256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Compile AST into Java class.
8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param ast A template AST.
8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param templateName Name of template (e.g. "foo.cs"). Used for error reporting. May be null,
8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return Template that can be executed (again and again).
8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private Template compile(TemplateSyntaxTree ast, String templateName, EscapeMode mode) {
8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    CharSequence javaSource = translateAstToJavaSource(ast, mode);
9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    String errorMessage = "Could not compile template: " + templateName;
9256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Class<?> templateClass = compileAndLoad(javaSource, errorMessage);
9356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
9456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    try {
9556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      BaseCompiledTemplate compiledTemplate = (BaseCompiledTemplate) templateClass.newInstance();
9656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      compiledTemplate.setFunctionExecutor(globalFunctionExecutor);
9756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      compiledTemplate.setTemplateName(templateName);
9856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      compiledTemplate.setTemplateLoader(templateLoaderDelegate);
9956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      compiledTemplate.setEscapeMode(mode);
10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      compiledTemplate.setAutoEscapeOptions(autoEscapeOptions);
10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return compiledTemplate;
10256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } catch (InstantiationException e) {
10356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new Error(e); // Should not be possible. Throw Error if it does.
10456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } catch (IllegalAccessException e) {
10556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new Error(e); // Should not be possible. Throw Error if it does.
10656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
10756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
10856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
10956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private CharSequence translateAstToJavaSource(TemplateSyntaxTree ast, EscapeMode mode) {
11056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringWriter sourceBuffer = new StringWriter(256);
11156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    boolean propagateStatus =
11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        autoEscapeOptions.getPropagateEscapeStatus() && mode.isAutoEscapingMode();
11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    ast.apply(new TemplateTranslator(PACKAGE_NAME, CLASS_NAME, sourceBuffer, propagateStatus));
11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringBuffer javaSource = sourceBuffer.getBuffer();
11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    logger.log(Level.FINEST, "Compiled template:\n{0}", javaSource);
11656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return javaSource;
11756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
11856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
11956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private Class<?> compileAndLoad(CharSequence javaSource, String errorMessage)
12056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws JSilverCompilationException {
12156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Need a parent class loader to load dependencies from.
12256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // This does not use any libraries outside of JSilver (e.g. custom user
12356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // libraries), so using this class's ClassLoader should be fine.
12456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    ClassLoader parentClassLoader = getClass().getClassLoader();
12556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Collect any compiler errors/warnings.
12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    DiagnosticCollector<JavaFileObject> diagnosticCollector =
12856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        new DiagnosticCollector<JavaFileObject>();
12956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
13056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    try {
13156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Magical ClassLoader that compiles source code on the fly.
13256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      CompilingClassLoader templateClassLoader =
13356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          new CompilingClassLoader(parentClassLoader, CLASS_NAME, javaSource, diagnosticCollector);
13456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return templateClassLoader.loadClass(PACKAGE_NAME + "." + CLASS_NAME);
13556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    } catch (Exception e) {
13656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // Ordinarily, this shouldn't happen as the code is generated. However,
13756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // in case there's a bug in JSilver, it will be helpful to have as much
13856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      // info as possible in the exception to diagnose the problem.
13956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throwExceptionWithLotsOfDiagnosticInfo(javaSource, errorMessage, diagnosticCollector
14056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson          .getDiagnostics(), e);
14156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return null; // Keep compiler happy.
14256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
14356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
14456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
14556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private void throwExceptionWithLotsOfDiagnosticInfo(CharSequence javaSource, String errorMessage,
14656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      List<Diagnostic<? extends JavaFileObject>> diagnostics, Exception cause)
14756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throws JSilverCompilationException {
14856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Create exception with lots of info in it.
14956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringBuilder message = new StringBuilder(errorMessage).append('\n');
15056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append("------ Source code ------\n").append(javaSource);
15156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append("------ Compiler messages ------\n");
15256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
15356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      message.append(diagnostic).append('\n');
15456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
15556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    message.append("------ ------\n");
15656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    throw new JSilverCompilationException(message.toString(), cause);
15756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
15856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson}
159