/**
  * Register a renderer for all objects of a particular "kind" for all templates evaluated relative
  * to this group. Use r to render if object in question is instanceof(attributeType).
  */
 public void registerRenderer(Class attributeType, AttributeRenderer r) {
   if (attributeType.isPrimitive()) {
     throw new IllegalArgumentException(
         "can't register renderer for primitive type " + attributeType.getSimpleName());
   }
   typeToAdaptorCache.clear(); // be safe, not clever; wack all values
   if (renderers == null) {
     renderers = Collections.synchronizedMap(new LinkedHashMap<Class, AttributeRenderer>());
   }
   renderers.put(attributeType, r);
 }
  public AttributeRenderer getAttributeRenderer(Class attributeType) {
    if (renderers == null) return null;
    AttributeRenderer r = null;
    if (typeToRendererCache != null) {
      r = typeToRendererCache.get(attributeType);
      if (r != null) return r;
    }

    // Else look up, finding first first
    for (Class t : renderers.keySet()) {
      // t works for attributeType if attributeType subclasses t or implements
      if (t.isAssignableFrom(attributeType)) {
        r = renderers.get(t);
        if (typeToRendererCache == null) {
          typeToRendererCache =
              Collections.synchronizedMap(new LinkedHashMap<Class, AttributeRenderer>());
        }
        typeToRendererCache.put(attributeType, r);
        return r;
      }
    }
    return null;
  }
/**
 * A directory or directory tree of .st template files and/or group files. Individual template files
 * contain formal template definitions. In a sense, it's like a single group file broken into
 * multiple files, one for each template. ST v3 had just the pure template inside, not the template
 * name and header. Name inside must match filename (minus suffix).
 */
public class STGroup {
  /** When we use key as a value in a dictionary, this is how we signify. */
  public static final String DICT_KEY = "key";

  public static final String DEFAULT_KEY = "default";

  /** Load files using what encoding? */
  public String encoding = "UTF-8";

  /**
   * Every group can import templates/dictionaries from other groups. The list must be synchronized
   * (see importTemplates).
   */
  protected List<STGroup> imports;

  public char delimiterStartChar = '<'; // Use <expr> by default
  public char delimiterStopChar = '>';

  /** Maps template name to StringTemplate object. synchronized. */
  protected Map<String, CompiledST> templates =
      Collections.synchronizedMap(new LinkedHashMap<String, CompiledST>());

  /**
   * Maps dict names to HashMap objects. This is the list of dictionaries defined by the user like
   * typeInitMap ::= ["int":"0"]
   */
  protected Map<String, Map<String, Object>> dictionaries =
      Collections.synchronizedMap(new HashMap<String, Map<String, Object>>());

  /**
   * A dictionary that allows people to register a renderer for a particular kind of object for any
   * template evaluated relative to this group. For example, a date should be formatted differently
   * depending on the locale. You can set Date.class to an object whose toString(Object) method
   * properly formats a Date attribute according to locale. Or you can have a different renderer
   * object for each locale.
   *
   * <p>Order of addition is recorded and matters. If more than one renderer works for an object,
   * the first registered has priority.
   *
   * <p>Renderer associated with type t works for object o if
   *
   * <p>t.isAssignableFrom(o.getClass()) // would assignment t = o work?
   *
   * <p>So it works if o is subclass or implements t.
   *
   * <p>This structure is synchronized.
   */
  protected Map<Class, AttributeRenderer> renderers;

  /**
   * A dictionary that allows people to register a model adaptor for a particular kind of object
   * (subclass or implementation). Applies for any template evaluated relative to this group.
   *
   * <p>ST initializes with model adaptors that know how to pull properties out of Objects, Maps,
   * and STs.
   *
   * <p>The last one you register gets priority; do least to most specific.
   */
  protected Map<Class, ModelAdaptor> adaptors =
      Collections.synchronizedMap(
          new LinkedHashMap<Class, ModelAdaptor>() {
            {
              put(Object.class, new ObjectModelAdaptor());
              put(ST.class, new STModelAdaptor());
              put(Map.class, new MapModelAdaptor());
              put(Aggregate.class, new AggregateModelAdaptor());
            }
          });

