com.greenlaw110.rythm.internal.compiler.TemplateClass Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rythm-engine Show documentation
Show all versions of rythm-engine Show documentation
A strong typed high performance Java Template engine with .Net Razor like syntax
The newest version!
/*
* Copyright (C) 2013 The Rythm Engine project
* Gelin Luo
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.greenlaw110.rythm.internal.compiler;
import com.greenlaw110.rythm.Rythm;
import com.greenlaw110.rythm.RythmEngine;
import com.greenlaw110.rythm.exception.CompileException;
import com.greenlaw110.rythm.exception.RythmException;
import com.greenlaw110.rythm.extension.IByteCodeEnhancer;
import com.greenlaw110.rythm.extension.ICodeType;
import com.greenlaw110.rythm.internal.CodeBuilder;
import com.greenlaw110.rythm.internal.IDialect;
import com.greenlaw110.rythm.logger.ILogger;
import com.greenlaw110.rythm.logger.Logger;
import com.greenlaw110.rythm.resource.ITemplateResource;
import com.greenlaw110.rythm.resource.StringTemplateResource;
import com.greenlaw110.rythm.template.ITemplate;
import com.greenlaw110.rythm.template.TagBase;
import com.greenlaw110.rythm.template.TemplateBase;
import com.greenlaw110.rythm.utils.S;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Define the data structure hold template class/template src/generated java src
*/
public class TemplateClass {
private static final ILogger logger = Logger.get(TemplateClass.class);
/**
* Store root level template class, e.g. the one that is not an embedded class
*/
private TemplateClass root;
public TemplateClass root() {
return root;
}
private TemplateClass() {
}
private boolean inner = false;
public static TemplateClass createInnerClass(String className, byte[] byteCode, TemplateClass parent) {
TemplateClass tc = new TemplateClass();
tc.name = className;
tc.javaByteCode = byteCode;
//tc.enhancedByteCode = byteCode;
tc.inner = true;
tc.root = parent.root();
return tc;
}
public boolean isInner() {
return inner;
}
private RythmEngine engine = null;
private RythmEngine engine() {
return null == engine ? Rythm.engine() : engine;
}
/**
* The fully qualified class name
*/
private String name;
public String name0() {
return name;
}
public String name() {
return name;
}
public TemplateClass extendedTemplateClass;
private Set includedTemplateClasses = new HashSet();
public void addIncludeTemplateClass(TemplateClass tc) {
includedTemplateClasses.add(tc);
includeTagTypes.putAll(tc.includeTagTypes);
}
public String includeTemplateClassNames = null;
private static final String NO_INCLUDE_CLASS = "NO_INCLUDE_CLASS";
public String refreshIncludeTemplateClassNames() {
if (includedTemplateClasses.size() == 0) {
includeTemplateClassNames = NO_INCLUDE_CLASS;
return NO_INCLUDE_CLASS;
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (TemplateClass tc : includedTemplateClasses) {
if (!first) {
sb.append(",");
} else {
first = false;
}
sb.append(engine().resourceManager().getFullTagName(tc));
}
includeTemplateClassNames = sb.toString();
return sb.toString();
}
private Map includeTagTypes = new HashMap();
public void setTagType(String tagName, String type) {
includeTagTypes.put(tagName, type);
}
public boolean returnObject(String tagName) {
String retType = includeTagTypes.get(tagName);
if (null != retType) {
return !"void".equals(retType);
}
if (null != extendedTemplateClass) return extendedTemplateClass.returnObject(tagName);
return true;
}
public String serializeIncludeTagTypes() {
if (includeTagTypes.isEmpty()) return "";
StringBuilder sb = new StringBuilder();
boolean empty = true;
for (String tagName : includeTagTypes.keySet()) {
if (!empty) sb.append(";");
else empty = false;
sb.append(tagName).append(":").append(includeTagTypes.get(tagName));
}
return sb.toString();
}
public void deserializeIncludeTagTypes(String s) {
includeTagTypes = new HashMap();
if (S.isEmpty(s)) return;
String[] sa = s.split(";");
for (String s0 : sa) {
String[] sa0 = s0.split(":");
if (sa0.length != 2) throw new IllegalArgumentException("Unknown include tag types string: " + s);
includeTagTypes.put(sa0[0], sa0[1]);
}
}
/**
* If not null then this template is a tag
*/
public String tagName() {
return null != templateResource ? templateResource.tagName() : null;
}
transient private String fullName;
public void setFullName(String fn) {
fullName = fn;
}
public String getFullName() {
if (null == fullName) return tagName();
return fullName;
}
/**
* the template resource
*/
public ITemplateResource templateResource;
/**
* The template source
*/
public String getTemplateSource() {
return getTemplateSource(false);
}
public String getTemplateSource(boolean includeRoot) {
if (null != templateResource) return templateResource.asTemplateContent();
if (!includeRoot) return "";
TemplateClass parent = root;
while ((null != parent) && parent.isInner()) {
parent = parent.root;
}
return null == parent ? "" : parent.getTemplateSource();
}
/**
* Is this template resource coming from a literal String or from a loaded resource like file
*/
public boolean isStringTemplate() {
return templateResource instanceof StringTemplateResource;
}
/**
* The Java source
*/
public String javaSource;
/**
* The compiled byteCode
*/
public byte[] javaByteCode;
/**
* The enhanced byteCode
*/
public byte[] enhancedByteCode;
/**
* Store a list of import path, i.e. those imports ends with ".*"
*/
public Set importPaths;
/**
* The in JVM loaded class
*/
public Class javaClass;
/**
* The in JVM loaded package
*/
public Package javaPackage;
/**
* The code type could be HTML, JS, JSON etc
*/
public ICodeType codeType;
/**
* Is this class compiled
*/
boolean compiled;
/**
* Signatures checksum
*/
public int sigChecksum;
/**
* Mark if this is a valid Rythm Template
*/
public boolean isValid = true;
/**
* CodeBuilder to generate java source code
*
* Could be used to merge state into including template class codeBuilder
*/
public CodeBuilder codeBuilder;
/**
* The ITemplate instance
*/
private TemplateBase templateInstance;
/**
* specify the dialect for the template
*/
transient private IDialect dialect;
private TemplateClass(RythmEngine engine) {
this.engine = null == engine ? null : engine.isSingleton() ? null : engine;
}
/**
* Construct a TemplateClass instance using template source file
*
* @param file the template source file
*/
public TemplateClass(File file, RythmEngine engine) {
this(engine.resourceManager().get(file), engine);
}
/**
* Construct a TemplateClass instance using template source content or file path
*
* @param template
*/
public TemplateClass(String template, RythmEngine engine) {
this(engine.resourceManager().get(template), engine);
}
/**
* Construct a TemplateClass instance using template source content or file path
*
* @param template
*/
public TemplateClass(String template, RythmEngine engine, IDialect dialect) {
this(engine.resourceManager().get(template), engine, dialect);
}
public TemplateClass(ITemplateResource resource, RythmEngine engine) {
this(resource, engine, false);
}
public TemplateClass(ITemplateResource resource, RythmEngine engine, IDialect dialect) {
this(resource, engine, false, dialect);
}
public TemplateClass(ITemplateResource resource, RythmEngine engine, boolean noRefresh) {
this(engine);
if (null == resource) throw new NullPointerException();
resource.setEngine(engine());
templateResource = resource;
if (!noRefresh) refresh();
}
public TemplateClass(ITemplateResource resource, RythmEngine engine, boolean noRefresh, IDialect dialect) {
this(engine);
if (null == resource) throw new NullPointerException();
resource.setEngine(engine());
templateResource = resource;
this.dialect = dialect;
if (!noRefresh) refresh();
}
/**
* Return string representation of the template
*
* @return
*/
public Object getKey() {
return null == templateResource ? name() : templateResource.getKey();
}
@SuppressWarnings("unchecked")
private Class> getJavaClass() throws Exception {
if (null == javaSource) {
if (refreshing()) {
lock.waitTillOpen();
}
if (null == javaSource) {
refresh();
}
}
Class> c = engine().classLoader().loadClass(name(), true);
if (null == javaClass) javaClass = (Class) c;
return c;
}
private static final ITemplate NULL_TEMPLATE = new TagBase() {
@Override
public ITemplate __cloneMe(RythmEngine engine, ITemplate caller) {
return null;
}
};
private ITemplate templateInstance_() {
if (!isValid) return NULL_TEMPLATE;
if (null == templateInstance) {
try {
if (Logger.isTraceEnabled()) logger.trace("About to new template instance");
Class> clz = getJavaClass();
if (Logger.isTraceEnabled()) logger.trace("template java class loaded");
templateInstance = (TemplateBase) clz.newInstance();
templateInstance.__setTemplateClass(this);
if (Logger.isTraceEnabled()) logger.trace("template instance generated");
} catch (RythmException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Error load template instance for " + getKey(), e);
}
}
RythmEngine engine = engine();
engine.registerTemplate(templateInstance);
if (!engine.isProdMode()) {
// check parent class change
Class> c = templateInstance.getClass();
Class> pc = c.getSuperclass();
if (null != pc && !Modifier.isAbstract(pc.getModifiers())) {
engine.classes().getByClassName(pc.getName());
}
}
return templateInstance;
}
public ITemplate asTemplate(ICodeType type, Locale locale) {
RythmEngine e = engine();
if (null == name || e.mode().isDev()) refresh();
TemplateBase tmpl = (TemplateBase)templateInstance_().__cloneMe(engine(), null);
tmpl.__prepareRender(type, locale);
return tmpl;
}
public ITemplate asTemplate() {
return asTemplate(null, null);
}
public ITemplate asTemplate(ITemplate caller) {
TemplateBase tb = (TemplateBase)caller;
TemplateBase tmpl = (TemplateBase)templateInstance_().__cloneMe(engine(), caller);
tmpl.__prepareRender(tb.__curCodeType(), tb.__curLocale());
return tmpl;
}
private boolean refreshing = false;
private boolean compiling = false;
private Object refreshLock = new Object();
private boolean refreshing() {
synchronized (refreshLock) {
return refreshing || compiling;
}
}
private void refreshing(boolean b) {
synchronized (refreshLock) {
refreshing = b;
}
if (b) {
lock = new RefreshLock();
} else {
lock.close();
}
}
public boolean refresh() {
return refresh(false);
}
public void buildSourceCode(String includingClassName) {
long start = System.currentTimeMillis();
importPaths = new HashSet();
// Possible bug here?
if (null != codeBuilder) codeBuilder.clear();
codeBuilder = new CodeBuilder(templateResource.asTemplateContent(), name(), tagName(), this, engine, dialect);
codeBuilder.includingCName = includingClassName;
codeBuilder.build();
extendedTemplateClass = codeBuilder.getExtendedTemplateClass();
javaSource = codeBuilder.toString();
if (logger.isTraceEnabled()) {
logger.trace("%s ms to generate java source for template: %s", System.currentTimeMillis() - start, getKey());
}
}
public void buildSourceCode() {
long start = System.currentTimeMillis();
importPaths = new HashSet();
// Possible bug here?
if (null != codeBuilder) codeBuilder.clear();
if (null == dialect)
codeBuilder = new CodeBuilder(templateResource.asTemplateContent(), name(), tagName(), this, engine, null);
else
codeBuilder = dialect.createCodeBuilder(templateResource.asTemplateContent(), name(), tagName(), this, engine);
codeBuilder.build();
extendedTemplateClass = codeBuilder.getExtendedTemplateClass();
javaSource = codeBuilder.toString();
if (engine().insideSandbox()) {
javaSource = CodeBuilder.preventInfiniteLoop(javaSource);
}
if (logger.isTraceEnabled()) {
logger.trace("%s ms to generate java source for template: %s", System.currentTimeMillis() - start, getKey());
}
}
private class RefreshLock {
CountDownLatch cdl = new CountDownLatch(1);
public void close() {
cdl.countDown();
}
public void waitTillOpen() {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
private RefreshLock lock;
/**
* @return true if this class has changes refreshed, otherwise this class has not been changed yet
*/
public boolean refresh(boolean forceRefresh) {
if (refreshing()) return false;
if (inner) return false;
refreshing(true);
try {
RythmEngine engine = engine();
if (!templateResource.isValid()) {
// it is removed?
isValid = false;
engine.classes().remove(this);
return false;
}
ICodeType type = engine.renderSettings.codeType();
if (null == type) {
type = templateResource.codeType();
}
if (null == type || ICodeType.DefImpl.RAW == type) {
type = engine.conf().defaultCodeType();
}
codeType = type;
if (null == name) {
// this is the root level template class
root = this;
name = templateResource.getSuggestedClassName() + CN_SUFFIX;
if (engine.conf().typeInferenceEnabled()) {
name += ParamTypeInferencer.uuid();
}
//name = templateResource.getSuggestedClassName();
engine.registerTemplateClass(this);
}
if (null == javaSource) {
engine.classCache().loadTemplateClass(this);
if (null != javaSource) {
// try refresh extended template class if there is
Pattern p = Pattern.compile(".*extends\\s+([a-zA-Z0-9_]+)\\s*\\{\\s*\\/\\/(.*)\\<\\/extended_resource_key\\>.*", Pattern.DOTALL);
Matcher m = p.matcher(javaSource);
if (m.matches()) {
String extended = m.group(1);
TemplateClassManager tcm = engine().classes();
extendedTemplateClass = tcm.getByClassName(extended);
if (null == extendedTemplateClass) {
String extendedResourceKey = m.group(2);
extendedTemplateClass = tcm.getByTemplate(extendedResourceKey);
if (null == extendedTemplateClass) {
extendedTemplateClass = new TemplateClass(extendedResourceKey, engine());
extendedTemplateClass.refresh();
}
}
engine.addExtendRelationship(extendedTemplateClass, this);
}
}
}
boolean extendedTemplateChanged = false;
if (extendedTemplateClass != null) extendedTemplateChanged = extendedTemplateClass.refresh(forceRefresh);
boolean includedTemplateChanged = false;
if (includedTemplateClasses.size() == 0 && !S.isEmpty(includeTemplateClassNames) && !NO_INCLUDE_CLASS.equals(includeTemplateClassNames)) {
// just loaded from persistent store
for (String tcName : includeTemplateClassNames.split(",")) {
if (S.isEmpty(tcName)) continue;
tcName = tcName.trim();
String fullName = engine().testTemplate(tcName, this);
if (null == fullName) {
logger.warn("Unable to load included template class from name: %s", tcName);
continue;
}
TemplateClass tc = engine().getRegisteredTemplateClass(fullName);
if (null == tc) {
logger.warn("Unable to load included template class from name: %s", tcName);
continue;
}
includedTemplateClasses.add(tc);
}
}
for (TemplateClass tc : includedTemplateClasses) {
if (tc.refresh(forceRefresh)) {
includedTemplateChanged = true;
break;
}
}
if (extendedTemplateChanged && !forceRefresh) {
reset();
compiled = false;
engine().restart(new ClassReloadException("extended class changed"));
refreshing(false);
refresh(forceRefresh);
return true; // pass refresh state to sub template
}
// templateResource.refresh() must be put at first so we make sure resource get refreshed
boolean resourceChanged = templateResource.refresh();
boolean refresh = resourceChanged || forceRefresh || (null == javaSource) || includedTemplateChanged || extendedTemplateChanged;
if (!refresh) return false;
// now start generate source and compile source to byte code
reset();
buildSourceCode();
engine().classCache().cacheTemplateClassSource(this); // cache source code for debugging purpose
if (!codeBuilder.isRythmTemplate()) {
isValid = false;
engine().classes().remove(this);
return false;
}
isValid = true;
//if (!engine().isProd Mode()) logger.info(javaSource);
compiled = false;
return true;
} finally {
refreshing(false);
}
}
public static final String CN_SUFFIX = "__R_T_C__";
/**
* Is this class already compiled but not defined ?
*
* @return if the class is compiled but not defined
*/
public boolean isDefinable() {
return compiled && javaClass != null;
}
/**
* Remove all java source/ byte code and cache
*/
public void reset() {
javaByteCode = null;
enhancedByteCode = null;
javaSource = null;
templateInstance = null;
for (TemplateClass tc : embeddedClasses) {
tc.reset();
engine().classes().remove(tc);
}
embeddedClasses.clear();
engine().classCache().deleteCache(this);
engine().invalidate(this);
javaClass = null;
}
/**
* Compile the class from Java source
*
* @return the bytes that comprise the class file
*/
public byte[] compile() {
if (null != javaByteCode) return javaByteCode;
if (null == javaSource) throw new IllegalStateException("Cannot find java source when compiling " + getKey());
compiling = true;
long start = System.currentTimeMillis();
try {
engine().classes().compiler.compile(new String[]{name()});
if (logger.isTraceEnabled()) {
logger.trace("%sms to compile template: %s", System.currentTimeMillis() - start, getKey());
}
} catch (CompileException.CompilerException e) {
String cn = e.className;
TemplateClass tc = S.isEqual(cn, name()) ? this : engine().classes().getByClassName(cn);
if (null == tc) tc = this;
CompileException ce = new CompileException(engine(), tc, e.javaLineNumber, e.message); // init ce before reset java source to get template line info
javaSource = null; // force parser to regenerate source. This helps to reload after fixing the tag file compilation failure
throw ce;
} catch (NullPointerException e) {
String clazzName = name();
TemplateClass tc = engine().classes().getByClassName(clazzName);
if (this != tc) {
logger.error("tc is not this");
}
if (!this.equals(tc)) {
logger.error("tc not match this");
}
logger.error("NPE encountered when compiling template class:" + name());
throw e;
} finally {
compiling = false;
}
if (logger.isTraceEnabled()) {
logger.trace("%sms to compile template class %s", System.currentTimeMillis() - start, getKey());
}
return javaByteCode;
}
private boolean enhancing = false;
private transient List embeddedClasses = new ArrayList();
/**
* Used to instruct embedded class byte code needs to be enhanced, but for now
* let's just use the java byte code as the enhanced bytecode
*/
public void delayedEnhance(TemplateClass root) {
enhancedByteCode = javaByteCode;
root.embeddedClasses.add(this);
}
public byte[] enhance() {
if (enhancing) throw new IllegalStateException("reenter enhance() call");
enhancing = true;
try {
byte[] bytes = enhancedByteCode;
if (null == bytes) {
bytes = javaByteCode;
if (null == bytes) bytes = compile();
long start = System.currentTimeMillis();
IByteCodeEnhancer en = engine().conf().byteCodeEnhancer();
if (null != en) {
try {
bytes = en.enhance(name(), bytes);
} catch (Exception e) {
logger.warn(e, "Error enhancing template class: %s", getKey());
}
if (logger.isTraceEnabled()) {
logger.trace("%sms to enhance template class %s", System.currentTimeMillis() - start, getKey());
}
}
enhancedByteCode = bytes;
engine().classCache().cacheTemplateClass(this);
}
for (TemplateClass embedded : embeddedClasses) {
embedded.enhancedByteCode = null;
embedded.enhance();
}
return bytes;
} finally {
enhancing = false;
}
}
/**
* Unload the class
*/
public void uncompile() {
javaClass = null;
}
public boolean isClass() {
return !name().endsWith("package-info");
}
public String getPackage() {
int dot = name().lastIndexOf('.');
return dot > -1 ? name().substring(0, dot) : "";
}
public void loadCachedByteCode(byte[] code) {
enhancedByteCode = code;
}
// public void compiled(byte[] code, boolean noCache) {
// javaByteCode = code;
// //enhancedByteCode = code;
// compiled = true;
// enhance();
// if (!noCache) engine().classCache().cacheTemplateClass(this);
// }
/**
* Call back when a class is compiled.
*
* @param code The bytecode.
*/
public void compiled(byte[] code) {
javaByteCode = code;
//enhancedByteCode = code;
compiled = true;
enhance();
//compiled(code, false);
}
@Override
public String toString() {
return "(compiled:" + compiled + ") " + name();
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof TemplateClass) {
TemplateClass that = (TemplateClass) o;
return that.getKey().equals(getKey());
}
return false;
}
@Override
public int hashCode() {
return getKey().hashCode();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy