patches to avoid FastWriter creation

Alex Twisleton-Wykeham-Fiennes <[email protected]> Thu, 9 Mar 2006 12:29:21 +0000
Newsgroups gmane.comp.java.webmacro.user
Message-ID <[email protected]>
All,

here are the patched files to avoid unrequired FastWriter creation during 
template evaluation.

Changes:-

- Template
added in a write(FastWriter,Context) method.  This is already present in the 
only implementing class (WMTemplate) so it has no affect other than exposing 
the API to classes that evaluate against the interface.

- IncludeDirective
- EvalDirective
Don't pre-cache the results of the sub-template parsing in a String but 
instead get them to write directly out to the FastWriter that is passed into 
the method.  This avoids the creation of a FastWriter to handle the template 
evaluation that is not required and which was taking up a load of processing 
time.

Alex
Template.java (text/x-java, 4.2 KB)
/*
 * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted under the terms of either of the following
 * Open Source licenses:
 *
 * The GNU General Public License, version 2, or any later version, as
 * published by the Free Software Foundation
 * (http://www.fsf.org/copyleft/gpl.html);
 *
 *  or
 *
 * The Semiotek Public License (http://webmacro.org/LICENSE.)
 *
 * This software is provided "as is", with NO WARRANTY, not even the
 * implied warranties of fitness to purpose, or merchantability. You
 * assume all risks and liabilities associated with its use.
 *
 * See www.webmacro.org for more information on the WebMacro project.
 */


package org.webmacro;

import java.io.*;
import java.util.Map;
/**
 * Defines the type of object which contains
 * WebMacro script, text, blocks to be evaluated.
 * @author lane
 *
 */
public interface Template extends Visitable
{

    /**
     * Force the template to parse now. Normally the template will not parse
     * the supplied file until the data is actually needed. However if you
     * want to parse all of your templates at the start of the application
     * to avoid incurring this call during an interactive session, you can
     * call the parse() function at an appropriate time. Once a template has
     * been parsed, subsequent calls to this method do not have an effect. If
     * you want to reparse the template, because you know, it has been changed, you
     * have to create a new Template object and leave this one to the garbage collector.
     * <p>
     * @exception TemplateException if the sytax was invalid and we could not recover
     * @exception IOException if we could not successfullly read the parseTool
     */
    public void parse () throws IOException, TemplateException;

    /**
     * A template may contain parameters, set by the #param directive.
     * These are statically evaluated during the parse phase of the
     * template and shared between all users of the template. They
     * are present so that the template can provide some meta information
     * to its user as to what kind of data it expects to find in the Context,
     * or other information about its use.
     * <p>
     * If the template has not already been parsed, it will be parsed. Thus
     * this method may throw ParseException or IOException if there is some
     * failure in accessing or parsing the template.
     * @exception IOException if an error occurred reading the template
     * @exception TemplateException if an error occurred parsing the template
     */
    public Object getParam (String name) throws IOException, TemplateException;

    /**
     * This method can be used to get  a list of all the parameters
     * that have been set in the template. getParam(Object) can be used
     * to look up any particular parameter.
     */

    /**
     * set a parameter.  Occasinally it's necessary to provide parameters
     * externally.  Although these might be considered of a different nature to
     * those set by #param, they can be stored as such.
     *
     * One example might be the output character encoding which is needed
     * when the template is played.
     */

    public void setParam (String key, Object value);

    public Map getParameters ();

    public String getName ();

    public void setName (String name);

    /**
     * Get a Map containing all #macros defined for this template.
     * The returned Map can be <code>null</code> if this Template
     * does not contain Macros, or if this Template has not been
     * parsed yet.
     * @return The map of macros defined.
     */
    public Map getMacros ();

    public void write (OutputStream out, Context context)
            throws PropertyException, IOException;

    public void write (OutputStream out, String encoding, Context context)
            throws PropertyException, IOException;

    public void write (FastWriter out, Context context)
        throws PropertyException, IOException;

    public String evaluateAsString(Context context)
            throws PropertyException;

    public byte[] evaluateAsBytes(String encoding, Context context)
            throws PropertyException;
}
EvalDirective.java (text/x-java, 6.2 KB)
/*
 * EvalDirective.java
 *
 * Created on May 12, 2003, 2:25 PM
 *
 * Copyright (C) 1998-2003 Semiotek Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted under the terms of either of the following
 * Open Source licenses:
 *
 * The GNU General Public License, version 2, or any later version, as
 * published by the Free Software Foundation
 * (http://www.fsf.org/copyleft/gpl.html);
 *
 *  or
 *
 * The Semiotek Public License (http://webmacro.org/LICENSE.)
 *
 * This software is provided "as is", with NO WARRANTY, not even the
 * implied warranties of fitness to purpose, or merchantability. You
 * assume all risks and liabilities associated with its use.
 *
 * See www.webmacro.org for more information on the WebMacro project.
 */

package org.webmacro.directive;

import org.webmacro.*;
import org.webmacro.engine.BuildContext;
import org.webmacro.engine.BuildException;
import org.webmacro.engine.StringMacro;
import org.webmacro.engine.Variable;

/**
 * Evaluate an instance of a webmacro target.
 * <pre>
 *   #eval $macroVar
 * or
 *   #eval $macroVar using $mapVar
 * </pre>
 *
 * @author  kkirsch
 */

public class EvalDirective extends org.webmacro.directive.Directive
{

   private static final int EVAL_EXPR = 1;
   private static final int EVAL_USING = 2;
   private static final int EVAL_MAP = 3;
   private static final int MAX_RECURSION_DEPTH = 100;

   //private Macro _evalMacro;
   private Object _evalTarget;
   private Object _mapExpr = null;

   private static final ArgDescriptor[] myArgs =
      new ArgDescriptor[] {
         new RValueArg(EVAL_EXPR),
         new OptionalGroup(2),
         new KeywordArg(EVAL_USING, "using"),
         new RValueArg(EVAL_MAP)};

   private static final DirectiveDescriptor myDescr =
      new DirectiveDescriptor("eval", null, myArgs, null);

   public static DirectiveDescriptor getDescriptor()
   {
      return myDescr;
   }

   /** Creates a new instance of EvalDirective */
   public EvalDirective()
   {
   }

   public Object build(DirectiveBuilder builder, BuildContext bc)
      throws BuildException
   {
      _evalTarget = builder.getArg(EVAL_EXPR, bc);
      if (builder.getArg(EVAL_USING) != null)
      {
         // "using" keyword specified, get map expression
         _mapExpr = builder.getArg(EVAL_MAP, bc);
      }
      //_result = (org.webmacro.engine.Block)builder.getArg(TEMPLET_RESULT, bc);
      return this;
   }