  /** Cache exact attribute type to adaptor object */
  protected Map<Class, ModelAdaptor> typeToAdaptorCache =
      Collections.synchronizedMap(new LinkedHashMap<Class, ModelAdaptor>());

  /** Cache exact attribute type to renderer object */
  protected Map<Class, AttributeRenderer> typeToRendererCache;

  /**
   * Used to indicate that the template doesn't exist. Prevents duplicate group file loads and
   * unnecessary file checks.
   */
  protected static final CompiledST NOT_FOUND_ST = new CompiledST();

  public static final ErrorManager DEFAULT_ERR_MGR = new ErrorManager();

  /** Watch loading of groups and templates */
  public static boolean verbose = false;

  /**
   * For debugging with STViz. Records where in code an ST was created and where code added
   * attributes.
   */
  public static boolean trackCreationEvents = false;

  /**
   * v3 compatibility; used to iterate across values not keys like v4. But to convert ANTLR
   * templates, it's too hard to find without static typing in templates.
   */
  public static boolean iterateAcrossValues = false;

  public static STGroup defaultGroup = new STGroup();

  /**
   * The errMgr for entire group; all compilations and executions. This gets copied to parsers,
   * walkers, and interpreters.
   */
  public ErrorManager errMgr = STGroup.DEFAULT_ERR_MGR;

  public STGroup() {}

  public STGroup(char delimiterStartChar, char delimiterStopChar) {
    this.delimiterStartChar = delimiterStartChar;
    this.delimiterStopChar = delimiterStopChar;
  }

  /**
   * The primary means of getting an instance of a template from this group. Names must be absolute,
   * fully-qualified names like a/b
   */
  public ST getInstanceOf(String name) {
    if (verbose) System.out.println("getInstanceOf(" + name + ") in group " + getName());
    if (name == null) return null;
    CompiledST c = lookupTemplate(name);
    if (c != null) {
      return createStringTemplate(c);
    }
    return null;
  }

  protected ST getEmbeddedInstanceOf(
      Interpreter interp, ST enclosingInstance, int ip, String name) {
    if (verbose) System.out.println("getEmbeddedInstanceOf(" + name + ")");
    ST st = getInstanceOf(name);
    if (st == null) {
      errMgr.runTimeError(interp, enclosingInstance, ip, ErrorType.NO_SUCH_TEMPLATE, name);
      return createStringTemplateInternally(new CompiledST());
    }
    // this is only called internally. wack any debug ST create events
    if (trackCreationEvents) {
      st.debugState.newSTEvent = null; // toss it out
    }
    return st;
  }

  /** Create singleton template for use with dictionary values */
  public ST createSingleton(Token templateToken) {
    String template;
    if (templateToken.getType() == GroupParser.BIGSTRING) {
      template = Misc.strip(templateToken.getText(), 2);
    } else {
      template = Misc.strip(templateToken.getText(), 1);
    }
    CompiledST impl = compile(getFileName(), null, null, template, templateToken);
    ST st = createStringTemplateInternally(impl);
    st.groupThatCreatedThisInstance = this;
    st.impl.hasFormalArgs = false;
    st.impl.name = ST.UNKNOWN_NAME;
    st.impl.defineImplicitlyDefinedTemplates(this);
    return st;
  }

  /**
   * Is this template defined in this group or from this group below? Names must be absolute,
   * fully-qualified names like /a/b
   */
  public boolean isDefined(String name) {
    return lookupTemplate(name) != null;
  }

  /** Look up a fully-qualified name */
  public CompiledST lookupTemplate(String name) {
    CompiledST code = rawGetTemplate(name);
    if (code == NOT_FOUND_ST) {
      if (verbose) System.out.println(name + " not found");
      return null;
    }
    // try to load from disk and look up again
    if (code == null) code = load(name);
    if (code == null) code = lookupImportedTemplate(name);
    if (code == null) {
      if (verbose) System.out.println(name + " not found");
      templates.put(name, NOT_FOUND_ST);
    }
    if (verbose) if (code != null) System.out.println("found " + name + " in " + getName());
    return code;
  }

  /**
   * "unload" all templates and dictionaries but leave renderers, adaptors, and import
   * relationships. This essentially forces next getInstanceOf to reload templates.
   */
  public synchronized void unload() {
    templates.clear();
    dictionaries.clear();
  }

  /**
   * Load st from disk if dir or load whole group file if .stg file (then return just one template).
   * name is fully-qualified.
   */
  protected CompiledST load(String name) {
    return null;
  }

  /** Force a load if it makes sense for the group */
  public void load() {
    ;
  }

  protected CompiledST lookupImportedTemplate(String name) {
    if (imports == null) return null;
    for (STGroup g : imports) {
      CompiledST code = g.lookupTemplate(name);
      if (code != null) {
        if (verbose) System.out.println("found " + name + " in " + g.getName());
        return code;
      }
    }
    if (verbose) System.out.println(name + "not found in imports");
    return null;
  }

  public CompiledST rawGetTemplate(String name) {
    return templates.get(name);
  }

  public Map<String, Object> rawGetDictionary(String name) {
    return dictionaries.get(name);
  }

  public boolean isDictionary(String name) {
    return dictionaries.get(name) != null;
  }

  // for testing
  public CompiledST defineTemplate(String templateName, String template) {
    try {
      CompiledST impl =
          defineTemplate(
              templateName, new CommonToken(GroupParser.ID, templateName), null, template, null);
      return impl;
    } catch (STException se) {
      // we have reported the error; the exception just blasts us
      // out of parsing this template
    }
    return null;
  }

  // for testing
  public CompiledST defineTemplate(String name, String argsS, String template) {
    String[] args = argsS.split(",");
    List<FormalArgument> a = new ArrayList<FormalArgument>();
    for (String arg : args) {
      a.add(new FormalArgument(arg));
    }
    return defineTemplate(name, new CommonToken(GroupParser.ID, name), a, template, null);
  }

  public CompiledST defineTemplate(
      String templateName,
      Token nameT,
      List<FormalArgument> args,
      String template,
      Token templateToken) {
    if (templateName == null || templateName.length() == 0) {
      throw new IllegalArgumentException("empty template name");
    }
    if (templateName.indexOf('.') >= 0) {
      throw new IllegalArgumentException("cannot have '.' in template names");
    }
    template = Misc.trimOneStartingNewline(template);
    template = Misc.trimOneTrailingNewline(template);
    // compile, passing in templateName as enclosing name for any embedded regions
    CompiledST code = compile(getFileName(), templateName, args, template, templateToken);
    code.name = templateName;
    rawDefineTemplate(templateName, code, nameT);
    code.defineArgDefaultValueTemplates(this);
    code.defineImplicitlyDefinedTemplates(this); // define any anonymous subtemplates

    return code;
  }

  /** Make name and alias for target. Replace any previous def of name */
  public CompiledST defineTemplateAlias(Token aliasT, Token targetT) {
    String alias = aliasT.getText();
    String target = targetT.getText();
    CompiledST targetCode = rawGetTemplate(target);
    if (targetCode == null) {
      errMgr.compileTimeError(ErrorType.ALIAS_TARGET_UNDEFINED, null, aliasT, alias, target);
      return null;
    }
    templates.put(alias, targetCode);
    return targetCode;
  }

