org.stringtemplate.v4.ST Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ST4 Show documentation
Show all versions of ST4 Show documentation
StringTemplate is a java template engine for generating source code,
web pages, emails, or any other formatted text output.
StringTemplate is particularly good at multi-targeted code generators,
multiple site skins, and internationalization/localization.
It evolved over years of effort developing jGuru.com.
StringTemplate also powers the ANTLR 3 and 4 code generator. Its distinguishing characteristic
is that unlike other engines, it strictly enforces model-view separation.
Strict separation makes websites and code generators more flexible
and maintainable; it also provides an excellent defense against malicious
template authors.
/*
* [The "BSD license"]
* Copyright (c) 2011 Terence Parr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.stringtemplate.v4;
import org.stringtemplate.v4.compiler.CompiledST;
import org.stringtemplate.v4.compiler.FormalArgument;
import org.stringtemplate.v4.debug.AddAttributeEvent;
import org.stringtemplate.v4.debug.ConstructionEvent;
import org.stringtemplate.v4.debug.EvalTemplateEvent;
import org.stringtemplate.v4.debug.InterpEvent;
import org.stringtemplate.v4.gui.STViz;
import org.stringtemplate.v4.misc.*;
import java.io.*;
import java.util.*;
/** An instance of the StringTemplate. It consists primarily of
* a reference to its implementation (shared among all instances)
* and a hash table of attributes. Because of dynamic scoping,
* we also need a reference to any enclosing instance. For example,
* in a deeply nested template for an HTML page body, we could still reference
* the title attribute defined in the outermost page template.
*
* To use templates, you create one (usually via STGroup) and then inject
* attributes using add(). To render its attacks, use render().
*/
public class ST {
public final static String VERSION = "@version@";
/** <@r()>, <@r>...<@end>, and @t.r() ::= "..." defined manually by coder */
public static enum RegionType { IMPLICIT, EMBEDDED, EXPLICIT }
/** Events during template hierarchy construction (not evaluation) */
public static class DebugState {
/** Record who made us? ConstructionEvent creates Exception to grab stack */
public ConstructionEvent newSTEvent;
/** Track construction-time add attribute "events"; used for ST user-level debugging */
public MultiMap addAttrEvents = new MultiMap();
}
public static final String UNKNOWN_NAME = "anonymous";
public static final Object EMPTY_ATTR = new Object();
/** Cache exception since this could happen a lot if people use "missing"
* to mean boolean false.
*/
public static STNoSuchAttributeException cachedNoSuchAttrException;
/** The implementation for this template among all instances of same tmpelate . */
public CompiledST impl;
/** Safe to simultaneously write via add, which is synchronized. Reading
* during exec is, however, NOT synchronized. So, not thread safe to
* add attributes while it is being evaluated. Initialized to EMPTY_ATTR
* to distinguish null from empty.
*/
protected Object[] locals;
/** Created as instance of which group? We need this to init interpreter
* via render. So, we create st and then it needs to know which
* group created it for sake of polymorphism:
*
* st = skin1.getInstanceOf("searchbox");
* result = st.render(); // knows skin1 created it
*
* Say we have a group, g1, with template t and import t and u templates from
* another group, g2. g1.getInstanceOf("u") finds u in g2 but remembers
* that g1 created it. If u includes t, it should create g1.t not g2.t.
*
* g1 = {t(), u()}
* |
* v
* g2 = {t()}
*/
public STGroup groupThatCreatedThisInstance;
/** If Interpreter.trackCreationEvents, track creation, add-attr events
* for each object. Create this object on first use.
*/
public DebugState debugState;
/** Just an alias for ArrayList, but this way I can track whether a
* list is something ST created or it's an incoming list.
*/
public static final class AttributeList extends ArrayList {
public AttributeList(int size) { super(size); }
public AttributeList() { super(); }
}
/** Used by group creation routine, not by users */
protected ST() {
if ( STGroup.trackCreationEvents ) {
if ( debugState==null ) debugState = new ST.DebugState();
debugState.newSTEvent = new ConstructionEvent();
}
}
/** Used to make templates inline in code for simple things like SQL or log records.
* No formal args are set and there is no enclosing instance.
*/
public ST(String template) {
this(STGroup.defaultGroup, template);
}
/** Create ST using non-default delimiters; each one of these will live
* in it's own group since you're overriding a default; don't want to
* alter STGroup.defaultGroup.
*/
public ST(String template, char delimiterStartChar, char delimiterStopChar) {
this(new STGroup(delimiterStartChar, delimiterStopChar), template);
}
public ST(STGroup group, String template) {
this();
groupThatCreatedThisInstance = group;
impl = groupThatCreatedThisInstance.compile(group.getFileName(), null,
null, template, null);
impl.hasFormalArgs = false;
impl.name = UNKNOWN_NAME;
impl.defineImplicitlyDefinedTemplates(groupThatCreatedThisInstance);
}
/** Clone a prototype template.
* Copy all fields minus debugState; don't call this(), which creates
* ctor event
*/
public ST(ST proto) {
this.impl = proto.impl;
if ( proto.locals!=null ) {
//this.locals = Arrays.copyOf(proto.locals, proto.locals.length);
this.locals = new Object[proto.locals.length];
System.arraycopy(proto.locals, 0, this.locals, 0, proto.locals.length);
}
this.groupThatCreatedThisInstance = proto.groupThatCreatedThisInstance;
}
/** Inject an attribute (name/value pair). If there is already an
* attribute with that name, this method turns the attribute into an
* AttributeList with both the previous and the new attribute as elements.
* This method will never alter a List that you inject. If you send
* in a List and then inject a single value element, add() copies
* original list and adds the new value.
*
* Return self so we can chain. t.add("x", 1).add("y", "hi");
*/
public synchronized ST add(String name, Object value) {
if ( name==null ) return this; // allow null value but not name
if ( name.indexOf('.')>=0 ) {
throw new IllegalArgumentException("cannot have '.' in attribute names");
}
if ( STGroup.trackCreationEvents ) {
if ( debugState==null ) debugState = new ST.DebugState();
debugState.addAttrEvents.map(name, new AddAttributeEvent(name, value));
}
FormalArgument arg = null;
if ( impl.hasFormalArgs ) {
if ( impl.formalArguments!=null ) arg = impl.formalArguments.get(name);
if ( arg==null ) {
throw new IllegalArgumentException("no such attribute: "+name);
}
}
else {
// define and make room in locals (a hack to make new ST("simple template") work.)
if ( impl.formalArguments!=null ) {
arg = impl.formalArguments.get(name);
}
if ( arg==null ) { // not defined
arg = new FormalArgument(name);
impl.addArg(arg);
if ( locals==null ) locals = new Object[1];
//else locals = Arrays.copyOf(locals, impl.formalArguments.size());
else {
Object[] copy = new Object[impl.formalArguments.size()];
System.arraycopy(locals, 0, copy, 0,
Math.min(locals.length, impl.formalArguments.size()));
locals = copy;
}
locals[arg.index] = EMPTY_ATTR;
}
}
Object curvalue = locals[arg.index];
if ( curvalue==EMPTY_ATTR ) { // new attribute
locals[arg.index] = value;
return this;
}
// attribute will be multi-valued for sure now
// convert current attribute to list if not already
// copy-on-write semantics; copy a list injected by user to add new value
AttributeList