   public void write(org.webmacro.FastWriter out, org.webmacro.Context context)
      throws org.webmacro.PropertyException, java.io.IOException
   {
      try
      {
//          String res = null;
         //Context c = null;

         Macro macro = null;

         // The target may be:
         // 1) a variable referencing a Macro
         // 2) a String to be evaluated as a template
         // 3) an expression that evaluates into one of the above?
         Object targ = _evalTarget;
         if (targ instanceof Variable)
         {
            // if variable, get it's value
            targ = ((Variable) _evalTarget).getValue(context);
         }
         if (targ instanceof String)
         {
            // convert string to template
            targ = new StringMacro((String) targ);
         }
         if (targ instanceof Macro)
         {
            macro = (Macro) targ;
         }
         else
         {
            // throw and exception ... we don't know what to do with this!
            throw new PropertyException("Invalid argument to #eval directive.  First arg must be a Macro or a String.");
         }
         if (_mapExpr == null)
         {
            // no map specified, use current context
            macro.write(out, context);
         }
         else
         {
            Object argmap = _mapExpr;
            if (argmap instanceof Macro)
            {
               argmap = ((Macro) argmap).evaluate(context);
            }
            if (!(argmap instanceof java.util.Map))
            {
               throw new PropertyException("The supplied expression did not evaluate to a java.util.Map instance.");
            }
            // check for max recursion
            int recursionDepth = 0;
            if (context.containsKey("EvalDepth"))
            { // check the value
               try
               {
                  recursionDepth =
                     ((Integer) context.get("EvalDepth")).intValue();
                  recursionDepth++;
               }
               catch (Exception e)
               {
                  // something bad happend, leave depth at default
               }
					if (recursionDepth > MAX_RECURSION_DEPTH)
					{
						throw new PropertyException(
							"ERROR: A recursive call to #eval exceeded the maximum depth of "
								+ MAX_RECURSION_DEPTH);
					}
            }
            java.util.Map outerVars = null;
            if (context.containsKey("OuterVars"))
            { // check the value
               try
               {
                  outerVars = (java.util.Map) context.get("OuterVars");
               }
               catch (Exception e)
               {
                  // something bad happened, use vars from calling context
               }
            }
            if (outerVars == null)
               outerVars = context.getMap();
            Context c = new Context(context.getBroker());
            // replace _variables map with supplied map
            c.setMap((java.util.Map) argmap);
            // put current depth into the new context
            c.put("EvalDepth", recursionDepth);
            // add a reference to parent context variables
            c.put("OuterVars", outerVars);
            // add a reference to this macro
            c.put("Self", macro);
            macro.write(out, c);
         }
      }
      catch (Exception e)
      {
         if (e instanceof PropertyException)
            throw (PropertyException) e;
         throw new PropertyException("#eval: Unable to evaluate macro.", e);
      }
   }

   public void accept(TemplateVisitor v)
   {
      v.beginDirective(myDescr.name);
      v.visitDirectiveArg("EvalTarget", _evalTarget);
      if (_mapExpr != null)
      {
         v.visitDirectiveArg("EvalKeyword", "using");
         v.visitDirectiveArg("EvalMap", _mapExpr);
      }
      v.endDirective();
   }

}
IncludeDirective.java (text/x-java, 20.7 KB)
/*
 * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted under the terms of either of the following
 * Open Source licenses:
 *
 * The GNU General Public License, version 2, or any later version, as
 * published by the Free Software Foundation
 * (http://www.fsf.org/copyleft/gpl.html);
 *
 *  or
 *
 * The Semiotek Public License (http://webmacro.org/LICENSE.)
 *
 * This software is provided "as is", with NO WARRANTY, not even the
 * implied warranties of fitness to purpose, or merchantability. You
 * assume all risks and liabilities associated with its use.
 *
 * See www.webmacro.org for more information on the WebMacro project.
 */

package org.webmacro.directive;

import org.webmacro.*;
import org.webmacro.engine.BuildContext;
import org.webmacro.engine.BuildException;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.StringTokenizer;

/**
 * IncludeDirective allows you to include other text files or Templates into
 * the current Template.<p>
 *
 * Syntax:<pre>
 *    #include [as text|template|macro] quoted-string-variable
 *</pre>
 *
 * <code>IncludeDirective</code> has 4 modes of operation which are detailed
 * below.<p>
 *
 * Please note that #include is now the preferred way to include files or
 * templates.  Although you can still use #parse (in the same manner as
 * described below), its use has been deprecated.<p>
 *
 * <b>Including as text</b><br>
 * Including a file as text causes the raw bytes of the file to be included
 * in the current template.  The file is <b>not</b> considered to be a Template,
 * and is not parsed by WebMacro.  This allows you to include external files
 * that contain javascript or stylesheets without fear of mangling by WebMacro's
 * parser.<p>
 *
 * Files included as text are found using the default <code>URLProvider</code>.
 * If the URLProvider cannot find the file, the Broker is asked to find it.
 * This allows you to include files from any of the following places:
 * <li>other websites
 * <li>local filesystem
 * <li>active classpath<p>
 *
 * Files included as text are located once.  Changes to the included file
 * will not be found by WebMacro!<p>
 *
 * Examples:<pre>
 *
 *   // include your system password file (not a good idea! :)
 *   #include as text "/etc/password"
 *
 *   // include the WebMacro homepage
 *   #include as text "http://www.webmacro.org"
 *
 *   // inlude a file that is in your classpath
 *   #include as text "somefile.txt"
 *
 * </pre><br>
 *
 * <b>Including as template</b><br>
 * Including a file as template causes the file to be parsed and evaluated
 * by WebMacro using the current <code>Context</code>.  The evaluated output of
 * the Template is included in your main template.<p>
 *
 * Please note that files included "as template" that contains #macro's <b>DO NOT</b>
 * make their #macro's available to the outer template.  See "Including as macro" below
 * for this.<p>
 *
 * Files included as template are found in your <code>TemplatePath</code>.
 * This path, if not explicitly set in <code>WebMacro.properties</code>, defaults
 * to the system classpath (standalone and JSDK 1.0), or the "web-app" directory
 * (JSDK 2.x).<p>
 *
 * Files included as templates are located and conditionally reloaded/parsed when
 * they change.<p>
 *
 * Examples:<pre>
 *
 *   // include a global header template
 *   #include as template "header.wm"
 *
 *   // include a header template from a subdirectory of your TemplatePath
 *   #include as template "common/header.wm"
 *
 *   // include a header template using an absolute path in your TemplatePath
 *   #include as template "/mysite/common/header.wm"
 *
 * </pre><br>
 *
 * <b>Including as macro</b><br>
 * Including a file as a macro is similiar to the above, except that #macro's
 * defined in the included file <b>are</b> made available to the outer template.
 * "Macro" templates are found using the same TemplatePath settings as
 * "#include as template".<p>
 *
 * Files included as macros are located once.  Changes to the included template
 * will not be found by WebMacro!<p>
 *
 * Exapmles:<pre>
 *
 *   // include a template with macros
 *   #include as macro "my_macros.wm"
 *
 * </pre><br>
 *
 *
 * <b>Including without qualification</b><br></a>
 * Including a file without first qualifing it as a <i>text</i> file or
 * <i>template</i> causes <code>IncludeDirective</code> to make some educated
 * guesses about what type of file it is.<p>
 *
 * If the filename contains <code>://</code>, it is assumed to be a URL and is
 * treated as if it were a <code>text</code> file.<p>
 *
 * Else if the filename ends with any of the configured template file extensions
 * (see below), it is assumed to be a template.<p>
 *
 * Else it is assumed to be <code>text</code>.<p>
 *
 * Examples:<pre>
 *
 *    // include homepage of WebMacro -- assumed to be "as text"
 *    #include "http://www.webmacro.org/"
 *
 *    // include a template -- assumed to be "as template"
 *    #include "header.wm"
 *
 *    // include a text file from local filesystem -- assumed to be "as text"
 *    #include "somefile.txt"
 *
 * </pre>
 *
 *
 * <b><code>WebMacro.properties</code> Configuration Options</b><br>
 *
 * <code>include.TemplateExtensions</code>: list of file extensions<br>
 * This list of file extensions is used to determine the file type of an
 * unqualified #include'd file.<p>
 *
 * The default set of extensions are: <code>wm, wmt, tml</code>
 *
 * @author <a href=mailto:[email protected]>Eric B. Ridge</a>
 * @since the beginning, but consolidated with #parse post 0.97
 *
 */
public class IncludeDirective extends Directive
{

    /** the file to include is a Template */
    public static final int TYPE_TEMPLATE = 0;
    /** the file to include as a Template containing #macro's */
    public static final int TYPE_MACRO = 1;
    /** the file to include is a static file. */
    public static final int TYPE_TEXT = 2;
    /** the file to include is unknown */
    public static final int TYPE_DYNAMIC = 3;


    private static final int PARSE_AS_K = 1;
    private static final int PARSE_TEMPLATE_K = 2;
    private static final int PARSE_TEXT_K = 3;
    private static final int PARSE_MACRO_K = 4;
    private static final int PARSE_FILENAME = 5;

    private static final ArgDescriptor[] _args = new ArgDescriptor[]{
        new OptionalGroup(4),
        new KeywordArg(PARSE_AS_K, "as"),
        new OptionalGroup(1),
        new KeywordArg(PARSE_TEMPLATE_K, "template"),
        new OptionalGroup(1),
        new KeywordArg(PARSE_TEXT_K, "text"),
        new OptionalGroup(1),
        new KeywordArg(PARSE_MACRO_K, "macro"),
        new QuotedStringArg(PARSE_FILENAME),
    };
    private static final DirectiveDescriptor _desc = new DirectiveDescriptor("include", null, _args, null);

    public static DirectiveDescriptor getDescriptor ()
    {
        return _desc;
    }


    //
    // these values are customized for this directive during build()
    //

    /** place holder for the TemplateExtensions configuration key name */
    private String TEMPLATE_EXTENSIONS_NAME = ".TemplateExtensions";


    /** Logging can be good */
    protected Log _log;
    /** the included file type.  one of TYPE_TEMPLATE, TYPE_STATIC, TYPE_MACRO, or TYPE_DYNAMIC */
    protected int _type;
    /** the filename as a Macro, if the filename arg is a Macro */
    protected Macro _macFilename;
    /**
     * the filename as a String, if it is something we can determine during
     * build()
     */
    protected String _strFilename;
    /**
     * the name given to the directive by webmacro configuration.  Used in
     * conjuction with the <code>StrictCompatibility</code>  configuration
     * and to make determinitaions on how the #included file should be included.
     */
    protected String _directiveName;

    /**
     * Build this use of the directive.<p>
     *
     * The specified file to include is included during <b>build/parse</b>
     * time if:<br>
     *    1) The specified filename is a String, not a $Variable; and<br>
     *    2) The "as" keyword is <b>"macro"</b>; or<br>
     *    3) if the <code>Lazy</code> configuration option is set for
     *       this directive.<p>
     *
     * Otherwise, template is found and including during runtime evaluation
     * of this directive.
     */
    public final Object build (DirectiveBuilder builder, BuildContext bc) throws BuildException
    {
        Broker broker = bc.getBroker();
        _log = bc.getLog("IncludeDirective");
        // build configuration key names, since they're based
        // on the configured name of this directive
        _directiveName = builder.getName();
        TEMPLATE_EXTENSIONS_NAME = _directiveName + TEMPLATE_EXTENSIONS_NAME;

        // determine what type of file we need to deal with
        if (builder.getArg(PARSE_TEXT_K, bc) != null)
        {
            _type = TYPE_TEXT;
        }
        else if (builder.getArg(PARSE_TEMPLATE_K, bc) != null)
        {
            _type = TYPE_TEMPLATE;
        }
        else if (builder.getArg(PARSE_MACRO_K, bc) != null)
        {
            _type = TYPE_MACRO;
        }
        else
        {
            _type = TYPE_DYNAMIC;
        }


        // if the filename passed to us was a Macro (needs to be evaluated later)
        // then store it as _macFilename.  Otherwise, assume it's a String
        // and we'll just use that string as the filename
        Object o = builder.getArg(PARSE_FILENAME, bc);
        if (o instanceof Macro)
        {
            if (_type == TYPE_TEXT || _type == TYPE_MACRO)
            {
                _log.warning("Included a 'static' file type using a dynamic filename. "
                        + "File will be included, but any included #macro's will not at " + bc.getCurrentLocation());
            }
            _macFilename = (Macro) o;
        }
        else
        {
            _strFilename = o.toString();
            if (_strFilename == null || _strFilename.length() == 0)
                throw makeBuildException("Filename cannot be null or empty");


            if (_type == TYPE_TEXT)
            {
                // we're a static type, need to
                // include the file (by returning it) now,
                // during build time
                try
                {
                    return getThingToInclude(broker, _type, _strFilename);
                }
                catch (Exception e)
                {
                    throw makeBuildException("Unable to include as text", e);
                }
            }
            else if (_type == TYPE_MACRO)
            {
                // we're a template type.  ned to get the template (already parsed)
                // and merge its macros into our build context.
                // then we return the template so its contents can also be included
                Template t = null;
                try
                {
                    t = getTemplate(broker, _strFilename);
                    bc.mergeConstants(t);
                }
                catch (Exception e)
                {
                    throw makeBuildException("Unable to include as macro", e);
                }
                try {
                    return t.evaluateAsString(bc);
                }
                catch (PropertyException e) {
                    return "";
                }
            }
            else if (_type == TYPE_DYNAMIC)
            {
                // being dynamic means we need to guess the
                // file type based on the file's extension
                // and take care of finding the file during runtime
                _type = guessType(broker, _strFilename);
            }

        }

        // we are configured to be lazy, or we couldn't determine the filename
        // during the build() process (b/c it is a Macro)
        return this;
    }