  public CompiledST defineRegion(
      String enclosingTemplateName, Token regionT, String template, Token templateToken) {
    String name = regionT.getText();
    template = Misc.trimOneStartingNewline(template);
    template = Misc.trimOneTrailingNewline(template);
    CompiledST code = compile(getFileName(), enclosingTemplateName, null, template, templateToken);
    String mangled = getMangledRegionName(enclosingTemplateName, name);

    if (lookupTemplate(mangled) == null) {
      errMgr.compileTimeError(
          ErrorType.NO_SUCH_REGION, templateToken, regionT, enclosingTemplateName, name);
      return new CompiledST();
    }
    code.name = mangled;
    code.isRegion = true;
    code.regionDefType = ST.RegionType.EXPLICIT;
    code.templateDefStartToken = regionT;

    rawDefineTemplate(mangled, code, regionT);
    code.defineArgDefaultValueTemplates(this);
    code.defineImplicitlyDefinedTemplates(this);

    return code;
  }

  public void defineTemplateOrRegion(
      String templateName,
      String regionSurroundingTemplateName,
      Token templateToken,
      String template,
      Token nameToken,
      List<FormalArgument> args) {
    try {
      if (regionSurroundingTemplateName != null) {
        defineRegion(regionSurroundingTemplateName, nameToken, template, templateToken);
      } else {
        defineTemplate(templateName, nameToken, args, template, templateToken);
      }
    } catch (STException e) {
      // after getting syntax error in a template, we emit msg
      // and throw exception to blast all the way out here.
    }
  }

  public void rawDefineTemplate(String name, CompiledST code, Token defT) {
    CompiledST prev = rawGetTemplate(name);
    if (prev != null) {
      if (!prev.isRegion) {
        errMgr.compileTimeError(ErrorType.TEMPLATE_REDEFINITION, null, defT);
        return;
      }
      if (prev.isRegion) {
        if (code.regionDefType != ST.RegionType.IMPLICIT
            && prev.regionDefType == ST.RegionType.EMBEDDED) {
          errMgr.compileTimeError(
              ErrorType.EMBEDDED_REGION_REDEFINITION, null, defT, getUnMangledTemplateName(name));
          return;
        } else if (code.regionDefType == ST.RegionType.IMPLICIT
            || prev.regionDefType == ST.RegionType.EXPLICIT) {
          errMgr.compileTimeError(
              ErrorType.REGION_REDEFINITION, null, defT, getUnMangledTemplateName(name));
          return;
        }
      }
    }
    code.nativeGroup = this;
    code.templateDefStartToken = defT;
    templates.put(name, code);
  }

  public void undefineTemplate(String name) {
    templates.remove(name);
  }

  /** Compile a template */
  public CompiledST compile(
      String srcName,
      String name,
      List<FormalArgument> args,
      String template,
      Token templateToken) // for error location
      {
    // System.out.println("STGroup.compile: "+enclosingTemplateName);
    Compiler c = new Compiler(this);
    return c.compile(srcName, name, args, template, templateToken);
  }

  /** The "foo" of t() ::= "<@foo()>" is mangled to "region#t#foo" */
  public static String getMangledRegionName(String enclosingTemplateName, String name) {
    return "region__" + enclosingTemplateName + "__" + name;
  }

  /** Return "t.foo" from "region__t__foo" */
  public static String getUnMangledTemplateName(String mangledName) {
    String t = mangledName.substring("region__".length(), mangledName.lastIndexOf("__"));
    String r = mangledName.substring(mangledName.lastIndexOf("__") + 2, mangledName.length());
    return t + '.' + r;
  }

  /**
   * Define a map for this group; not thread safe...do not keep adding these while you reference
   * them.
   */
  public void defineDictionary(String name, Map<String, Object> mapping) {
    dictionaries.put(name, mapping);
  }

  /** Make this group import templates/dictionaries from g. */
  public void importTemplates(STGroup g) {
    if (g == null) return;
    if (imports == null) imports = Collections.synchronizedList(new ArrayList<STGroup>());
    imports.add(g);
  }

