0001: /*
0002: [The "BSD licence"]
0003: Copyright (c) 2003-2005 Terence Parr
0004: All rights reserved.
0005:
0006: Redistribution and use in source and binary forms, with or without
0007: modification, are permitted provided that the following conditions
0008: are met:
0009: 1. Redistributions of source code must retain the above copyright
0010: notice, this list of conditions and the following disclaimer.
0011: 2. Redistributions in binary form must reproduce the above copyright
0012: notice, this list of conditions and the following disclaimer in the
0013: documentation and/or other materials provided with the distribution.
0014: 3. The name of the author may not be used to endorse or promote products
0015: derived from this software without specific prior written permission.
0016:
0017: THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0018: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0019: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0020: IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0021: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0022: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0023: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0024: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0026: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027: */
0028: package org.antlr.stringtemplate;
0029:
0030: import org.antlr.stringtemplate.language.DefaultTemplateLexer;
0031: import org.antlr.stringtemplate.language.GroupLexer;
0032: import org.antlr.stringtemplate.language.GroupParser;
0033: import org.antlr.stringtemplate.language.AngleBracketTemplateLexer;
0034:
0035: import java.util.*;
0036: import java.io.*;
0037: import java.lang.reflect.Constructor;
0038:
0039: /** Manages a group of named mutually-referential StringTemplate objects.
0040: * Currently the templates must all live under a directory so that you
0041: * can reference them as foo.st or gutter/header.st. To refresh a
0042: * group of templates, just create a new StringTemplateGroup and start
0043: * pulling templates from there. Or, set the refresh interval.
0044: *
0045: * Use getInstanceOf(template-name) to get a string template
0046: * to fill in.
0047: *
0048: * The name of a template is the file name minus ".st" ending if present
0049: * unless you name it as you load it.
0050: *
0051: * You can use the group file format also to define a group of templates
0052: * (this works better for code gen than for html page gen). You must give
0053: * a Reader to the ctor for it to load the group; this is general and
0054: * distinguishes it from the ctors for the old-style "load template files
0055: * from the disk".
0056: *
0057: * 10/2005 I am adding a StringTemplateGroupLoader concept so people can define supergroups
0058: * within a group and have it load that group automatically.
0059: */
0060: public class StringTemplateGroup {
0061: /** What is the group name */
0062: protected String name;
0063:
0064: /** Maps template name to StringTemplate object */
0065: protected Map templates = new HashMap();
0066:
0067: /** Maps map names to HashMap objects. This is the list of maps
0068: * defined by the user like typeInitMap ::= ["int":"0"]
0069: */
0070: protected Map maps = new HashMap();
0071:
0072: /** How to pull apart a template into chunks? */
0073: protected Class templateLexerClass = null;
0074:
0075: /** You can set the lexer once if you know all of your groups use the
0076: * same separator. If the instance has templateLexerClass set
0077: * then it is used as an override.
0078: */
0079: protected static Class defaultTemplateLexerClass = DefaultTemplateLexer.class;
0080:
0081: /** Under what directory should I look for templates? If null,
0082: * to look into the CLASSPATH for templates as resources.
0083: */
0084: protected String rootDir = null;
0085:
0086: /** Track all groups by name; maps name to StringTemplateGroup */
0087: protected static Map nameToGroupMap = Collections
0088: .synchronizedMap(new HashMap());
0089:
0090: /** Track all interfaces by name; maps name to StringTemplateGroupInterface */
0091: protected static Map nameToInterfaceMap = Collections
0092: .synchronizedMap(new HashMap());
0093:
0094: /** Are we derived from another group? Templates not found in this group
0095: * will be searched for in the superGroup recursively.
0096: */
0097: protected StringTemplateGroup super Group = null;
0098:
0099: /** Keep track of all interfaces implemented by this group. */
0100: protected List interfaces = null;
0101:
0102: /** When templates are files on the disk, the refresh interval is used
0103: * to know when to reload. When a Reader is passed to the ctor,
0104: * it is a stream full of template definitions. The former is used
0105: * for web development, but the latter is most likely used for source
0106: * code generation for translators; a refresh is unlikely. Anyway,
0107: * I decided to track the source of templates in case such info is useful
0108: * in other situations than just turning off refresh interval. I just
0109: * found another: don't ever look on the disk for individual templates
0110: * if this group is a group file...immediately look into any super group.
0111: * If not in the super group, report no such template.
0112: */
0113: protected boolean templatesDefinedInGroupFile = false;
0114:
0115: /** Normally AutoIndentWriter is used to filter output, but user can
0116: * specify a new one.
0117: */
0118: protected Class userSpecifiedWriter;
0119:
0120: protected boolean debugTemplateOutput = false;
0121:
0122: /** The set of templates to ignore when dumping start/stop debug strings */
0123: protected Set noDebugStartStopStrings;
0124:
0125: /** A Map<Class,Object> that allows people to register a renderer for
0126: * a particular kind of object to be displayed for any template in this
0127: * group. For example, a date should be formatted differently depending
0128: * on the locale. You can set Date.class to an object whose
0129: * toString(Object) method properly formats a Date attribute
0130: * according to locale. Or you can have a different renderer object
0131: * for each locale.
0132: *
0133: * These render objects are used way down in the evaluation chain
0134: * right before an attribute's toString() method would normally be
0135: * called in ASTExpr.write().
0136: *
0137: * Synchronized at creation time.
0138: */
0139: protected Map attributeRenderers;
0140:
0141: /** Maps obj.prop to a value to avoid reflection costs; track one
0142: * set of all class.property -> Member mappings for all ST usage in VM.
0143: protected static Map classPropertyCache = new HashMap();
0144:
0145: public static class ClassPropCacheKey {
0146: Class c;
0147: String propertyName;
0148: public ClassPropCacheKey(Class c, String propertyName) {
0149: this.c=c;
0150: this.propertyName=propertyName;
0151: }
0152:
0153: public boolean equals(Object other) {
0154: ClassPropCacheKey otherKey = (ClassPropCacheKey)other;
0155: return c.equals(otherKey.c) &&
0156: propertyName.equals(otherKey.propertyName);
0157: }
0158:
0159: public int hashCode() {
0160: return c.hashCode()+propertyName.hashCode();
0161: }
0162: }
0163: */
0164:
0165: /** If a group file indicates it derives from a supergroup, how do we
0166: * find it? Shall we make it so the initial StringTemplateGroup file
0167: * can be loaded via this loader? Right now we pass a Reader to ctor
0168: * to distinguish from the other variety.
0169: */
0170: private static StringTemplateGroupLoader groupLoader = null;
0171:
0172: /** Where to report errors. All string templates in this group
0173: * use this error handler by default.
0174: */
0175: protected StringTemplateErrorListener listener = DEFAULT_ERROR_LISTENER;
0176:
0177: public static StringTemplateErrorListener DEFAULT_ERROR_LISTENER = new StringTemplateErrorListener() {
0178: public void error(String s, Throwable e) {
0179: System.err.println(s);
0180: if (e != null) {
0181: e.printStackTrace(System.err);
0182: }
0183: }
0184:
0185: public void warning(String s) {
0186: System.out.println(s);
0187: }
0188: };
0189:
0190: /** Used to indicate that the template doesn't exist.
0191: * We don't have to check disk for it; we know it's not there.
0192: */
0193: protected static final StringTemplate NOT_FOUND_ST = new StringTemplate();
0194:
0195: /** How long before tossing out all templates in seconds. */
0196: protected int refreshIntervalInSeconds = Integer.MAX_VALUE / 1000; // default: no refreshing from disk
0197: protected long lastCheckedDisk = 0L;
0198:
0199: /** How are the files encoded (ascii, UTF8, ...)? You might want to read
0200: * UTF8 for example on an ascii machine.
0201: */
0202: String fileCharEncoding = System.getProperty("file.encoding");
0203:
0204: /** Create a group manager for some templates, all of which are
0205: * at or below the indicated directory.
0206: */
0207: public StringTemplateGroup(String name, String rootDir) {
0208: this (name, rootDir, DefaultTemplateLexer.class);
0209: }
0210:
0211: public StringTemplateGroup(String name, String rootDir, Class lexer) {
0212: this .name = name;
0213: this .rootDir = rootDir;
0214: lastCheckedDisk = System.currentTimeMillis();
0215: nameToGroupMap.put(name, this );
0216: this .templateLexerClass = lexer;
0217: }
0218:
0219: /** Create a group manager for some templates, all of which are
0220: * loaded as resources via the classloader.
0221: */
0222: public StringTemplateGroup(String name) {
0223: this (name, null, null);
0224: }
0225:
0226: public StringTemplateGroup(String name, Class lexer) {
0227: this (name, null, lexer);
0228: }
0229:
0230: /** Create a group from the template group defined by a input stream.
0231: * The name is pulled from the file. The format is
0232: *
0233: * group name;
0234: *
0235: * t1(args) ::= "..."
0236: * t2() ::= <<
0237: * >>
0238: * ...
0239: */
0240: public StringTemplateGroup(Reader r) {
0241: this (r, AngleBracketTemplateLexer.class,
0242: DEFAULT_ERROR_LISTENER, (StringTemplateGroup) null);
0243: }
0244:
0245: public StringTemplateGroup(Reader r,
0246: StringTemplateErrorListener errors) {
0247: this (r, AngleBracketTemplateLexer.class, errors,
0248: (StringTemplateGroup) null);
0249: }
0250:
0251: public StringTemplateGroup(Reader r, Class lexer) {
0252: this (r, lexer, null, (StringTemplateGroup) null);
0253: }
0254:
0255: public StringTemplateGroup(Reader r, Class lexer,
0256: StringTemplateErrorListener errors) {
0257: this (r, lexer, errors, (StringTemplateGroup) null);
0258: }
0259:
0260: /** Create a group from the input stream, but use a nondefault lexer
0261: * to break the templates up into chunks. This is usefor changing
0262: * the delimiter from the default $...$ to <...>, for example.
0263: */
0264: public StringTemplateGroup(Reader r, Class lexer,
0265: StringTemplateErrorListener errors,
0266: StringTemplateGroup super Group) {
0267: this .templatesDefinedInGroupFile = true;
0268: // if no lexer specified, then assume <...> when loading from group file
0269: if (lexer == null) {
0270: lexer = AngleBracketTemplateLexer.class;
0271: }
0272: this .templateLexerClass = lexer;
0273: if (errors != null) { // always have to have a listener
0274: this .listener = errors;
0275: }
0276: setSuperGroup(super Group);
0277: parseGroup(r);
0278: nameToGroupMap.put(name, this );
0279: verifyInterfaceImplementations();
0280: }
0281:
0282: /** What lexer class to use to break up templates. If not lexer set
0283: * for this group, use static default.
0284: */
0285: public Class getTemplateLexerClass() {
0286: if (templateLexerClass != null) {
0287: return templateLexerClass;
0288: }
0289: return defaultTemplateLexerClass;
0290: }
0291:
0292: public String getName() {
0293: return name;
0294: }
0295:
0296: public void setName(String name) {
0297: this .name = name;
0298: }
0299:
0300: public void setSuperGroup(StringTemplateGroup super Group) {
0301: this .super Group = super Group;
0302: }
0303:
0304: /** Called by group parser when ": supergroupname" is found.
0305: * This method forces the supergroup's lexer to be same as lexer
0306: * for this (sub) group.
0307: */
0308: public void setSuperGroup(String super GroupName) {
0309: StringTemplateGroup super Group = (StringTemplateGroup) nameToGroupMap
0310: .get(super GroupName);
0311: if (super Group != null) { // we've seen before; just use it
0312: setSuperGroup(super Group);
0313: return;
0314: }
0315: // else load it using this group's template lexer
0316: super Group = loadGroup(super GroupName, this .templateLexerClass,
0317: null);
0318: if (super Group != null) {
0319: nameToGroupMap.put(super GroupName, super Group);
0320: setSuperGroup(super Group);
0321: } else {
0322: if (groupLoader == null) {
0323: listener.error("no group loader registered", null);
0324: }
0325: }
0326: }
0327:
0328: /** Just track the new interface; check later. Allows dups, but no biggie. */
0329: public void implementInterface(StringTemplateGroupInterface I) {
0330: if (interfaces == null) {
0331: interfaces = new ArrayList();
0332: }
0333: interfaces.add(I);
0334: }
0335:
0336: /** Indicate that this group implements this interface; load if necessary
0337: * if not in the nameToInterfaceMap.
0338: */
0339: public void implementInterface(String interfaceName) {
0340: StringTemplateGroupInterface I = (StringTemplateGroupInterface) nameToInterfaceMap
0341: .get(interfaceName);
0342: if (I != null) { // we've seen before; just use it
0343: implementInterface(I);
0344: return;
0345: }
0346: I = loadInterface(interfaceName); // else load it
0347: if (I != null) {
0348: nameToInterfaceMap.put(interfaceName, I);
0349: implementInterface(I);
0350: } else {
0351: if (groupLoader == null) {
0352: listener.error("no group loader registered", null);
0353: }
0354: }
0355: }
0356:
0357: public StringTemplateGroup getSuperGroup() {
0358: return super Group;
0359: }
0360:
0361: /** Walk up group hierarchy and show top down to this group */
0362: public String getGroupHierarchyStackString() {
0363: List groupNames = new LinkedList();
0364: StringTemplateGroup p = this ;
0365: while (p != null) {
0366: groupNames.add(0, p.name);
0367: p = p.super Group;
0368: }
0369: return groupNames.toString().replaceAll(",", "");
0370: }
0371:
0372: public String getRootDir() {
0373: return rootDir;
0374: }
0375:
0376: public void setRootDir(String rootDir) {
0377: this .rootDir = rootDir;
0378: }
0379:
0380: /** StringTemplate object factory; each group can have its own. */
0381: public StringTemplate createStringTemplate() {
0382: StringTemplate st = new StringTemplate();
0383: return st;
0384: }
0385:
0386: /** A support routine that gets an instance of name knowing which
0387: * ST encloses it for error messages.
0388: */
0389: protected StringTemplate getInstanceOf(
0390: StringTemplate enclosingInstance, String name)
0391: throws IllegalArgumentException {
0392: //System.out.println("getInstanceOf("+getName()+"::"+name+")");
0393: StringTemplate st = lookupTemplate(enclosingInstance, name);
0394: if (st != null) {
0395: StringTemplate instanceST = st.getInstanceOf();
0396: return instanceST;
0397: }
0398: return null;
0399: }
0400:
0401: /** The primary means of getting an instance of a template from this
0402: * group.
0403: */
0404: public StringTemplate getInstanceOf(String name) {
0405: return getInstanceOf(null, name);
0406: }
0407:
0408: /** The primary means of getting an instance of a template from this
0409: * group when you have a predefined set of attributes you want to
0410: * use.
0411: */
0412: public StringTemplate getInstanceOf(String name, Map attributes) {
0413: StringTemplate st = getInstanceOf(name);
0414: st.attributes = attributes;
0415: return st;
0416: }
0417:
0418: public StringTemplate getEmbeddedInstanceOf(
0419: StringTemplate enclosingInstance, String name)
0420: throws IllegalArgumentException {
0421: /*
0422: System.out.println("surrounding group is "+
0423: enclosingInstance.getGroup().getName()+
0424: " with native group "+enclosingInstance.getNativeGroup().getName());
0425: */
0426: StringTemplate st = null;
0427: // TODO: seems like this should go into lookupTemplate
0428: if (name.startsWith("super.")) {
0429: // for super.foo() refs, ensure that we look at the native
0430: // group for the embedded instance not the current evaluation
0431: // group (which is always pulled down to the original group
0432: // from which somebody did group.getInstanceOf("foo");
0433: st = enclosingInstance.getNativeGroup().getInstanceOf(
0434: enclosingInstance, name);
0435: } else {
0436: st = getInstanceOf(enclosingInstance, name);
0437: }
0438: // make sure all embedded templates have the same group as enclosing
0439: // so that polymorphic refs will start looking at the original group
0440: st.setGroup(this );
0441: st.setEnclosingInstance(enclosingInstance);
0442: return st;
0443: }
0444:
0445: /** Get the template called 'name' from the group. If not found,
0446: * attempt to load. If not found on disk, then try the superGroup
0447: * if any. If not even there, then record that it's
0448: * NOT_FOUND so we don't waste time looking again later. If we've gone
0449: * past refresh interval, flush and look again.
0450: *
0451: * If I find a template in a super group, copy an instance down here
0452: */
0453: public synchronized StringTemplate lookupTemplate(
0454: StringTemplate enclosingInstance, String name)
0455: throws IllegalArgumentException {
0456: //System.out.println("look up "+getName()+"::"+name);
0457: if (name.startsWith("super.")) {
0458: if (super Group != null) {
0459: int dot = name.indexOf('.');
0460: name = name.substring(dot + 1, name.length());
0461: StringTemplate super ScopeST = super Group
0462: .lookupTemplate(enclosingInstance, name);
0463: /*
0464: System.out.println("superScopeST is "+
0465: superScopeST.getGroup().getName()+"::"+name+
0466: " with native group "+superScopeST.getNativeGroup().getName());
0467: */
0468: return super ScopeST;
0469: }
0470: throw new IllegalArgumentException(getName()
0471: + " has no super group; invalid template: " + name);
0472: }
0473: checkRefreshInterval();
0474: StringTemplate st = (StringTemplate) templates.get(name);
0475: if (st == null) {
0476: // not there? Attempt to load
0477: if (!templatesDefinedInGroupFile) {
0478: // only check the disk for individual template
0479: st = loadTemplateFromBeneathRootDirOrCLASSPATH(getFileNameFromTemplateName(name));
0480: }
0481: if (st == null && super Group != null) {
0482: // try to resolve in super group
0483: st = super Group.getInstanceOf(name);
0484: // make sure that when we inherit a template, that it's
0485: // group is reset; it's nativeGroup will remain where it was
0486: if (st != null) {
0487: st.setGroup(this );
0488: }
0489: }
0490: if (st != null) { // found in superGroup
0491: // insert into this group; refresh will allow super
0492: // to change it's def later or this group to add
0493: // an override.
0494: templates.put(name, st);
0495: } else {
0496: // not found; remember that this sucker doesn't exist
0497: templates.put(name, NOT_FOUND_ST);
0498: String context = "";
0499: if (enclosingInstance != null) {
0500: context = "; context is "
0501: + enclosingInstance
0502: .getEnclosingInstanceStackString();
0503: }
0504: String hier = getGroupHierarchyStackString();
0505: context += "; group hierarchy is " + hier;
0506: throw new IllegalArgumentException(
0507: "Can't find template "
0508: + getFileNameFromTemplateName(name)
0509: + context);
0510: }
0511: } else if (st == NOT_FOUND_ST) {
0512: return null;
0513: }
0514: //System.out.println("lookup found "+st.getGroup().getName()+"::"+st.getName());
0515: return st;
0516: }
0517:
0518: public StringTemplate lookupTemplate(String name) {
0519: return lookupTemplate(null, name);
0520: }
0521:
0522: protected void checkRefreshInterval() {
0523: if (templatesDefinedInGroupFile) {
0524: return;
0525: }
0526: boolean timeToFlush = refreshIntervalInSeconds == 0
0527: || (System.currentTimeMillis() - lastCheckedDisk) >= refreshIntervalInSeconds * 1000;
0528: if (timeToFlush) {
0529: // throw away all pre-compiled references
0530: templates.clear();
0531: lastCheckedDisk = System.currentTimeMillis();
0532: }
0533: }
0534:
0535: protected StringTemplate loadTemplate(String name, BufferedReader r)
0536: throws IOException {
0537: String line;
0538: String nl = System.getProperty("line.separator");
0539: StringBuffer buf = new StringBuffer(300);
0540: while ((line = r.readLine()) != null) {
0541: buf.append(line);
0542: buf.append(nl);
0543: }
0544: // strip newlines etc.. from front/back since filesystem
0545: // may add newlines etc...
0546: String pattern = buf.toString().trim();
0547: if (pattern.length() == 0) {
0548: error("no text in template '" + name + "'");
0549: return null;
0550: }
0551: return defineTemplate(name, pattern);
0552: }
0553:
0554: /** Load a template whose name is derived from the template filename.
0555: * If there is no root directory, try to load the template from
0556: * the classpath. If there is a rootDir, try to load the file
0557: * from there.
0558: */
0559: protected StringTemplate loadTemplateFromBeneathRootDirOrCLASSPATH(
0560: String fileName) {
0561: StringTemplate template = null;
0562: String name = getTemplateNameFromFileName(fileName);
0563: // if no rootDir, try to load as a resource in CLASSPATH
0564: if (rootDir == null) {
0565: ClassLoader cl = Thread.currentThread()
0566: .getContextClassLoader();
0567: InputStream is = cl.getResourceAsStream(fileName);
0568: if (is == null) {
0569: cl = this .getClass().getClassLoader();
0570: is = cl.getResourceAsStream(fileName);
0571: }
0572: if (is == null) {
0573: return null;
0574: }
0575: BufferedReader br = null;
0576: try {
0577: br = new BufferedReader(getInputStreamReader(is));
0578: template = loadTemplate(name, br);
0579: } catch (IOException ioe) {
0580: error("Problem reading template file: " + fileName, ioe);
0581: } finally {
0582: if (br != null) {
0583: try {
0584: br.close();
0585: } catch (IOException ioe2) {
0586: error(
0587: "Cannot close template file: "
0588: + fileName, ioe2);
0589: }
0590: }
0591: }
0592: return template;
0593: }
0594: // load via rootDir
0595: template = loadTemplate(name, rootDir + "/" + fileName);
0596: return template;
0597: }
0598:
0599: /** (public so that people can override behavior; not a general
0600: * purpose method)
0601: */
0602: public String getFileNameFromTemplateName(String templateName) {
0603: return templateName + ".st";
0604: }
0605:
0606: /** Convert a filename relativePath/name.st to relativePath/name.
0607: * (public so that people can override behavior; not a general
0608: * purpose method)
0609: */
0610: public String getTemplateNameFromFileName(String fileName) {
0611: String name = fileName;
0612: int suffix = name.lastIndexOf(".st");
0613: if (suffix >= 0) {
0614: name = name.substring(0, suffix);
0615: }
0616: return name;
0617: }
0618:
0619: protected StringTemplate loadTemplate(String name, String fileName) {
0620: BufferedReader br = null;
0621: StringTemplate template = null;
0622: try {
0623: InputStream fin = new FileInputStream(fileName);
0624: InputStreamReader isr = getInputStreamReader(fin);
0625: br = new BufferedReader(isr);
0626: template = loadTemplate(name, br);
0627: br.close();
0628: br = null;
0629: } catch (IOException ioe) {
0630: if (br != null) {
0631: try {
0632: br.close();
0633: } catch (IOException ioe2) {
0634: error("Cannot close template file: " + fileName);
0635: }
0636: }
0637: }
0638: return template;
0639: }
0640:
0641: protected InputStreamReader getInputStreamReader(InputStream in) {
0642: InputStreamReader isr = null;
0643: try {
0644: isr = new InputStreamReader(in, fileCharEncoding);
0645: } catch (UnsupportedEncodingException uee) {
0646: error("Invalid file character encoding: "
0647: + fileCharEncoding);
0648: }
0649: return isr;
0650: }
0651:
0652: public String getFileCharEncoding() {
0653: return fileCharEncoding;
0654: }
0655:
0656: public void setFileCharEncoding(String fileCharEncoding) {
0657: this .fileCharEncoding = fileCharEncoding;
0658: }
0659:
0660: /** Define an examplar template; precompiled and stored
0661: * with no attributes. Remove any previous definition.
0662: */
0663: public synchronized StringTemplate defineTemplate(String name,
0664: String template) {
0665: //System.out.println("defineTemplate "+getName()+"::"+name);
0666: if (name != null && name.indexOf('.') >= 0) {
0667: throw new IllegalArgumentException(
0668: "cannot have '.' in template names");
0669: }
0670: StringTemplate st = createStringTemplate();
0671: st.setName(name);
0672: st.setGroup(this );
0673: st.setNativeGroup(this );
0674: st.setTemplate(template);
0675: st.setErrorListener(listener);
0676: templates.put(name, st);
0677: return st;
0678: }
0679:
0680: /** Track all references to regions <@foo>...<@end> or <@foo()>. */
0681: public StringTemplate defineRegionTemplate(
0682: String enclosingTemplateName, String regionName,
0683: String template, int type) {
0684: String mangledName = getMangledRegionName(
0685: enclosingTemplateName, regionName);
0686: StringTemplate regionST = defineTemplate(mangledName, template);
0687: regionST.setIsRegion(true);
0688: regionST.setRegionDefType(type);
0689: return regionST;
0690: }
0691:
0692: /** Track all references to regions <@foo>...<@end> or <@foo()>. */
0693: public StringTemplate defineRegionTemplate(
0694: StringTemplate enclosingTemplate, String regionName,
0695: String template, int type) {
0696: StringTemplate regionST = defineRegionTemplate(
0697: enclosingTemplate.getOutermostName(), regionName,
0698: template, type);
0699: enclosingTemplate.getOutermostEnclosingInstance()
0700: .addRegionName(regionName);
0701: return regionST;
0702: }
0703:
0704: /** Track all references to regions <@foo()>. We automatically
0705: * define as
0706: *
0707: * @enclosingtemplate.foo() ::= ""
0708: *
0709: * You cannot set these manually in the same group; you have to subgroup
0710: * to override.
0711: */
0712: public StringTemplate defineImplicitRegionTemplate(
0713: StringTemplate enclosingTemplate, String name) {
0714: return defineRegionTemplate(enclosingTemplate, name, "",
0715: StringTemplate.REGION_IMPLICIT);
0716:
0717: }
0718:
0719: /** The "foo" of t() ::= "<@foo()>" is mangled to "region#t#foo" */
0720: public String getMangledRegionName(String enclosingTemplateName,
0721: String name) {
0722: return "region__" + enclosingTemplateName + "__" + name;
0723: }
0724:
0725: /** Return "t" from "region__t__foo" */
0726: public String getUnMangledTemplateName(String mangledName) {
0727: return mangledName.substring("region__".length(), mangledName
0728: .lastIndexOf("__"));
0729: }
0730:
0731: /** Make name and alias for target. Replace any previous def of name */
0732: public synchronized StringTemplate defineTemplateAlias(String name,
0733: String target) {
0734: StringTemplate targetST = getTemplateDefinition(target);
0735: if (targetST == null) {
0736: error("cannot alias " + name + " to undefined template: "
0737: + target);
0738: return null;
0739: }
0740: templates.put(name, targetST);
0741: return targetST;
0742: }
0743:
0744: public synchronized boolean isDefinedInThisGroup(String name) {
0745: StringTemplate st = (StringTemplate) templates.get(name);
0746: if (st != null) {
0747: if (st.isRegion()) {
0748: // don't allow redef of @t.r() ::= "..." or <@r>...<@end>
0749: if (st.getRegionDefType() == StringTemplate.REGION_IMPLICIT) {
0750: return false;
0751: }
0752: }
0753: return true;
0754: }
0755: return false;
0756: }
0757:
0758: /** Get the ST for 'name' in this group only */
0759: public synchronized StringTemplate getTemplateDefinition(String name) {
0760: return (StringTemplate) templates.get(name);
0761: }
0762:
0763: /** Is there *any* definition for template 'name' in this template
0764: * or above it in the group hierarchy?
0765: */
0766: public boolean isDefined(String name) {
0767: try {
0768: return lookupTemplate(name) != null;
0769: } catch (IllegalArgumentException iae) {
0770: return false;
0771: }
0772: }
0773:
0774: protected void parseGroup(Reader r) {
0775: try {
0776: GroupLexer lexer = new GroupLexer(r);
0777: GroupParser parser = new GroupParser(lexer);
0778: parser.group(this );
0779: //System.out.println("read group\n"+this.toString());
0780: } catch (Exception e) {
0781: String name = "<unknown>";
0782: if (getName() != null) {
0783: name = getName();
0784: }
0785: error("problem parsing group " + name + ": " + e, e);
0786: }
0787: }
0788:
0789: /** verify that this group satisfies its interfaces */
0790: protected void verifyInterfaceImplementations() {
0791: for (int i = 0; interfaces != null && i < interfaces.size(); i++) {
0792: StringTemplateGroupInterface I = (StringTemplateGroupInterface) interfaces
0793: .get(i);
0794: List missing = I.getMissingTemplates(this );
0795: List mismatched = I.getMismatchedTemplates(this );
0796: if (missing != null) {
0797: error("group " + getName()
0798: + " does not satisfy interface " + I.getName()
0799: + ": missing templates " + missing);
0800: }
0801: if (mismatched != null) {
0802: error("group " + getName()
0803: + " does not satisfy interface " + I.getName()
0804: + ": mismatched arguments on these templates "
0805: + mismatched);
0806: }
0807: }
0808: }
0809:
0810: public int getRefreshInterval() {
0811: return refreshIntervalInSeconds;
0812: }
0813:
0814: /** How often to refresh all templates from disk. This is a crude
0815: * mechanism at the moment--just tosses everything out at this
0816: * frequency. Set interval to 0 to refresh constantly (no caching).
0817: * Set interval to a huge number like MAX_INT to have no refreshing
0818: * at all (DEFAULT); it will cache stuff.
0819: */
0820: public void setRefreshInterval(int refreshInterval) {
0821: this .refreshIntervalInSeconds = refreshInterval;
0822: }
0823:
0824: public void setErrorListener(StringTemplateErrorListener listener) {
0825: this .listener = listener;
0826: }
0827:
0828: public StringTemplateErrorListener getErrorListener() {
0829: return listener;
0830: }
0831:
0832: /** Specify a StringTemplateWriter implementing class to use for
0833: * filtering output
0834: */
0835: public void setStringTemplateWriter(Class c) {
0836: userSpecifiedWriter = c;
0837: }
0838:
0839: /** return an instance of a StringTemplateWriter that spits output to w.
0840: * If a writer is specified, use it instead of the default.
0841: */
0842: public StringTemplateWriter getStringTemplateWriter(Writer w) {
0843: StringTemplateWriter stw = null;
0844: if (userSpecifiedWriter != null) {
0845: try {
0846: Constructor ctor = userSpecifiedWriter
0847: .getConstructor(new Class[] { Writer.class });
0848: stw = (StringTemplateWriter) ctor
0849: .newInstance(new Object[] { w });
0850: } catch (Exception e) {
0851: error("problems getting StringTemplateWriter", e);
0852: }
0853: }
0854: if (stw == null) {
0855: stw = new AutoIndentWriter(w);
0856: }
0857: return stw;
0858: }
0859:
0860: /** Specify a complete map of what object classes should map to which
0861: * renderer objects for every template in this group (that doesn't
0862: * override it per template).
0863: */
0864: public void setAttributeRenderers(Map renderers) {
0865: this .attributeRenderers = renderers;
0866: }
0867:
0868: /** Register a renderer for all objects of a particular type for all
0869: * templates in this group.
0870: */
0871: public void registerRenderer(Class attributeClassType,
0872: Object renderer) {
0873: if (attributeRenderers == null) {
0874: attributeRenderers = Collections
0875: .synchronizedMap(new HashMap());
0876: }
0877: attributeRenderers.put(attributeClassType, renderer);
0878: }
0879:
0880: /** What renderer is registered for this attributeClassType for
0881: * this group? If not found, as superGroup if it has one.
0882: */
0883: public AttributeRenderer getAttributeRenderer(
0884: Class attributeClassType) {
0885: if (attributeRenderers == null) {
0886: if (super Group == null) {
0887: return null; // no renderers and no parent? Stop.
0888: }
0889: // no renderers; consult super group
0890: return super Group.getAttributeRenderer(attributeClassType);
0891: }
0892:
0893: AttributeRenderer renderer = (AttributeRenderer) attributeRenderers
0894: .get(attributeClassType);
0895: if (renderer == null) {
0896: if (super Group != null) {
0897: // no renderer registered for this class, check super group
0898: renderer = super Group
0899: .getAttributeRenderer(attributeClassType);
0900: }
0901: }
0902: return renderer;
0903: }
0904:
0905: /*
0906: public void cacheClassProperty(Class c, String propertyName, Member member) {
0907: Object key = new ClassPropCacheKey(c,propertyName);
0908: classPropertyCache.put(key,member);
0909: }
0910:
0911: public Member getCachedClassProperty(Class c, String propertyName) {
0912: Object key = new ClassPropCacheKey(c,propertyName);
0913: return (Member)classPropertyCache.get(key);
0914: }
0915: */
0916:
0917: public Map getMap(String name) {
0918: if (maps == null) {
0919: if (super Group == null) {
0920: return null;
0921: }
0922: return super Group.getMap(name);
0923: }
0924: Map m = (Map) maps.get(name);
0925: if (m == null && super Group != null) {
0926: m = super Group.getMap(name);
0927: }
0928: return m;
0929: }
0930:
0931: /** Define a map for this group; not thread safe...do not keep adding
0932: * these while you reference them.
0933: */
0934: public void defineMap(String name, Map mapping) {
0935: maps.put(name, mapping);
0936: }
0937:
0938: public static void registerDefaultLexer(Class lexerClass) {
0939: defaultTemplateLexerClass = lexerClass;
0940: }
0941:
0942: public static void registerGroupLoader(
0943: StringTemplateGroupLoader loader) {
0944: groupLoader = loader;
0945: }
0946:
0947: public static StringTemplateGroup loadGroup(String name) {
0948: return loadGroup(name, null, null);
0949: }
0950:
0951: public static StringTemplateGroup loadGroup(String name,
0952: StringTemplateGroup super Group) {
0953: return loadGroup(name, null, super Group);
0954: }
0955:
0956: public static StringTemplateGroup loadGroup(String name,
0957: Class lexer, StringTemplateGroup super Group) {
0958: if (groupLoader != null) {
0959: return groupLoader.loadGroup(name, lexer, super Group);
0960: }
0961: return null;
0962: }
0963:
0964: public static StringTemplateGroupInterface loadInterface(String name) {
0965: if (groupLoader != null) {
0966: return groupLoader.loadInterface(name);
0967: }
0968: return null;
0969: }
0970:
0971: public void error(String msg) {
0972: error(msg, null);
0973: }
0974:
0975: public void error(String msg, Exception e) {
0976: if (listener != null) {
0977: listener.error(msg, e);
0978: } else {
0979: System.err.println("StringTemplate: " + msg);
0980: if (e != null) {
0981: e.printStackTrace();
0982: }
0983: }
0984: }
0985:
0986: public synchronized Set getTemplateNames() {
0987: return templates.keySet();
0988: }
0989:
0990: /** Indicate whether ST should emit <templatename>...</templatename>
0991: * strings for debugging around output for templates from this group.
0992: */
0993: public void emitDebugStartStopStrings(boolean emit) {
0994: this .debugTemplateOutput = emit;
0995: }
0996:
0997: public void doNotEmitDebugStringsForTemplate(String templateName) {
0998: if (noDebugStartStopStrings == null) {
0999: noDebugStartStopStrings = new HashSet();
1000: }
1001: noDebugStartStopStrings.add(templateName);
1002: }
1003:
1004: public void emitTemplateStartDebugString(StringTemplate st,
1005: StringTemplateWriter out) throws IOException {
1006: if (noDebugStartStopStrings == null
1007: || !noDebugStartStopStrings.contains(st.getName())) {
1008: out.write("<" + st.getName() + ">");
1009: }
1010: }
1011:
1012: public void emitTemplateStopDebugString(StringTemplate st,
1013: StringTemplateWriter out) throws IOException {
1014: if (noDebugStartStopStrings == null
1015: || !noDebugStartStopStrings.contains(st.getName())) {
1016: out.write("</" + st.getName() + ">");
1017: }
1018: }
1019:
1020: public String toString() {
1021: return toString(true);
1022: }
1023:
1024: public String toString(boolean showTemplatePatterns) {
1025: StringBuffer buf = new StringBuffer();
1026: Set templateNameSet = templates.keySet();
1027: List sortedNames = new ArrayList(templateNameSet);
1028: Collections.sort(sortedNames);
1029: Iterator iter = sortedNames.iterator();
1030: buf.append("group " + getName() + ";\n");
1031: StringTemplate formalArgs = new StringTemplate(
1032: "$args;separator=\",\"$");
1033: while (iter.hasNext()) {
1034: String tname = (String) iter.next();
1035: StringTemplate st = (StringTemplate) templates.get(tname);
1036: if (st != NOT_FOUND_ST) {
1037: formalArgs = formalArgs.getInstanceOf();
1038: formalArgs
1039: .setAttribute("args", st.getFormalArguments());
1040: buf.append(tname + "(" + formalArgs + ")");
1041: if (showTemplatePatterns) {
1042: buf.append(" ::= <<");
1043: buf.append(st.getTemplate());
1044: buf.append(">>\n");
1045: } else {
1046: buf.append('\n');
1047: }
1048: }
1049: }
1050: return buf.toString();
1051: }
1052: }
|