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 17package com.google.clearsilver.jsilver.compiler; 18 19import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; 20import com.google.clearsilver.jsilver.autoescape.EscapeMode; 21import com.google.clearsilver.jsilver.functions.FunctionExecutor; 22import com.google.clearsilver.jsilver.interpreter.TemplateFactory; 23import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; 24import com.google.clearsilver.jsilver.syntax.TemplateSyntaxTree; 25import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader; 26import com.google.clearsilver.jsilver.template.Template; 27import com.google.clearsilver.jsilver.template.TemplateLoader; 28 29import java.io.StringWriter; 30import java.util.List; 31import java.util.logging.Level; 32import java.util.logging.Logger; 33 34import javax.tools.Diagnostic; 35import javax.tools.DiagnosticCollector; 36import 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 */ 42public 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