All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jamon.compiler.RecompilingTemplateManager Maven / Gradle / Ivy

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.jamon.compiler;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jamon.AbstractTemplateImpl;
import org.jamon.AbstractTemplateManager;
import org.jamon.AbstractTemplateProxy;
import org.jamon.IdentityTemplateReplacer;
import org.jamon.TemplateManager;
import org.jamon.TemplateReplacer;
import org.jamon.AbstractTemplateProxy.Intf;
import org.jamon.annotations.Template;
import org.jamon.api.SourceGenerator;
import org.jamon.api.TemplateSource;
import org.jamon.codegen.Analyzer;
import org.jamon.codegen.ImplGenerator;
import org.jamon.codegen.ProxyGenerator;
import org.jamon.codegen.TemplateDescriber;
import org.jamon.codegen.TemplateUnit;
import org.jamon.util.JavaCompiler;
import org.jamon.util.JavaCompilerFactory;
import org.jamon.util.StringUtils;
import org.jamon.util.WorkDirClassLoader;

/**
 * An implementation of the {@link TemplateManager} interface which supports dynamic regeneration
 * and recompilation of templates as they are changed, much as JSP does.
 * RecompilingTemplateManager instances are thread-safe. In your applications, you
 * generally want exactly one instance of a RecompilingTemplateManager (i.e. a singleton).
 * Configuration of a RecompilingTemplateManager occurs only at construction time, and
 * is determined by the RecompilingTemplateManager.Data object passed to the
 * constructor. The properties on the Data are:
 * 
    *
  • setSourceDir - determines where the RecompilingTemplateManager looks for * template source files. Default is the current directory, which is most likely unsuitable for most * situations. *
  • setWorkDir - determines where the generated Java source files corresponding to * templates are placed. Default is uniquely generated subdirectory under the directory specified by * the system property java.io.tmpdir. *
  • setJavaCompiler - determines what program to execute to compile the generated Java * source files. Default is bin/javac (Commands/javac on MacOS) under the * directory specified by the system property java.home. *
  • setClasspath - used to specify additional components to prepend to the classpath when * compiling generated Java source files. Default is null. *
  • setClassLoader - used to set the class loader explicitly. Default is use the class * loader of the RecompilingTemplateManager instance. *
*/ public class RecompilingTemplateManager extends AbstractTemplateManager { public static class Data { public Data setSourceDir(String sourceDir) { this.sourceDir = sourceDir; return this; } public String getSourceDir() { return sourceDir; } private String sourceDir; public Data setTemplateSource(TemplateSource templateSource) { this.templateSource = templateSource; return this; } public TemplateSource getTemplateSource() { return templateSource; } private TemplateSource templateSource; public Data setWorkDir(String workDir) { this.workDir = workDir; return this; } public String getWorkDir() { return workDir; } private String workDir; public Data setJavaCompiler(String javaCompiler) { this.javaCompiler = javaCompiler; return this; } public String getJavaCompiler() { return javaCompiler; } private String javaCompiler; public Data setClasspath(String classpath) { this.classpath = classpath; return this; } public String getClasspath() { return classpath; } private String classpath; public Data setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; return this; } public ClassLoader getClassLoader() { return classLoader; } private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); public Data setTemplateReplacer(TemplateReplacer templateReplacer) { this.templateReplacer = templateReplacer; return this; } public TemplateReplacer getTemplateReplacer() { return templateReplacer; } private TemplateReplacer templateReplacer; } public RecompilingTemplateManager() { this(new Data()); } public RecompilingTemplateManager(Data data) { super(data.getTemplateReplacer() == null ? IdentityTemplateReplacer.INSTANCE : data.getTemplateReplacer()); classLoader = data.classLoader == null ? getClass().getClassLoader() : data.classLoader; workDir = data.workDir == null ? getDefaultWorkDir() : data.workDir; javaCompiler = JavaCompilerFactory.makeCompiler(data, workDir, classLoader); if (TRACE) { trace("Java Compiler: " + javaCompiler.getClass().getSimpleName()); } if (data.templateSource != null) { templateSource = data.templateSource; } else { templateSource = new FileTemplateSource(data.sourceDir == null ? System.getProperty("user.dir") : data.sourceDir); } loader = AccessController.doPrivileged(new PrivilegedAction() { @Override public WorkDirClassLoader run() { return new WorkDirClassLoader(classLoader, workDir); } }); } @Override protected Intf constructImplFromReplacedProxy(AbstractTemplateProxy replacedProxy) { return replacedProxy.constructImpl(getImplClass(replacedProxy.getClass())); } /** * Given a template path, return a proxy for that template. * * @param path the path to the template * @return a Template proxy instance **/ @Override public AbstractTemplateProxy constructProxy(String path) { try { // need to do this first to check dependencies if so enabled return getProxyClass(path).getConstructor(new Class[] { TemplateManager.class }) .newInstance(new Object[] { this }); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } public static void trace(String p_message) { System.err.println(p_message); } private static String getDefaultWorkDir() { File workDir = new File(System.getProperty("java.io.tmpdir"), "jamon" + (new java.util.Random().nextInt(100000000)) + ".tmp"); if (!workDir.mkdirs()) { throw new RuntimeException("Unable to create default work directory " + workDir); } workDir.deleteOnExit(); return workDir.toString(); } private Class getImplClass( Class proxyClass) { return getTemplateClass( StringUtils.classToTemplatePath(proxyClass), proxyClass.getName() + "Impl") .asSubclass(AbstractTemplateImpl.class); } private Class getProxyClass(String path) { return getTemplateClass( path, StringUtils.templatePathToClassName(path)).asSubclass(AbstractTemplateProxy.class); } private Class getTemplateClass(String path, String className) { try { try { ensureUpToDate(path, new TemplateDescriber(templateSource, loader)); return loader.loadClass(className); } catch (ClassNotFoundException e) { if (!templateSource.available(path)) { throw new RuntimeException("The template at path " + path + " could not be found"); } else { throw new RuntimeException(e); } } } catch (IOException e) { throw new RuntimeException(e); } } private final TemplateSource templateSource; private final String workDir; private final ClassLoader classLoader; private final JavaCompiler javaCompiler; private final WorkDirClassLoader loader; private String prefix() { return workDir; } private String javaImpl(String path) { return prefix() + path + "Impl.java"; } private String classImpl(String path) { return prefix() + path + "Impl.class"; } private String javaIntf(String p_path) { return prefix() + p_path + ".java"; } private synchronized void ensureUpToDate(String startingPath, TemplateDescriber describer) throws IOException { Collection seen = new HashSet(); Collection outOfDateJavaFiles = new HashSet(); List workQueue = new LinkedList(); workQueue.add(startingPath); while (!workQueue.isEmpty()) { String path = workQueue.remove(0); if (TRACE) { trace("processing " + path); } seen.add(path); if (!templateSource.available(path)) { if (TRACE) { trace(path + " source not found; assume class exists"); } continue; } boolean intfGenerated = false; long modTime = templateSource.lastModified(path); String intfFileName = javaIntf(path); File intfFile = new File(intfFileName); if (intfFile.lastModified() < modTime) { TemplateUnit templateUnit = new Analyzer(path, describer).analyze(); String signature = templateUnit.getSignature(); if (isIntfChanged(path, signature, classLoader)) { if (isIntfChanged(path, signature, loader)) { generateIntf(path, describer, templateUnit); outOfDateJavaFiles.add(intfFileName); intfGenerated = true; } } else { intfFile.delete(); deleteClassFilesFor(path); } } File jm = new File(javaImpl(path)); long ts = jm.lastModified(); if (jm.lastModified() < modTime) { dependencyCache.put(path, new DependencyEntry(generateImpl(path, describer))); ts = System.currentTimeMillis(); } if (new File(classImpl(path)).lastModified() < ts || intfGenerated) { outOfDateJavaFiles.add(javaImpl(path)); } DependencyEntry d = dependencyCache.get(path); if (d == null || d.lastUpdated() < modTime) { d = new DependencyEntry(computeDependencies(path, describer)); dependencyCache.put(path, d); } for (String dp : d.getDependencies()) { if (!seen.contains(dp)) { workQueue.add(dp); } } } if (!outOfDateJavaFiles.isEmpty()) { String errors = compile(outOfDateJavaFiles); loader.invalidate(); if (errors != null) { throw new TemplateCompilationException(errors); } } } private void deleteClassFilesFor(String path) { int i = path.lastIndexOf('/'); String templateName = i < 0 ? path : path.substring(i + 1); File dir = new File(new File(workDir), StringUtils.templatePathToFileDir(path)); String[] files = dir.list(); if (files != null) { for (int j = 0; j < files.length; ++j) { if (StringUtils.isGeneratedClassFilename(templateName, files[j])) { new File(dir, files[j]).delete(); // FIXME: error checking? } } } } private File getWriteableFile(String filename) { File file = new File(filename); File parent = file.getParentFile(); if (parent != null) { parent.mkdirs(); } return file; } private void generateSource(String filename, SourceGenerator sourceGenerator) throws IOException { File javaFile = getWriteableFile(filename); FileOutputStream out = new FileOutputStream(javaFile); try { sourceGenerator.generateSource(out); out.close(); } catch (IOException e) { out.close(); javaFile.delete(); throw e; } } /** * @return dependencies */ private Collection generateImpl(String path, TemplateDescriber describer) throws IOException { if (TRACE) { trace("generating impl for " + path); } TemplateUnit templateUnit = new Analyzer(path, describer).analyze(); generateSource(javaImpl(path), new ImplGenerator(describer, templateUnit)); return templateUnit.getTemplateDependencies(); } private void generateIntf(String path, TemplateDescriber describer, TemplateUnit templateUnit) throws IOException { if (TRACE) { trace("generating intf for " + path); } generateSource(javaIntf(path), new ProxyGenerator(describer, templateUnit)); } private String getIntfSignatureFromClass(String path, ClassLoader loader) { if (TRACE) { trace("Looking for signature of " + StringUtils.templatePathToClassName(path)); } try { return loader.loadClass(StringUtils.templatePathToClassName(path)).getAnnotation( Template.class).signature(); } catch (ClassNotFoundException e) { return null; } } private boolean isIntfChanged(String path, String signature, ClassLoader loader) { return !signature.equals(getIntfSignatureFromClass(path, loader)); } private String compile(Collection sourceFiles) { if (sourceFiles.isEmpty()) { return null; } StringBuilder buf = new StringBuilder(); buf.append("compiling: "); StringUtils.commaJoin(buf, sourceFiles); if (TRACE) { trace(buf.toString()); } return javaCompiler.compile(sourceFiles.toArray(new String[sourceFiles.size()])); } private Collection computeDependencies(String path, TemplateDescriber describer) throws IOException { if (TRACE) { trace("computing dependencies for " + path); } return new Analyzer(path, describer).analyze().getTemplateDependencies(); } private Map dependencyCache = new HashMap(); private static class DependencyEntry { DependencyEntry(Collection dependencies) { this.dependencies = dependencies; lastUpdated = System.currentTimeMillis(); } Collection dependencies; long lastUpdated; Collection getDependencies() { return dependencies; } long lastUpdated() { return lastUpdated; } } // FIXME - use JavaLogging. public static final boolean TRACE = Boolean.valueOf( System.getProperty(RecompilingTemplateManager.class.getName() + ".trace")).booleanValue(); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy