Home | History | Annotate | Download | only in compiler
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.clearsilver.jsilver.compiler;
     18 
     19 import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
     20 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
     21 import com.google.clearsilver.jsilver.functions.FunctionExecutor;
     22 import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
     23 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
     24 import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree;
     25 import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
     26 import com.google.clearsilver.jsilver.template.Template;
     27 import com.google.clearsilver.jsilver.template.TemplateLoader;
     28 
     29 import java.io.StringWriter;
     30 import java.util.List;
     31 import java.util.logging.Level;
     32 import java.util.logging.Logger;
     33 
     34 import javax.tools.Diagnostic;
     35 import javax.tools.DiagnosticCollector;
     36 import javax.tools.JavaFileObject;
     37 
     38 /**
     39  * Takes a template AST and compiles it into a Java class, which executes much faster than the
     40  * intepreter.
     41  */
     42 public class TemplateCompiler implements DelegatingTemplateLoader {
     43 
     44   private static final Logger logger = Logger.getLogger(TemplateCompiler.class.getName());
     45 
     46   private static final String PACKAGE_NAME = "com.google.clearsilver.jsilver.compiler";
     47 
     48   // Because each template is isolated in its own ClassLoader, it doesn't
     49   // matter if there are naming clashes between templates.
     50   private static final String CLASS_NAME = "$CompiledTemplate";
     51 
     52   private final TemplateFactory templateFactory;
     53 
     54   private final FunctionExecutor globalFunctionExecutor;
     55   private final AutoEscapeOptions autoEscapeOptions;
     56   private TemplateLoader templateLoaderDelegate = this;
     57 
     58   public TemplateCompiler(TemplateFactory templateFactory, FunctionExecutor globalFunctionExecutor,
     59       AutoEscapeOptions autoEscapeOptions) {
     60     this.templateFactory = templateFactory;
     61     this.globalFunctionExecutor = globalFunctionExecutor;
     62     this.autoEscapeOptions = autoEscapeOptions;
     63   }
     64 
     65   @Override
     66   public void setTemplateLoaderDelegate(TemplateLoader templateLoaderDelegate) {
     67     this.templateLoaderDelegate = templateLoaderDelegate;
     68   }
     69 
     70   @Override
     71   public Template load(String templateName, ResourceLoader resourceLoader, EscapeMode escapeMode) {
     72     return compile(templateFactory.find(templateName, resourceLoader, escapeMode), templateName,
     73         escapeMode);
     74   }
     75 
     76   @Override
     77   public Template createTemp(String name, String content, EscapeMode escapeMode) {
     78     return compile(templateFactory.createTemp(content, escapeMode), name, escapeMode);
     79   }
     80 
     81   /**
     82    * Compile AST into Java class.
     83    *
     84    * @param ast A template AST.
     85    * @param templateName Name of template (e.g. "foo.cs"). Used for error reporting. May be null,
     86    * @return Template that can be executed (again and again).
     87    */
     88   private Template compile(TemplateSyntaxTree ast, String templateName, EscapeMode mode) {
     89     CharSequence javaSource = translateAstToJavaSource(ast, mode);
     90 
     91     String errorMessage = "Could not compile template: " + templateName;
     92     Class<?> templateClass = compileAndLoad(javaSource, errorMessage);
     93 
     94     try {
     95       BaseCompiledTemplate compiledTemplate = (BaseCompiledTemplate) templateClass.newInstance();
     96       compiledTemplate.setFunctionExecutor(globalFunctionExecutor);
     97       compiledTemplate.setTemplateName(templateName);
     98       compiledTemplate.setTemplateLoader(templateLoaderDelegate);
     99       compiledTemplate.setEscapeMode(mode);
    100       compiledTemplate.setAutoEscapeOptions(autoEscapeOptions);
    101       return compiledTemplate;
    102     } catch (InstantiationException e) {
    103       throw new Error(e); // Should not be possible. Throw Error if it does.
    104     } catch (IllegalAccessException e) {
    105       throw new Error(e); // Should not be possible. Throw Error if it does.
    106     }
    107   }
    108 
    109   private CharSequence translateAstToJavaSource(TemplateSyntaxTree ast, EscapeMode mode) {
    110     StringWriter sourceBuffer = new StringWriter(256);
    111     boolean propagateStatus =
    112         autoEscapeOptions.getPropagateEscapeStatus() && mode.isAutoEscapingMode();
    113     ast.apply(new TemplateTranslator(PACKAGE_NAME, CLASS_NAME, sourceBuffer, propagateStatus));
    114     StringBuffer javaSource = sourceBuffer.getBuffer();
    115     logger.log(Level.FINEST, "Compiled template:\n{0}", javaSource);
    116     return javaSource;
    117   }
    118 
    119   private Class<?> compileAndLoad(CharSequence javaSource, String errorMessage)
    120       throws JSilverCompilationException {
    121     // Need a parent class loader to load dependencies from.
    122     // This does not use any libraries outside of JSilver (e.g. custom user
    123     // libraries), so using this class's ClassLoader should be fine.
    124     ClassLoader parentClassLoader = getClass().getClassLoader();
    125 
    126     // Collect any compiler errors/warnings.
    127     DiagnosticCollector<JavaFileObject> diagnosticCollector =
    128         new DiagnosticCollector<JavaFileObject>();
    129 
    130     try {
    131       // Magical ClassLoader that compiles source code on the fly.
    132       CompilingClassLoader templateClassLoader =
    133           new CompilingClassLoader(parentClassLoader, CLASS_NAME, javaSource, diagnosticCollector);
    134       return templateClassLoader.loadClass(PACKAGE_NAME + "." + CLASS_NAME);
    135     } catch (Exception e) {
    136       // Ordinarily, this shouldn't happen as the code is generated. However,
    137       // in case there's a bug in JSilver, it will be helpful to have as much
    138       // info as possible in the exception to diagnose the problem.
    139       throwExceptionWithLotsOfDiagnosticInfo(javaSource, errorMessage, diagnosticCollector
    140           .getDiagnostics(), e);
    141       return null; // Keep compiler happy.
    142     }
    143   }
    144 
    145   private void throwExceptionWithLotsOfDiagnosticInfo(CharSequence javaSource, String errorMessage,
    146       List<Diagnostic<? extends JavaFileObject>> diagnostics, Exception cause)
    147       throws JSilverCompilationException {
    148     // Create exception with lots of info in it.
    149     StringBuilder message = new StringBuilder(errorMessage).append('\n');
    150     message.append("------ Source code ------\n").append(javaSource);
    151     message.append("------ Compiler messages ------\n");
    152     for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
    153       message.append(diagnostic).append('\n');
    154     }
    155     message.append("------ ------\n");
    156     throw new JSilverCompilationException(message.toString(), cause);
    157   }
    158 }
    159