    /**
     * Write out the included file to the specified FastWriter.  If the
     * included file is actually a template, it is evaluated against the
     * <code>context</code> parameter before being written to the FastWriter
     */
    public void write (FastWriter out, Context context) throws PropertyException, IOException
    {
        Broker broker = context.getBroker();

        // the filename arg passed to us was a Macro, so
        // evaluate and check it now
        if (_macFilename != null)
        {
            _strFilename = _macFilename.evaluate(context).toString();
            if (_strFilename == null || _strFilename.length() == 0)
            {
                throw makePropertyException("Filename cannot be null or empty");
            }
        }

        if (_log.loggingDebug() && context.getCurrentLocation().indexOf(_strFilename) > -1)
        {
            // when in debug mode, output a warning if a template tries to include itself
            // there are situtations where this is desired, but it's good to make
            // the user aware of what they're doing
            _log.warning(context.getCurrentLocation() + " includes itself.");
        }

        // this should only be true if StrictCompatibility is set to false
        // and "as <something>" wasn't specified in the arg list
        if (_type == TYPE_DYNAMIC)
            _type = guessType(broker, _strFilename);

        if (_log.loggingDebug())
        {
            _log.debug("Including '" + _strFilename + "' as "
                    + ((_type == TYPE_MACRO) ? "MACRO"
                    : (_type == TYPE_TEMPLATE) ? "TEMPLATE"
                    : (_type == TYPE_TEXT) ? "TEXT"
                    : "UNKNOWN.  Throwing exception"));
        }

        Object toInclude = getThingToInclude(broker, _type, _strFilename);
        switch (_type)
        {
            case TYPE_MACRO:
                // during runtime evaluation of a template,
                // a TYPE_MACRO doesn't really work as expected.
                // we logged a warning above in build(), but
                // we still need to write it out as a template
                // so just fall through
            case TYPE_TEMPLATE:
                ((Template) toInclude).write(out, context);
                break;

            case TYPE_TEXT:
                // static types are strings
                out.write(toInclude.toString());
                break;

            default:
                // should never happen
                throw makePropertyException("Unrecognized file type: " + _type);
        }
    }

    /**
     * get an array of Template file extensions we should use, if type==dynamic,
     * to decide if the specified file is a template or not
     */
    protected String[] getTemplateExtensions (Broker b)
    {
        String[] ret = (String[]) b.getBrokerLocal(TEMPLATE_EXTENSIONS_NAME);

        if (ret == null)
        {
            String prop = b.getSettings().getSetting(TEMPLATE_EXTENSIONS_NAME, "wm");
            StringTokenizer st = new StringTokenizer(prop, ",; ");
            ret = new String[st.countTokens()];
            int x = 0;
            while (st.hasMoreElements())
            {
                ret[x] = st.nextToken();
                x++;
            }

            b.setBrokerLocal(TEMPLATE_EXTENSIONS_NAME, ret);
        }

        return ret;
    }

    /**
     * if the filename contains <i>://</i> assume it's a file b/c it's probably
     * a url.<p>
     *
     * If the filename ends with any of the configured
     * <code>ParseDirective.TemplateExtensions</code>, assume it's a template.<p>
     *
     * Otherwise, it must be a file
     */
    protected int guessType (Broker b, String filename)
    {
        if (filename.indexOf("://") > -1)
        {
            return TYPE_TEXT;
        }
        else
        {
            String[] extensions = getTemplateExtensions(b);
            for (int x = 0; x < extensions.length; x++)
            {
                if (filename.endsWith(extensions[x]))
                    return TYPE_TEMPLATE;
            }
        }

        return TYPE_TEXT;
    }

