package play.templates;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import play.Logger;
import play.Play;
import play.vfs.VirtualFile;
import play.exceptions.TemplateCompilationException;
import play.exceptions.TemplateNotFoundException;
/**
* Load templates
*/
public class TemplateLoader {
protected static Map<String, BaseTemplate> templates = new HashMap<String, BaseTemplate>();
/**
* See getUniqueNumberForTemplateFile() for more info
*/
private static AtomicLong nextUniqueNumber = new AtomicLong(1000);//we start on 1000
private static Map<String, String> templateFile2UniqueNumber = new HashMap<String, String>();
/**
* All loaded templates is cached in the templates-list using a key.
* This key is included as part of the classname for the generated class for a specific template.
* The key is included in the classname to make it possible to resolve the original template-file
* from the classname, when creating cleanStackTrace
*
* This method returns a unique representation of the path which is usable as part of a classname
*
* @param path
* @return
*/
public static String getUniqueNumberForTemplateFile(String path) {
//a path cannot be a valid classname so we have to convert it somehow.
//If we did some encoding on the path, the result would be at least as long as the path.
//Therefor we assign a unique number to each path the first time we see it, and store it..
//This way, all seen paths gets a unique number. This number is our UniqueValidClassnamePart..
String uniqueNumber = templateFile2UniqueNumber.get(path);
if (uniqueNumber == null) {
//this is the first time we see this path - must assign a unique number to it.
uniqueNumber = Long.toString(nextUniqueNumber.getAndIncrement());
templateFile2UniqueNumber.put(path, uniqueNumber);
}
return uniqueNumber;
}
/**
* Load a template from a virtual file
* @param file A VirtualFile
* @return The executable template
*/
public static Template load(VirtualFile file) {
// Try with plugin
Template pluginProvided = Play.pluginCollection.loadTemplate(file);
if (pluginProvided != null) {
return pluginProvided;
}
// Use default engine
final String key = getUniqueNumberForTemplateFile(file.relativePath());
if (!templates.containsKey(key) || templates.get(key).compiledTemplate == null) {
if (Play.usePrecompiled) {
BaseTemplate template = new GroovyTemplate(file.relativePath().replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent"), file.contentAsString());
try {
template.loadPrecompiled();
templates.put(key, template);
return template;
} catch(Exception e) {
Logger.warn("Precompiled template %s not found, trying to load it dynamically...", file.relativePath());
}
}
BaseTemplate template = new GroovyTemplate(file.relativePath(), file.contentAsString());
if (template.loadFromCache()) {
templates.put(key, template);
} else {
templates.put(key, new GroovyTemplateCompiler().compile(file));
}
} else {
BaseTemplate template = templates.get(key);
if (Play.mode == Play.Mode.DEV && template.timestamp < file.lastModified()) {
templates.put(key, new GroovyTemplateCompiler().compile(file));
}
}
if (templates.get(key) == null) {
throw new TemplateNotFoundException(file.relativePath());
}
return templates.get(key);
}
/**
* Load a template from a String
* @param key A unique identifier for the template, used for retreiving a cached template
* @param source The template source
* @return A Template
*/
public static BaseTemplate load(String key, String source) {
if (!templates.containsKey(key) || templates.get(key).compiledTemplate == null) {
BaseTemplate template = new GroovyTemplate(key, source);
if (template.loadFromCache()) {
templates.put(key, template);
} else {
templates.put(key, new GroovyTemplateCompiler().compile(template));
}
} else {
BaseTemplate template = new GroovyTemplate(key, source);
if (Play.mode == Play.Mode.DEV) {
templates.put(key, new GroovyTemplateCompiler().compile(template));
}
}
if (templates.get(key) == null) {
throw new TemplateNotFoundException(key);
}
return templates.get(key);
}
/**
* Clean the cache for that key
* Then load a template from a String
* @param key A unique identifier for the template, used for retreiving a cached template
* @param source The template source
* @return A Template
*/
public static BaseTemplate load(String key, String source, boolean reload) {
cleanCompiledCache(key);
return load(key, source);
}
/**
* Load template from a String, but don't cache it
* @param source The template source
* @return A Template
*/
public static BaseTemplate loadString(String source) {
BaseTemplate template = new GroovyTemplate(source);
return new GroovyTemplateCompiler().compile(template);
}
/**
* Cleans the cache for all templates
*/
public static void cleanCompiledCache() {
templates.clear();
}
/**
* Cleans the specified key from the cache
* @param key The template key
*/
public static void cleanCompiledCache(String key) {
templates.remove(key);
}
/**
* Load a template
* @param path The path of the template (ex: Application/index.html)
* @return The executable template
*/
public static Template load(String path) {
Template template = null;
for (VirtualFile vf : Play.templatesPath) {
if (vf == null) {
continue;
}
VirtualFile tf = vf.child(path);
if (tf.exists()) {
template = TemplateLoader.load(tf);
break;
}
}
/*
if (template == null) {
//When using the old 'key = (file.relativePath().hashCode() + "").replace("-", "M");',
//the next line never return anything, since all values written to templates is using the
//above key.
//when using just file.relativePath() as key, the next line start returning stuff..
//therefor I have commented it out.
template = templates.get(path);
}
*/
//TODO: remove ?
if (template == null) {
VirtualFile tf = Play.getVirtualFile(path);
if (tf != null && tf.exists()) {
template = TemplateLoader.load(tf);
} else {
throw new TemplateNotFoundException(path);
}
}
return template;
}
/**
* List all found templates
* @return A list of executable templates
*/
public static List<Template> getAllTemplate() {
List<Template> res = new ArrayList<Template>();
for (VirtualFile virtualFile : Play.templatesPath) {
scan(res, virtualFile);
}
for (VirtualFile root : Play.roots) {
VirtualFile vf = root.child("conf/routes");
if (vf != null && vf.exists()) {
Template template = load(vf);
if (template != null) {
template.compile();
}
}
}
return res;
}
private static void scan(List<Template> templates, VirtualFile current) {
if (!current.isDirectory() && !current.getName().startsWith(".") && !current.getName().endsWith(".scala.html")) {
long start = System.currentTimeMillis();
Template template = load(current);
if (template != null) {
try {
template.compile();
if (Logger.isTraceEnabled()) {
Logger.trace("%sms to load %s", System.currentTimeMillis() - start, current.getName());
}
} catch (TemplateCompilationException e) {
Logger.error("Template %s does not compile at line %d", e.getTemplate().name, e.getLineNumber());
throw e;
}
templates.add(template);
}
} else if (current.isDirectory() && !current.getName().startsWith(".")) {
for (VirtualFile virtualFile : current.list()) {
scan(templates, virtualFile);
}
}
}
}