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(); 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); } } 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()); } } templateInstance.setTemplateClass(this); return templateInstance; }
public static void registerLoggerFactory(ILoggerFactory fact) { Logger.registerLoggerFactory(fact); }
/** Define the data structure hold template class/template src/generated java src */ public class TemplateClass { private static AtomicLong nextVersion = new AtomicLong(); 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(); tc.version = parent.version(); 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 isInner() ? name : name + "v" + version; RythmEngine e = engine(); String n = (!e.reloadByIncClassVersion() || isInner()) ? name : name + "v" + version; return n; } private long version; public long version() { return root().version; } public void setVersion(int v) { version = (long) v; } public TemplateClass extendedTemplateClass; private Set<TemplateClass> includedTemplateClasses = new HashSet<TemplateClass>(); 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<String, String> includeTagTypes = new HashMap<String, String>(); public void setTagType(String tagName, String type) { includeTagTypes.put(tagName, type); } public String getTagType(String tagName) { return includeTagTypes.get(tagName); } 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<String, String>(); 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; } private transient 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<String> importPaths; /** The in JVM loaded class */ public Class<ITemplate> javaClass; /** The in JVM loaded package */ public Package javaPackage; /** 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 * * <p>Could be used to merge state into including template class codeBuilder */ public CodeBuilder codeBuilder; /** Indicate this template class represent a simple template */ public boolean simpleTemplate; /** The ITemplate instance */ public TemplateBase templateInstance; /** specify the dialect for the template */ private transient 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); } 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(); templateResource = resource; if (resource instanceof StringTemplateResource) { simpleTemplate = true; } if (!noRefresh) refresh(); } public TemplateClass( ITemplateResource resource, RythmEngine engine, boolean noRefresh, IDialect dialect) { this(engine); if (null == resource) throw new NullPointerException(); templateResource = resource; if (resource instanceof StringTemplateResource) { simpleTemplate = true; } 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 { Class<?> c = engine().classLoader.loadClass(name(), true); if (null == javaClass) javaClass = (Class<ITemplate>) c; return c; } private static final ITemplate NULL_TEMPLATE = new TemplateBase() { @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(); 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); } } 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()); } } templateInstance.setTemplateClass(this); return templateInstance; } public ITemplate asTemplate() { if (null == name || engine().mode.isDev()) refresh(); return templateInstance_().cloneMe(engine(), null); } public ITemplate asTemplate(ITemplate caller) { return templateInstance_().cloneMe(engine(), caller); } 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; } } private void addVersion() { RythmEngine e = engine(); if (!e.reloadByIncClassVersion()) return; TemplateClassManager tcc = engine().classes; tcc.clsNameIdx.remove(name()); // List<TemplateClass> allEmbedded = tcc.getEmbeddedClasses(name0()); version = nextVersion.getAndIncrement(); tcc.clsNameIdx.put(name(), this); } public boolean refresh() { return refresh(false); } public void buildSourceCode(String includingClassName) { long start = System.currentTimeMillis(); addVersion(); importPaths = new HashSet<String>(); // 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(); addVersion(); importPaths = new HashSet<String>(); // 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 (logger.isTraceEnabled()) { logger.trace( "%s ms to generate java source for template: %s", System.currentTimeMillis() - start, getKey()); } } /** * @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; try { RythmEngine e = engine(); refreshing(true); if (!templateResource.isValid()) { // it is removed? isValid = false; engine().classes.remove(this); return false; } if (null == name) { // this is the root level template class root = this; name = templateResource.getSuggestedClassName() + CN_SUFFIX; // name = templateResource.getSuggestedClassName(); if (e.reloadByIncClassVersion()) version = nextVersion.getAndIncrement(); engine().classes.add(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\\>(.*)\\<\\/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().testTag(tcName, this); if (null == fullName) { logger.warn("Unable to load included template class from name: %s", tcName); continue; } TemplateClass tc = engine().getTemplateClassFromTagName(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 && engine().reloadByRestart() && !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); if (engine().reloadByIncClassVersion()) 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( tc, e.javaLineNumber, e.message); // init ce before reset java source to get template line info if (engine().isProdMode()) { TextBuilder tb = new TextBuilder(); String[] lines = javaSource.split("(\\n\\r|\\r\\n|\\r|\\n)"); for (int line = 0; line < lines.length; ++line) { tb.p(line + 1).p(":").p(lines[line]).p("\n"); } logger.error("error compiling java source:\n%s", tb.toString()); } 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<TemplateClass> embeddedClasses = new ArrayList<TemplateClass>(); /** * 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(); for (ITemplateClassEnhancer en : engine().templateClassEnhancers) { 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(); } }
public class TemplateParser implements IContext { private final ILogger logger = Logger.get(TemplateParser.class); private final CodeBuilder cb; private String template; private int totalLines; int cursor = 0; public TemplateParser(CodeBuilder cb) { this.template = cb.template(); totalLines = StringUtils.countMatches(template, "\n") + 1; this.cb = cb; } public static class ExitInstruction extends FastRuntimeException {} public static class NotSIMTemplate extends FastRuntimeException {} void parse() { DialectManager dm = cb.engine.getDialectManager(); dm.beginParse(this); try { TemplateTokenizer tt = new TemplateTokenizer(template, this); for (TextBuilder builder : tt) { cb.addBuilder(builder); } } catch (ExitInstruction e) { // ignore, just break the parsing process } catch (NotSIMTemplate e) { logger.error("NotSIMTemplate captured"); cb.rewind(); cursor = 0; dm.pushDef(); try { TemplateTokenizer tt = new TemplateTokenizer(template, this); for (TextBuilder builder : tt) { cb.addBuilder(builder); } } catch (ExitInstruction e0) { // ignore just break } finally { dm.pop(); } } finally { dm.endParse(this); } } @Override public TemplateClass getTemplateClass() { return cb.getTemplateClass(); } @Override public CodeBuilder getCodeBuilder() { return cb; } public IDialect getDialect() { return cb.engine.getDialectManager().get(); } public void setDialect(String dialect) { throw new UnsupportedOperationException(); } @Override public String getRemain() { return cursor < template.length() ? template.substring(cursor) : ""; } @Override public int cursor() { return cursor; } @Override public boolean hasRemain() { return cursor < template.length(); } @Override public char peek() { if (!hasRemain()) return '\u0000'; return template.charAt(cursor); } @Override public char pop() { if (!hasRemain()) throw new ArrayIndexOutOfBoundsException(); char c = template.charAt(cursor); step(1); return c; } @Override public void step(int i) { cursor += i; } @Override public String getTemplateSource(int start, int end) { return template.substring(start, end); } private Stack<IBlockHandler> blocks = new Stack<IBlockHandler>(); @Override public void openBlock(IBlockHandler bh) { bh.openBlock(); blocks.push(bh); } @Override public IBlockHandler currentBlock() { return blocks.isEmpty() ? null : blocks.peek(); } @Override public String closeBlock() throws ParseException { if (blocks.isEmpty()) throw new ParseException(cb.getTemplateClass(), currentLine(), "No open block found"); IBlockHandler bh = blocks.pop(); return null == bh ? "" : bh.closeBlock(); } @Override public int currentLine() { if (null == template) return -1; // for testing purpose only if (cursor >= template.length()) return totalLines; // return template.substring(0, cursor).split("(\\r\\n|\\n|\\r)").length; return StringUtils.countMatches(template.substring(0, cursor), "\n") + 1; } @Override public RythmEngine getEngine() { return cb.engine; } @Override public boolean compactMode() { if (!compactStack.empty()) return compactStack.peek(); return getEngine().compactMode(); } private Stack<Boolean> compactStack = new Stack<Boolean>(); @Override public void pushCompact(Boolean compact) { compactStack.push(compact); } @Override public Boolean peekCompact() { if (compactStack.empty()) return null; return compactStack.peek(); } @Override public Boolean popCompact() { if (compactStack.empty()) return null; return compactStack.pop(); } private Stack<Break> breakStack = new Stack<Break>(); @Override public void pushBreak(Break b) { breakStack.push(b); } @Override public Break peekBreak() { if (breakStack.empty()) return null; return breakStack.peek(); } @Override public Break popBreak() { if (breakStack.empty()) return null; return breakStack.pop(); } private Stack<Continue> continueStack = new Stack<Continue>(); @Override public void pushContinue(Continue b) { continueStack.push(b); } @Override public Continue peekContinue() { if (continueStack.empty()) return null; return continueStack.peek(); } @Override public Continue popContinue() { if (continueStack.empty()) return null; return continueStack.pop(); } /* this constructor is just for testing purpose */ private TemplateParser(String s) { template = s; totalLines = template.split("(\\r\\n|\\n|\\r)").length + 1; cb = null; } public static void main(String[] args) { TemplateParser tp = new TemplateParser("\nHello \n\r\nworld!"); System.out.println(tp.totalLines); System.out.println(tp.currentLine()); tp.step(5); System.out.println("5 steps ahead"); System.out.println(tp.currentLine()); System.out.println(tp.getRemain()); tp.step(4); System.out.println("4 steps ahead"); System.out.println(tp.currentLine()); System.out.println(tp.getRemain()); } }