    /**
     * get the template or file that the user wants to include, based on the
     * specified type.  This method does not know how to get a file whose type
     * is "TYPE_DYNAMIC".
     */
    protected Object getThingToInclude (Broker b, int type, String filename) throws PropertyException
    {
        switch (type)
        {
            case TYPE_TEMPLATE:
                // wants to include a template
                return getTemplate(b, filename);

            case TYPE_MACRO:
                // wants to include a template
                return getTemplate(b, filename);

            case TYPE_TEXT:
                // wants to include a static file
                return getFile(b, filename);

            case TYPE_DYNAMIC:
                // this should never happen
                throw makePropertyException("Internal Error.  Never guessed file type");

            default:
                // default case should never happen b/c we take care of this
                // during build()
                throw makePropertyException("Internal Error.  Unrecognized file type: " + type);
        }
    }

    /**
     * get a Template via the "template" provider known by the specified broker
     */
    protected Template getTemplate (Broker b, String name) throws PropertyException
    {
        try
        {
            return (Template) b.get("template", name);
        }
        catch (NotFoundException nfe)
        {
            throw makePropertyException("Not found by template provider");
        }
        catch (ResourceException re)
        {
            throw makePropertyException("Unable to get template", re);
        }
        catch (Exception e)
        {
            throw makePropertyException("Unexpected ectpion while getting template");
        }
    }

    /**
     * get the contents of a file (local file or url) via the "url" provider
     * known by the specified broker.  If the url provider can't find it
     * we check the Broker (Broker.getResource).
     */
    protected String getFile (Broker b, String name) throws PropertyException
    {
        try
        {
            // first, ask the URL provider (if we have one) to find the file for us
            return b.get("url", name).toString();
        }
        catch (Exception e)
        {
            // for whatever reason, the URL provider couldn't find the file
            // (maybe we don't have a URL provider?).  No matter, directly
            // ask the Broker to load it as a resource
            URL url = null;

            try
            {
                url = b.getResource(name);
                if (url == null) // doh!  the Broker couldn't find it either.  Guess it doesn't exist
                    throw makePropertyException("Resource not found by URL provider or Broker");

                // open a URLConnection...
                URLConnection conn = url.openConnection();
                StringBuffer sb = new StringBuffer();
                InputStream in = conn.getInputStream();
                String enc = conn.getContentEncoding();
                if (enc == null)
                    enc = b.getSetting("TemplateEncoding");

                // ...and stream the contents of the URL into a String
                int cnt = 0;
                byte[] buff = new byte[4096];
                while ((cnt = in.read(buff)) > 0)
                {
                    sb.append(new String(buff, 0, cnt, enc));
                }
                in.close();

                // return the string form of the resource.
                // This is what will be included in the template
                return sb.toString();
            }
            catch (IOException ioe)
            {
                throw makePropertyException("Error streaming file from: " + url, ioe);
            }
        }
    }

    public void accept (TemplateVisitor v)
    {
        v.beginDirective(_desc.name);
        v.visitDirectiveArg("IncludeDirective", _strFilename);
        v.endDirective();
    }

    //
    // helper methods for throwing exceptions
    //

    private BuildException makeBuildException (String message)
    {
        return makeBuildException(message, null);
    }

    private BuildException makeBuildException (String message, Exception cause)
    {
        message = "#" + _directiveName + " " + _strFilename + ": " + message;
        if (cause != null)
            return new BuildException(message, cause);
        else
            return new BuildException(message);
    }

    private PropertyException makePropertyException (String message)
    {
        return makePropertyException(message, null);
    }

    private PropertyException makePropertyException (String message, Exception cause)
    {
        message = "#" + _directiveName + " " + _strFilename + ": " + message;
        if (cause != null)
            return new PropertyException(message, cause);
        else
            return new PropertyException(message);
    }
}