  /**
   * Import template files, directories, and group files. Priority is given to templates defined in
   * the current group; this, in effect, provides inheritance. Polymorphism is in effect so that if
   * an inherited template references template t() then we search for t() in the subgroup first.
   *
   * <p>If you specify an absolute file name or directory name, the import statement uses that
   * directly. If it is not an absolute path, we look that entity up in the directory holding the
   * group that initiates the import. If file or directory is not in that directory, then we load
   * using the classpath.
   *
   * <p>Templates are loaded on-demand from import dirs. Imported groups are loaded on-demand when
   * searching for a template.
   *
   * <p>The listener of this group is passed to the import group so errors found while loading
   * imported element are sent to listener of this group.
   */
  public void importTemplates(Token fileNameToken) {
    String fileName = fileNameToken.getText();
    // do nothing upon syntax error
    if (fileName == null || fileName.equals("<missing STRING>")) return;
    fileName = Misc.strip(fileName, 1);

    // System.out.println("import "+fileName);
    boolean isGroupFile = fileName.endsWith(".stg");
    boolean isTemplateFile = fileName.endsWith(".st");
    boolean isGroupDir = !(isGroupFile || isTemplateFile);

    STGroup g = null;

    File f = new File(fileName);
    if (f.isAbsolute()) { // load directly if absolute
      if (isTemplateFile) {
        g = new STGroup();
        g.setListener(this.getListener());
        g.loadAbsoluteTemplateFile(fileName);
      } else if (isGroupFile) {
        g = new STGroupFile(fileName, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      } else if (isGroupDir) {
        g = new STGroupDir(fileName, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      }
      importTemplates(g);
      return;
    }

    // it's a relative name; search path is working dir, g.stg's dir, CLASSPATH
    URL thisRoot = getRootDirURL();
    URL fileUnderRoot = null;
    //		System.out.println("thisRoot="+thisRoot);
    try {
      fileUnderRoot = new URL(thisRoot + "/" + fileName);
    } catch (MalformedURLException mfe) {
      errMgr.internalError(null, "can't build URL for " + thisRoot + "/" + fileName, mfe);
      return;
    }
    if (isTemplateFile) {
      g = new STGroup();
      g.setListener(this.getListener());
      URL fileURL;
      if (Misc.urlExists(fileUnderRoot)) fileURL = fileUnderRoot;
      else fileURL = getURL(fileName); // try CLASSPATH
      if (fileURL != null) {
        try {
          InputStream s = fileURL.openStream();
          ANTLRInputStream templateStream = new ANTLRInputStream(s);
          templateStream.name = fileName;
          CompiledST code = g.loadTemplateFile("", fileName, templateStream);
          if (code == null) g = null;
        } catch (IOException ioe) {
          errMgr.internalError(null, "can't read from " + fileURL, ioe);
          g = null;
        }
      } else {
        g = null;
      }
    } else if (isGroupFile) {
      // System.out.println("look for fileUnderRoot: "+fileUnderRoot);
      if (Misc.urlExists(fileUnderRoot)) {
        g = new STGroupFile(fileUnderRoot, encoding, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      } else {
        g = new STGroupFile(fileName, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      }
    } else if (isGroupDir) {
      //			System.out.println("try dir "+fileUnderRoot);
      if (Misc.urlExists(fileUnderRoot)) {
        g = new STGroupDir(fileUnderRoot, encoding, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      } else {
        // try in CLASSPATH
        //				System.out.println("try dir in CLASSPATH "+fileName);
        g = new STGroupDir(fileName, delimiterStartChar, delimiterStopChar);
        g.setListener(this.getListener());
      }
    }

    if (g == null) {
      errMgr.compileTimeError(ErrorType.CANT_IMPORT, null, fileNameToken, fileName);
    } else {
      importTemplates(g);
    }
  }

  /** Load a group file with full path fileName; it's relative to root by prefix. */
  public void loadGroupFile(String prefix, String fileName) {
    // System.out.println("load group file prefix="+prefix+", fileName="+fileName);
    GroupParser parser = null;
    try {
      URL f = new URL(fileName);
      ANTLRInputStream fs = new ANTLRInputStream(f.openStream(), encoding);
      GroupLexer lexer = new GroupLexer(fs);
      fs.name = fileName;
      CommonTokenStream tokens = new CommonTokenStream(lexer);
      parser = new GroupParser(tokens);
      parser.group(this, prefix);
    } catch (Exception e) {
      errMgr.IOError(null, ErrorType.CANT_LOAD_GROUP_FILE, e, fileName);
    }
  }

  /** Load template file into this group using absolute filename */
  public CompiledST loadAbsoluteTemplateFile(String fileName) {
    ANTLRFileStream fs;
    try {
      fs = new ANTLRFileStream(fileName, encoding);
      fs.name = fileName;
    } catch (IOException ioe) {
      // doesn't exist
      // errMgr.IOError(null, ErrorType.NO_SUCH_TEMPLATE, ioe, fileName);
      return null;
    }
    return loadTemplateFile("", fileName, fs);
  }

  /** Load template stream into this group */
  public CompiledST loadTemplateFile(String prefix, String fileName, CharStream templateStream) {
    GroupLexer lexer = new GroupLexer(templateStream);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    GroupParser parser = new GroupParser(tokens);
    parser.group = this;
    lexer.group = this;
    try {
      parser.templateDef(prefix);
    } catch (RecognitionException re) {
      errMgr.groupSyntaxError(ErrorType.SYNTAX_ERROR, fileName, re, re.getMessage());
    }
    String templateName = Misc.getFileNameNoSuffix(fileName);
    if (prefix != null && prefix.length() > 0) templateName = prefix + "/" + templateName;
    return rawGetTemplate(templateName);
  }

  /**
   * Add an adaptor for a kind of object so ST knows how to pull properties from them. Add adaptors
   * in increasing order of specificity. ST adds Object, Map, and ST model adaptors for you first.
   * Adaptors you add have priority over default adaptors.
   *
   * <p>If an adaptor for type T already exists, it is replaced by the adaptor arg.
   *
   * <p>This must invalidate cache entries, so set your adaptors up before render()ing your
   * templates for efficiency.
   */
  public void registerModelAdaptor(Class attributeType, ModelAdaptor adaptor) {
    if (attributeType.isPrimitive()) {
      throw new IllegalArgumentException(
          "can't register ModelAdaptor for primitive type " + attributeType.getSimpleName());
    }
    adaptors.put(attributeType, adaptor);
    invalidateModelAdaptorCache(attributeType);
  }

  /** remove at least all types in cache that are subclasses or implement attributeType */
  public void invalidateModelAdaptorCache(Class attributeType) {
    typeToAdaptorCache.clear(); // be safe, not clever; wack all values
  }

  public ModelAdaptor getModelAdaptor(Class attributeType) {
    ModelAdaptor a = typeToAdaptorCache.get(attributeType);
    if (a != null) return a;

    // System.out.println("looking for adaptor for "+attributeType);
    // Else, we must find adaptor that fits;
    // find last fit (most specific)
    for (Class t : adaptors.keySet()) {
      // t works for attributeType if attributeType subclasses t or implements
      // System.out.println("checking "+t.getSimpleName()+" against "+attributeType);
      if (t.isAssignableFrom(attributeType)) {
        // System.out.println(t.getName()+" = "+attributeType.getName());
        a = adaptors.get(t);
      }
    }
    // System.out.println("adaptor for "+attributeType+" is "+a);
    typeToAdaptorCache.put(attributeType, a); // cache it for next time
    return a;
  }

  /**
   * Register a renderer for all objects of a particular "kind" for all templates evaluated relative
   * to this group. Use r to render if object in question is instanceof(attributeType).
   */
  public void registerRenderer(Class attributeType, AttributeRenderer r) {
    if (attributeType.isPrimitive()) {
      throw new IllegalArgumentException(
          "can't register renderer for primitive type " + attributeType.getSimpleName());
    }
    typeToAdaptorCache.clear(); // be safe, not clever; wack all values
    if (renderers == null) {
      renderers = Collections.synchronizedMap(new LinkedHashMap<Class, AttributeRenderer>());
    }
    renderers.put(attributeType, r);
  }

  public AttributeRenderer getAttributeRenderer(Class attributeType) {
    if (renderers == null) return null;
    AttributeRenderer r = null;
    if (typeToRendererCache != null) {
      r = typeToRendererCache.get(attributeType);
      if (r != null) return r;
    }

    // Else look up, finding first first
    for (Class t : renderers.keySet()) {
      // t works for attributeType if attributeType subclasses t or implements
      if (t.isAssignableFrom(attributeType)) {
        r = renderers.get(t);
        if (typeToRendererCache == null) {
          typeToRendererCache =
              Collections.synchronizedMap(new LinkedHashMap<Class, AttributeRenderer>());
        }
        typeToRendererCache.put(attributeType, r);
        return r;
      }
    }
    return null;
  }

  public ST createStringTemplate(CompiledST impl) {
    ST st = new ST();
    st.impl = impl;
    st.groupThatCreatedThisInstance = this;
    if (impl.formalArguments != null) {
      st.locals = new Object[impl.formalArguments.size()];
      Arrays.fill(st.locals, ST.EMPTY_ATTR);
    }
    return st;
  }

  /**
   * differentiate so we can avoid having creation events for regions, map operations, and other
   * "new ST" events used during interp.
   */
  public ST createStringTemplateInternally(CompiledST impl) {
    ST st = createStringTemplate(impl);
    if (trackCreationEvents && st.debugState != null) {
      st.debugState.newSTEvent = null; // toss it out
    }
    return st;
  }

  public ST createStringTemplateInternally(ST proto) {
    return new ST(proto); // no need to wack debugState; not set in ST(proto).
  }

  public String getName() {
    return "<no name>;";
  }

  public String getFileName() {
    return null;
  }

  /**
   * Return root dir if this is group dir; return dir containing group file if this is group file.
   * This is derived from original incoming dir or filename. If it was absolute, this should come
   * back as full absolute path. If only a URL is available, return URL of one dir up.
   */
  public URL getRootDirURL() {
    return null;
  }

  public URL getURL(String fileName) {
    URL url = null;
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    url = cl.getResource(fileName);
    if (url == null) {
      cl = this.getClass().getClassLoader();
      url = cl.getResource(fileName);
    }
    return url;
  }

  public String toString() {
    return getName();
  }

  public String show() {
    StringBuilder buf = new StringBuilder();
    if (imports != null) buf.append(" : " + imports);
    for (String name : templates.keySet()) {
      CompiledST c = rawGetTemplate(name);
      if (c.isAnonSubtemplate || c == NOT_FOUND_ST) continue;
      int slash = name.lastIndexOf('/');
      name = name.substring(slash + 1, name.length());
      buf.append(name);
      buf.append('(');
      if (c.formalArguments != null)
        buf.append(Misc.join(c.formalArguments.values().iterator(), ","));
      buf.append(')');
      buf.append(" ::= <<" + Misc.newline);
      buf.append(c.template + Misc.newline);
      buf.append(">>" + Misc.newline);
    }
    return buf.toString();
  }

  public STErrorListener getListener() {
    return errMgr.listener;
  }

  public void setListener(STErrorListener listener) {
    errMgr = new ErrorManager(listener);
  }
}
 /** Make this group import templates/dictionaries from g. */
 public void importTemplates(STGroup g) {
   if (g == null) return;
   if (imports == null) imports = Collections.synchronizedList(new ArrayList<STGroup>());
   imports.add(g);
 }