Save This Page
Home » freemarker-2.3.13 » freemarker.core » [javadoc | source]
    1   /*
    2    * Copyright (c) 2003 The Visigoth Software Society. All rights
    3    * reserved.
    4    *
    5    * Redistribution and use in source and binary forms, with or without
    6    * modification, are permitted provided that the following conditions
    7    * are met:
    8    *
    9    * 1. Redistributions of source code must retain the above copyright
   10    *    notice, this list of conditions and the following disclaimer.
   11    *
   12    * 2. Redistributions in binary form must reproduce the above copyright
   13    *    notice, this list of conditions and the following disclaimer in
   14    *    the documentation and/or other materials provided with the
   15    *    distribution.
   16    *
   17    * 3. The end-user documentation included with the redistribution, if
   18    *    any, must include the following acknowledgement:
   19    *       "This product includes software developed by the
   20    *        Visigoth Software Society (http://www.visigoths.org/)."
   21    *    Alternately, this acknowledgement may appear in the software itself,
   22    *    if and wherever such third-party acknowledgements normally appear.
   23    *
   24    * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
   25    *    project contributors may be used to endorse or promote products derived
   26    *    from this software without prior written permission. For written
   27    *    permission, please contact visigoths@visigoths.org.
   28    *
   29    * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
   30    *    nor may "FreeMarker" or "Visigoth" appear in their names
   31    *    without prior written permission of the Visigoth Software Society.
   32    *
   33    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   34    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   35    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   36    * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
   37    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   38    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   39    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   40    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   41    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   42    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   43    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   44    * SUCH DAMAGE.
   45    * ====================================================================
   46    *
   47    * This software consists of voluntary contributions made by many
   48    * individuals on behalf of the Visigoth Software Society. For more
   49    * information on the Visigoth Software Society, please see
   50    * http://www.visigoths.org/
   51    */
   52   
   53   package freemarker.core;
   54   
   55   import java.io;
   56   import java.text;
   57   import java.util;
   58   
   59   import freemarker.ext.beans.BeansWrapper;
   60   import freemarker.log.Logger;
   61   import freemarker.template;
   62   import freemarker.template.utility.UndeclaredThrowableException;
   63   
   64   /**
   65    * Object that represents the runtime environment during template processing.
   66    * For every invocation of a <tt>Template.process()</tt> method, a new instance
   67    * of this object is created, and then discarded when <tt>process()</tt> returns.
   68    * This object stores the set of temporary variables created by the template,
   69    * the value of settings set by the template, the reference to the data model root,
   70    * etc. Everything that is needed to fulfill the template processing job.
   71    *
   72    * <p>Data models that need to access the <tt>Environment</tt>
   73    * object that represents the template processing on the current thread can use
   74    * the {@link #getCurrentEnvironment()} method.
   75    *
   76    * <p>If you need to modify or read this object before or after the <tt>process</tt>
   77    * call, use {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
   78    *
   79    * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
   80    * @author Attila Szegedi
   81    */
   82   public final class Environment extends Configurable {
   83   
   84       private static final ThreadLocal threadEnv = new ThreadLocal();
   85   
   86       private static final Logger logger = Logger.getLogger("freemarker.runtime");
   87   
   88       private static final Map localizedNumberFormats = new HashMap();
   89       private static final Map localizedDateFormats = new HashMap();
   90   
   91       // Do not use this object directly; clone it first! DecimalFormat isn't
   92       // thread-safe.
   93       private static final DecimalFormat C_NUMBER_FORMAT
   94               = new DecimalFormat(
   95                       "0.################",
   96                       new DecimalFormatSymbols(Locale.US));
   97       static {
   98           C_NUMBER_FORMAT.setGroupingUsed(false);
   99           C_NUMBER_FORMAT.setDecimalSeparatorAlwaysShown(false);
  100       }
  101   
  102       private final TemplateHashModel rootDataModel;
  103       private final ArrayList elementStack = new ArrayList();
  104       private final ArrayList recoveredErrorStack = new ArrayList();
  105   
  106       private NumberFormat numberFormat;
  107       private Map numberFormats;
  108   
  109       private DateFormat timeFormat, dateFormat, dateTimeFormat;
  110       private Map[] dateFormats;
  111       private NumberFormat cNumberFormat;
  112   
  113       private Collator collator;
  114   
  115       private Writer out;
  116       private Macro.Context currentMacroContext;
  117       private ArrayList localContextStack; 
  118       private Namespace mainNamespace, currentNamespace, globalNamespace;
  119       private HashMap loadedLibs;
  120   
  121       private Throwable lastThrowable;
  122       
  123       private TemplateModel lastReturnValue;
  124       private HashMap macroToNamespaceLookup = new HashMap();
  125   
  126       private TemplateNodeModel currentVisitorNode;    
  127       private TemplateSequenceModel nodeNamespaces;
  128       // Things we keep track of for the fallback mechanism.
  129       private int nodeNamespaceIndex;
  130       private String currentNodeName, currentNodeNS;
  131       
  132       private String cachedURLEscapingCharset;
  133       private boolean urlEscapingCharsetCached;
  134   
  135       /**
  136        * Retrieves the environment object associated with the current
  137        * thread. Data model implementations that need access to the
  138        * environment can call this method to obtain the environment object
  139        * that represents the template processing that is currently running
  140        * on the current thread.
  141        */
  142       public static Environment getCurrentEnvironment()
  143       {
  144           return (Environment)threadEnv.get();
  145       }
  146   
  147       public Environment(Template template, final TemplateHashModel rootDataModel, Writer out)
  148       {
  149           super(template);
  150           this.globalNamespace = new Namespace(null);
  151           this.currentNamespace = mainNamespace = new Namespace(template);
  152           this.out = out;
  153           this.rootDataModel = rootDataModel;
  154           importMacros(template);
  155       }
  156   
  157       /**
  158        * Retrieves the currently processed template.
  159        */
  160       public Template getTemplate()
  161       {
  162           return (Template)getParent();
  163       }
  164   
  165       /**
  166        * Deletes cached values that meant to be valid only during a single
  167        * template execution. 
  168        */
  169       private void clearCachedValues() {
  170           numberFormats = null;
  171           numberFormat = null;
  172           dateFormats = null;
  173           collator = null;
  174           cachedURLEscapingCharset = null;
  175           urlEscapingCharsetCached = false;
  176       }
  177       
  178       /**
  179        * Processes the template to which this environment belongs.
  180        */
  181       public void process() throws TemplateException, IOException {
  182           Object savedEnv = threadEnv.get();
  183           threadEnv.set(this);
  184           try {
  185               // Cached values from a previous execution are possibly outdated.
  186               clearCachedValues();
  187               try {
  188                   visit(getTemplate().getRootTreeNode());
  189                   // Do not flush if there was an exception.
  190                   out.flush();
  191               } finally {
  192                   // It's just to allow the GC to free memory...
  193                   clearCachedValues();
  194               }
  195           } finally {
  196               threadEnv.set(savedEnv);
  197           }
  198       }
  199       
  200       /**
  201        * "Visit" the template element.
  202        */
  203       void visit(TemplateElement element)
  204       throws TemplateException, IOException
  205       {
  206           pushElement(element);
  207           try {
  208               element.accept(this);
  209           }
  210           catch (TemplateException te) {
  211               handleTemplateException(te);
  212           }
  213           finally {
  214               popElement();
  215           }
  216       }
  217   
  218       private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
  219       
  220       public void visit(final TemplateElement element,
  221               TemplateDirectiveModel directiveModel, Map args, 
  222               final List bodyParameterNames) throws TemplateException, IOException {
  223           TemplateDirectiveBody nested;
  224           if(element == null) {
  225               nested = null;
  226           }
  227           else {
  228               nested = new TemplateDirectiveBody() {
  229                   public void render(Writer newOut) throws TemplateException, IOException {
  230                       Writer prevOut = out;
  231                       out = newOut;
  232                       try {
  233                           Environment.this.visit(element);
  234                       }
  235                       finally {
  236                           out = prevOut;
  237                       }
  238                   }
  239               };
  240           }
  241           final TemplateModel[] outArgs;
  242           if(bodyParameterNames == null || bodyParameterNames.isEmpty()) {
  243               outArgs = NO_OUT_ARGS;
  244           }
  245           else {
  246               outArgs = new TemplateModel[bodyParameterNames.size()];
  247           }
  248           if(outArgs.length > 0) {
  249               pushLocalContext(new LocalContext() {
  250                   public TemplateModel getLocalVariable(String name) {
  251                       int index = bodyParameterNames.indexOf(name);
  252                       return index != -1 ? outArgs[index] : null;
  253                   }
  254   
  255                   public Collection getLocalVariableNames() {
  256                       return bodyParameterNames;
  257                   }
  258               });
  259           }
  260           try {
  261               directiveModel.execute(this, args, outArgs, nested);
  262           }
  263           finally {
  264               if(outArgs.length > 0) {
  265                   popLocalContext();
  266               }
  267           }
  268       }
  269       
  270       /**
  271        * "Visit" the template element, passing the output
  272        * through a TemplateTransformModel
  273        * @param element the element to visit through a transform
  274        * @param transform the transform to pass the element output
  275        * through
  276        * @param args optional arguments fed to the transform
  277        */
  278       void visit(TemplateElement element,
  279                  TemplateTransformModel transform,
  280                  Map args)
  281       throws TemplateException, IOException
  282       {
  283           try {
  284               Writer tw = transform.getWriter(out, args);
  285               if (tw == null) tw = EMPTY_BODY_WRITER;
  286               TransformControl tc =
  287                   tw instanceof TransformControl
  288                   ? (TransformControl)tw
  289                   : null;
  290   
  291               Writer prevOut = out;
  292               out = tw;
  293               try {
  294                   if(tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
  295                       do {
  296                           if(element != null) {
  297                               visit(element);
  298                           }
  299                       } while(tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
  300                   }
  301               }
  302               catch(Throwable t) {
  303                   try {
  304                       if(tc != null) {
  305                           tc.onError(t);
  306                       }
  307                       else {
  308                           throw t;
  309                       }
  310                   }
  311                   catch(TemplateException e) {
  312                       throw e;
  313                   }
  314                   catch(IOException e) {
  315                       throw e;
  316                   }
  317                   catch(RuntimeException e) {
  318                       throw e;
  319                   }
  320                   catch(Error e) {
  321                       throw e;
  322                   }
  323                   catch(Throwable e) {
  324                       throw new UndeclaredThrowableException(e);
  325                   }
  326               }
  327               finally {
  328                   out = prevOut;
  329                   tw.close();
  330               }
  331           }
  332           catch(TemplateException te) {
  333               handleTemplateException(te);
  334           }
  335       }
  336       
  337       /**
  338        * Visit a block using buffering/recovery
  339        */
  340       
  341        void visit(TemplateElement attemptBlock, TemplateElement recoveryBlock) 
  342        throws TemplateException, IOException {
  343            Writer prevOut = this.out;
  344            StringWriter sw = new StringWriter();
  345            this.out = sw;
  346            TemplateException thrownException = null;
  347            try {
  348                visit(attemptBlock);
  349            } catch (TemplateException te) {
  350                thrownException = te;
  351            } finally {
  352                this.out = prevOut;
  353            }
  354            if (thrownException != null) {
  355                if (logger.isErrorEnabled()) {
  356                    String msg = "Error in attempt block " + attemptBlock.getStartLocation();
  357                    logger.error(msg, thrownException);
  358                }
  359                try {
  360                    recoveredErrorStack.add(thrownException.getMessage());
  361                    visit(recoveryBlock);
  362                } finally {
  363                    recoveredErrorStack.remove(recoveredErrorStack.size() -1);
  364                }
  365            } else {
  366                out.write(sw.toString());
  367            }
  368        }
  369        
  370        String getCurrentRecoveredErrorMesssage() throws TemplateException {
  371            if(recoveredErrorStack.isEmpty()) {
  372                throw new TemplateException(
  373                    ".error is not available outside of a <#recover> block", this);
  374            }
  375            return (String) recoveredErrorStack.get(recoveredErrorStack.size() -1);
  376        }
  377   
  378   
  379       void visit(BodyInstruction.Context bctxt) throws TemplateException, IOException {
  380           Macro.Context invokingMacroContext = getCurrentMacroContext();
  381           ArrayList prevLocalContextStack = localContextStack;
  382           TemplateElement body = invokingMacroContext.body;
  383           if (body != null) {
  384               this.currentMacroContext = invokingMacroContext.prevMacroContext;
  385               currentNamespace = invokingMacroContext.bodyNamespace;
  386               Configurable prevParent = getParent();
  387               setParent(currentNamespace.getTemplate());
  388               this.localContextStack = invokingMacroContext.prevLocalContextStack;
  389               if (invokingMacroContext.bodyParameterNames != null) {
  390                   pushLocalContext(bctxt);
  391               }
  392               try {
  393                   visit(body);
  394               }
  395               finally {
  396                   if (invokingMacroContext.bodyParameterNames != null) {
  397                       popLocalContext();
  398                   }
  399                   this.currentMacroContext = invokingMacroContext;
  400                   currentNamespace = getMacroNamespace(invokingMacroContext.getMacro());
  401                   setParent(prevParent);
  402                   this.localContextStack = prevLocalContextStack;
  403               }
  404           }
  405       }
  406   
  407       /**
  408        * "visit" an IteratorBlock
  409        */
  410       void visit(IteratorBlock.Context ictxt)
  411       throws TemplateException, IOException
  412       {
  413           pushLocalContext(ictxt);
  414           try {
  415               ictxt.runLoop(this);
  416           }
  417           catch (BreakInstruction.Break br) {
  418           }
  419           catch (TemplateException te) {
  420               handleTemplateException(te);
  421           }
  422           finally {
  423               popLocalContext();
  424           }
  425       }
  426       
  427       /**
  428        * "Visit" A TemplateNodeModel
  429        */
  430       
  431       void visit(TemplateNodeModel node, TemplateSequenceModel namespaces) 
  432       throws TemplateException, IOException 
  433       {
  434           if (nodeNamespaces == null) {
  435               SimpleSequence ss = new SimpleSequence(1);
  436               ss.add(currentNamespace);
  437               nodeNamespaces = ss;
  438           }
  439           int prevNodeNamespaceIndex = this.nodeNamespaceIndex;
  440           String prevNodeName = this.currentNodeName;
  441           String prevNodeNS = this.currentNodeNS;
  442           TemplateSequenceModel prevNodeNamespaces = nodeNamespaces;
  443           TemplateNodeModel prevVisitorNode = currentVisitorNode;
  444           currentVisitorNode = node;
  445           if (namespaces != null) {
  446               this.nodeNamespaces = namespaces;
  447           }
  448           try {
  449               TemplateModel macroOrTransform = getNodeProcessor(node);
  450               if (macroOrTransform instanceof Macro) {
  451                   visit((Macro) macroOrTransform, null, null, null, null);
  452               }
  453               else if (macroOrTransform instanceof TemplateTransformModel) {
  454                   visit(null, (TemplateTransformModel) macroOrTransform, null); 
  455               }
  456               else {
  457                   String nodeType = node.getNodeType();
  458                   if (nodeType != null) {
  459                       // If the node's type is 'text', we just output it.
  460                       if ((nodeType.equals("text") && node instanceof TemplateScalarModel)) 
  461                       {
  462                              out.write(((TemplateScalarModel) node).getAsString());
  463                       }
  464                       else if (nodeType.equals("document")) {
  465                           recurse(node, namespaces);
  466                       }
  467                       // We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
  468                       // we just ignore it.
  469                       else if (!nodeType.equals("pi") 
  470                            && !nodeType.equals("comment") 
  471                            && !nodeType.equals("document_type")) 
  472                       {
  473                           String nsBit = "";
  474                           String ns = node.getNodeNamespace();
  475                           if (ns != null) {
  476                               if (ns.length() >0) {
  477                                   nsBit = " and namespace " + ns;
  478                               } else {
  479                                   nsBit = " and no namespace";
  480                               }
  481                           }
  482                           throw new TemplateException("No macro or transform defined for node named "  
  483                                       + node.getNodeName() + nsBit
  484                                       + ", and there is no fallback handler called @" + nodeType + " either.",
  485                                       this);
  486                       }
  487                   }
  488                   else {
  489                       String nsBit = "";
  490                       String ns = node.getNodeNamespace();
  491                       if (ns != null) {
  492                           if (ns.length() >0) {
  493                               nsBit = " and namespace " + ns;
  494                           } else {
  495                               nsBit = " and no namespace";
  496                           }
  497                       }
  498                       throw new TemplateException("No macro or transform defined for node with name " 
  499                                   + node.getNodeName() + nsBit 
  500                                   + ", and there is no macro or transform called @default either.",
  501                                   this);
  502                   }
  503               }
  504           } 
  505           finally {
  506               this.currentVisitorNode = prevVisitorNode;
  507               this.nodeNamespaceIndex = prevNodeNamespaceIndex;
  508               this.currentNodeName = prevNodeName;
  509               this.currentNodeNS = prevNodeNS;
  510               this.nodeNamespaces = prevNodeNamespaces;
  511           }
  512       }
  513       
  514       void fallback() throws TemplateException, IOException {
  515           TemplateModel macroOrTransform = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
  516           if (macroOrTransform instanceof Macro) {
  517               visit((Macro) macroOrTransform, null, null, null, null);
  518           }
  519           else if (macroOrTransform instanceof TemplateTransformModel) {
  520               visit(null, (TemplateTransformModel) macroOrTransform, null); 
  521           }
  522       }
  523       
  524       /**
  525        * "visit" a macro.
  526        */
  527       
  528       void visit(Macro macro, 
  529                  Map namedArgs, 
  530                  List positionalArgs, 
  531                  List bodyParameterNames,
  532                  TemplateElement nestedBlock) 
  533          throws TemplateException, IOException 
  534       {
  535           if (macro == Macro.DO_NOTHING_MACRO) {
  536               return;
  537           }
  538           pushElement(macro);
  539           try {
  540               Macro.Context previousMacroContext = currentMacroContext;
  541               Macro.Context mc = macro.new Context(this, nestedBlock, bodyParameterNames);
  542   
  543               String catchAll = macro.getCatchAll();
  544               TemplateModel unknownVars = null;
  545               
  546               if (namedArgs != null) {
  547                   if (catchAll != null)
  548                       unknownVars = new SimpleHash();
  549                   for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext();) {
  550                       Map.Entry entry = (Map.Entry) it.next();
  551                       String varName = (String) entry.getKey();
  552                       boolean hasVar = macro.hasArgNamed(varName);
  553                       if (hasVar || catchAll != null) {
  554                           Expression arg = (Expression) entry.getValue();
  555                           TemplateModel value = arg.getAsTemplateModel(this);
  556                           if (hasVar) {
  557                               mc.setLocalVar(varName, value);
  558                           } else {
  559                               ((SimpleHash)unknownVars).put(varName, value);
  560                           }
  561                       } else {
  562                           String msg = "Macro " + macro.getName() + " has no such argument: " + varName;
  563                           throw new TemplateException(msg, this);
  564                       }
  565                   }
  566               }
  567               else if (positionalArgs != null) {
  568                   if (catchAll != null)
  569                       unknownVars = new SimpleSequence();
  570                   String[] argumentNames = macro.getArgumentNames();
  571                   int size = positionalArgs.size();
  572                   if (argumentNames.length < size && catchAll == null) {
  573                       throw new TemplateException("Macro " + macro.getName() 
  574                         + " only accepts " + argumentNames.length + " parameters.", this);
  575                   }
  576                   for (int i = 0; i < size; i++) {
  577                       Expression argExp = (Expression) positionalArgs.get(i);
  578                       TemplateModel argModel = argExp.getAsTemplateModel(this);
  579                       try {
  580                           if (i < argumentNames.length) {
  581                               String argName = argumentNames[i];
  582                               mc.setLocalVar(argName, argModel);
  583                           } else {
  584                               ((SimpleSequence)unknownVars).add(argModel);
  585                           }
  586                       } catch (RuntimeException re) {
  587                           throw new TemplateException(re, this);
  588                       }
  589                   }
  590               }
  591               if (catchAll != null) {
  592                   mc.setLocalVar(catchAll, unknownVars);
  593               }
  594               ArrayList prevLocalContextStack = localContextStack;
  595               localContextStack = null;
  596               Namespace prevNamespace = currentNamespace;
  597               Configurable prevParent = getParent();
  598               currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
  599               currentMacroContext = mc;
  600               try {
  601                   mc.runMacro(this);
  602               }
  603               catch (ReturnInstruction.Return re) {
  604               }
  605               catch (TemplateException te) {
  606                   handleTemplateException(te);
  607               } finally {
  608                   currentMacroContext = previousMacroContext;
  609                   localContextStack = prevLocalContextStack;
  610                   currentNamespace = prevNamespace;
  611                   setParent(prevParent);
  612               }
  613           } finally {
  614               popElement();
  615           }
  616       }
  617       
  618       void visitMacroDef(Macro macro) {
  619           macroToNamespaceLookup.put(macro, currentNamespace);
  620           currentNamespace.put(macro.getName(), macro);
  621       }
  622       
  623       Namespace getMacroNamespace(Macro macro) {
  624           return (Namespace) macroToNamespaceLookup.get(macro);
  625       }
  626       
  627       void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces)
  628       throws TemplateException, IOException 
  629       {
  630           if (node == null) {
  631               node = this.getCurrentVisitorNode();
  632               if (node == null) {
  633                   throw new TemplateModelException(
  634                           "The target node of recursion is missing or null.");
  635               }
  636           }
  637           TemplateSequenceModel children = node.getChildNodes();
  638           if (children == null) return;
  639           for (int i=0; i<children.size(); i++) {
  640               TemplateNodeModel child = (TemplateNodeModel) children.get(i);
  641               if (child != null) {
  642                   visit(child, namespaces);
  643               }
  644           }
  645       }
  646   
  647       Macro.Context getCurrentMacroContext() {
  648           return currentMacroContext;
  649       }
  650       
  651       private void handleTemplateException(TemplateException te)
  652           throws TemplateException
  653       {
  654           // Logic to prevent double-handling of the exception in
  655           // nested visit() calls.
  656           if(lastThrowable == te) {
  657               throw te;
  658           }
  659           lastThrowable = te;
  660   
  661           // Log the exception
  662           if(logger.isErrorEnabled()) {
  663               logger.error("", te);
  664           }
  665   
  666           // Stop exception is not passed to the handler, but
  667           // explicitly rethrown.
  668           if(te instanceof StopException) {
  669               throw te;
  670           }
  671   
  672           // Finally, pass the exception to the handler
  673           getTemplateExceptionHandler().handleTemplateException(te, this, out);
  674       }
  675   
  676       public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
  677           super.setTemplateExceptionHandler(templateExceptionHandler);
  678           lastThrowable = null;
  679       }
  680       
  681       public void setLocale(Locale locale) {
  682           super.setLocale(locale);
  683           // Clear local format cache
  684           numberFormats = null;
  685           numberFormat = null;
  686   
  687           dateFormats = null;
  688           timeFormat = dateFormat = dateTimeFormat = null;
  689   
  690           collator = null;
  691       }
  692   
  693       public void setTimeZone(TimeZone timeZone) {
  694           super.setTimeZone(timeZone);
  695           // Clear local date format cache
  696           dateFormats = null;
  697           timeFormat = dateFormat = dateTimeFormat = null;
  698       }
  699       
  700       public void setURLEscapingCharset(String urlEscapingCharset) {
  701           urlEscapingCharsetCached = false;
  702           super.setURLEscapingCharset(urlEscapingCharset);
  703       }
  704       
  705       /*
  706        * Note that altough it is not allowed to set this setting with the
  707        * <tt>setting</tt> directive, it still must be allowed to set it from Java
  708        * code while the template executes, since some frameworks allow templates
  709        * to actually change the output encoding on-the-fly.
  710        */
  711       public void setOutputEncoding(String outputEncoding) {
  712           urlEscapingCharsetCached = false;
  713           super.setOutputEncoding(outputEncoding);
  714       }
  715       
  716       /**
  717        * Returns the name of the charset that should be used for URL encoding.
  718        * This will be <code>null</code> if the information is not available.
  719        * The function caches the return value, so it is quick to call it
  720        * repeately. 
  721        */
  722       String getEffectiveURLEscapingCharset() {
  723           if (!urlEscapingCharsetCached) {
  724               cachedURLEscapingCharset = getURLEscapingCharset();
  725               if (cachedURLEscapingCharset == null) {
  726                   cachedURLEscapingCharset = getOutputEncoding();
  727               }
  728               urlEscapingCharsetCached = true;
  729           }
  730           return cachedURLEscapingCharset;
  731       }
  732   
  733       Collator getCollator() {
  734           if(collator == null) {
  735               collator = Collator.getInstance(getLocale());
  736           }
  737           return collator;
  738       }
  739   
  740       public void setOut(Writer out) {
  741           this.out = out;
  742       }
  743   
  744       public Writer getOut() {
  745           return out;
  746       }
  747   
  748       String formatNumber(Number number) {
  749           if(numberFormat == null) {
  750               numberFormat = getNumberFormatObject(getNumberFormat());
  751           }
  752           return numberFormat.format(number);
  753       }
  754   
  755       public void setNumberFormat(String formatName) {
  756           super.setNumberFormat(formatName);
  757           numberFormat = null;
  758       }
  759   
  760       String formatDate(Date date, int type) throws TemplateModelException {
  761           DateFormat df = getDateFormatObject(type);
  762           if(df == null) {
  763               throw new TemplateModelException("Can't convert the date to string, because it is not known which parts of the date variable are in use. Use ?date, ?time or ?datetime built-in, or ?string.<format> or ?string(format) built-in with this date.");
  764           }
  765           return df.format(date);
  766       }
  767   
  768       public void setTimeFormat(String formatName) {
  769           super.setTimeFormat(formatName);
  770           timeFormat = null;
  771       }
  772   
  773       public void setDateFormat(String formatName) {
  774           super.setDateFormat(formatName);
  775           dateFormat = null;
  776       }
  777   
  778       public void setDateTimeFormat(String formatName) {
  779           super.setDateTimeFormat(formatName);
  780           dateTimeFormat = null;
  781       }
  782   
  783       public Configuration getConfiguration() {
  784           return getTemplate().getConfiguration();
  785       }
  786       
  787       TemplateModel getLastReturnValue() {
  788           return lastReturnValue;
  789       }
  790       
  791       void setLastReturnValue(TemplateModel lastReturnValue) {
  792           this.lastReturnValue = lastReturnValue;
  793       }
  794       
  795       void clearLastReturnValue() {
  796           this.lastReturnValue = null;
  797       }
  798   
  799       NumberFormat getNumberFormatObject(String pattern)
  800       {
  801           if(numberFormats == null) {
  802               numberFormats = new HashMap();
  803           }
  804   
  805           NumberFormat format = (NumberFormat) numberFormats.get(pattern);
  806           if(format != null)
  807           {
  808               return format;
  809           }
  810   
  811           // Get format from global format cache
  812           synchronized(localizedNumberFormats)
  813           {
  814               Locale locale = getLocale();
  815               NumberFormatKey fk = new NumberFormatKey(pattern, locale);
  816               format = (NumberFormat)localizedNumberFormats.get(fk);
  817               if(format == null)
  818               {
  819                   // Add format to global format cache. Note this is
  820                   // globally done once per locale per pattern.
  821                   if("number".equals(pattern))
  822                   {
  823                       format = NumberFormat.getNumberInstance(locale);
  824                   }
  825                   else if("currency".equals(pattern))
  826                   {
  827                       format = NumberFormat.getCurrencyInstance(locale);
  828                   }
  829                   else if("percent".equals(pattern))
  830                   {
  831                       format = NumberFormat.getPercentInstance(locale);
  832                   }
  833                   else
  834                   {
  835                       format = new DecimalFormat(pattern, new DecimalFormatSymbols(getLocale()));
  836                   }
  837                   localizedNumberFormats.put(fk, format);
  838               }
  839           }
  840   
  841           // Clone it and store the clone in the local cache
  842           format = (NumberFormat)format.clone();
  843           numberFormats.put(pattern, format);
  844           return format;
  845       }
  846   
  847       DateFormat getDateFormatObject(int dateType)
  848       throws
  849           TemplateModelException
  850       {
  851           switch(dateType) {
  852               case TemplateDateModel.UNKNOWN: {
  853                   return null;
  854               }
  855               case TemplateDateModel.TIME: {
  856                   if(timeFormat == null) {
  857                       timeFormat = getDateFormatObject(dateType, getTimeFormat());
  858                   }
  859                   return timeFormat;
  860               }
  861               case TemplateDateModel.DATE: {
  862                   if(dateFormat == null) {
  863                       dateFormat = getDateFormatObject(dateType, getDateFormat());
  864                   }
  865                   return dateFormat;
  866               }
  867               case TemplateDateModel.DATETIME: {
  868                   if(dateTimeFormat == null) {
  869                       dateTimeFormat = getDateFormatObject(dateType, getDateTimeFormat());
  870                   }
  871                   return dateTimeFormat;
  872               }
  873               default: {
  874                   throw new TemplateModelException("Unrecognized date type " + dateType);
  875               }
  876           }
  877       }
  878       
  879       DateFormat getDateFormatObject(int dateType, String pattern)
  880       throws
  881           TemplateModelException
  882       {
  883           if(dateFormats == null) {
  884               dateFormats = new Map[4];
  885               dateFormats[TemplateDateModel.UNKNOWN] = new HashMap();
  886               dateFormats[TemplateDateModel.TIME] = new HashMap();
  887               dateFormats[TemplateDateModel.DATE] = new HashMap();
  888               dateFormats[TemplateDateModel.DATETIME] = new HashMap();
  889           }
  890           Map typedDateFormat = dateFormats[dateType];
  891   
  892           DateFormat format = (DateFormat) typedDateFormat.get(pattern);
  893           if(format != null) {
  894               return format;
  895           }
  896   
  897           // Get format from global format cache
  898           synchronized(localizedDateFormats) {
  899               Locale locale = getLocale();
  900               TimeZone timeZone = getTimeZone();
  901               DateFormatKey fk = new DateFormatKey(dateType, pattern, locale, timeZone);
  902               format = (DateFormat)localizedDateFormats.get(fk);
  903               if(format == null) {
  904                   // Add format to global format cache. Note this is
  905                   // globally done once per locale per pattern.
  906                   StringTokenizer tok = new StringTokenizer(pattern, "_");
  907                   int style = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : DateFormat.DEFAULT;
  908                   if(style != -1) {
  909                       switch(dateType) {
  910                           case TemplateDateModel.UNKNOWN: {
  911                               throw new TemplateModelException(
  912                                   "Can't convert the date to string using a " +
  913                                   "built-in format, because it is not known which " +
  914                                   "parts of the date variable are in use. Use " +
  915                                   "?date, ?time or ?datetime built-in, or " +
  916                                   "?string.<format> or ?string(<format>) built-in "+
  917                                   "with explicit formatting pattern with this date.");
  918                           }
  919                           case TemplateDateModel.TIME: {
  920                               format = DateFormat.getTimeInstance(style, locale);
  921                               break;
  922                           }
  923                           case TemplateDateModel.DATE: {
  924                               format = DateFormat.getDateInstance(style, locale);
  925                               break;
  926                           }
  927                           case TemplateDateModel.DATETIME: {
  928                               int timestyle = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : style;
  929                               if(timestyle != -1) {
  930                                   format = DateFormat.getDateTimeInstance(style, timestyle, locale);
  931                               }
  932                               break;
  933                           }
  934                       }
  935                   }
  936                   if(format == null) {
  937                       try {
  938                           format = new SimpleDateFormat(pattern, locale);
  939                       }
  940                       catch(IllegalArgumentException e) {
  941                           throw new TemplateModelException("Can't parse " + pattern + " to a date format.", e);
  942                       }
  943                   }
  944                   format.setTimeZone(timeZone);
  945                   localizedDateFormats.put(fk, format);
  946               }
  947           }
  948   
  949           // Clone it and store the clone in the local cache
  950           format = (DateFormat)format.clone();
  951           typedDateFormat.put(pattern, format);
  952           return format;
  953       }
  954   
  955       int parseDateStyleToken(String token) {
  956           if("short".equals(token)) {
  957               return DateFormat.SHORT;
  958           }
  959           if("medium".equals(token)) {
  960               return DateFormat.MEDIUM;
  961           }
  962           if("long".equals(token)) {
  963               return DateFormat.LONG;
  964           }
  965           if("full".equals(token)) {
  966               return DateFormat.FULL;
  967           }
  968           return -1;
  969       }
  970   
  971       /**
  972        * Returns the {@link NumberFormat} used for the <tt>c</tt> built-in.
  973        * This is always US English <code>"0.################"</code>, without
  974        * grouping and without superfluous decimal separator.
  975        */
  976       public NumberFormat getCNumberFormat() {
  977           // It can't be cached in a static field, because DecimalFormat-s aren't
  978           // thread-safe.
  979           if (cNumberFormat == null) {
  980               cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT.clone();
  981           }
  982           return cNumberFormat;
  983       }
  984   
  985       TemplateTransformModel getTransform(Expression exp) throws TemplateException {
  986           TemplateTransformModel ttm = null;
  987           TemplateModel tm = exp.getAsTemplateModel(this);
  988           if (tm instanceof TemplateTransformModel) {
  989               ttm = (TemplateTransformModel) tm;
  990           }
  991           else if (exp instanceof Identifier) {
  992               tm = getConfiguration().getSharedVariable(exp.toString());
  993               if (tm instanceof TemplateTransformModel) {
  994                   ttm = (TemplateTransformModel) tm;
  995               }
  996           }
  997           return ttm;
  998       }
  999   
 1000       /**
 1001        * Returns the loop or macro local variable corresponding to this
 1002        * variable name. Possibly null.
 1003        * (Note that the misnomer is kept for backward compatibility: loop variables
 1004        * are not local variables according to our terminology.)
 1005        */
 1006       public TemplateModel getLocalVariable(String name) throws TemplateModelException {
 1007           if (localContextStack != null) {
 1008               for (int i = localContextStack.size()-1; i>=0; i--) {
 1009                   LocalContext lc = (LocalContext) localContextStack.get(i);
 1010                   TemplateModel tm = lc.getLocalVariable(name);
 1011                   if (tm != null) {
 1012                       return tm;
 1013                   }
 1014               }
 1015           }
 1016           return currentMacroContext == null ? null : currentMacroContext.getLocalVariable(name);
 1017       }
 1018   
 1019       /**
 1020        * Returns the variable that is visible in this context.
 1021        * This is the correspondent to an FTL top-level variable reading expression.
 1022        * That is, it tries to find the the variable in this order:
 1023        * <ol>
 1024        *   <li>An loop variable (if we're in a loop or user defined directive body) such as foo_has_next
 1025        *   <li>A local variable (if we're in a macro)
 1026        *   <li>A variable defined in the current namespace (say, via &lt;#assign ...&gt;)
 1027        *   <li>A variable defined globally (say, via &lt;#global ....&gt;)
 1028        *   <li>Variable in the data model:
 1029        *     <ol>
 1030        *       <li>A variable in the root hash that was exposed to this
 1031                    rendering environment in the Template.process(...) call
 1032        *       <li>A shared variable set in the configuration via a call to Configuration.setSharedVariable(...)
 1033        *     </ol>
 1034        *   </li>
 1035        * </ol>
 1036        */
 1037       public TemplateModel getVariable(String name) throws TemplateModelException {
 1038           TemplateModel result = getLocalVariable(name);
 1039           if (result == null) {
 1040               result = currentNamespace.get(name);
 1041           }
 1042           if (result == null) {
 1043               result = getGlobalVariable(name);
 1044           }
 1045           return result;
 1046       }
 1047   
 1048       /**
 1049        * Returns the globally visible variable of the given name (or null).
 1050        * This is correspondent to FTL <code>.globals.<i>name</i></code>.
 1051        * This will first look at variables that were assigned globally via:
 1052        * &lt;#global ...&gt; and then at the data model exposed to the template.
 1053        */
 1054       public TemplateModel getGlobalVariable(String name) throws TemplateModelException {
 1055           TemplateModel result = globalNamespace.get(name);
 1056           if (result == null) {
 1057               result = rootDataModel.get(name);
 1058           }
 1059           if (result == null) {
 1060               result = getConfiguration().getSharedVariable(name);
 1061           }
 1062           return result;
 1063       }
 1064   
 1065       /**
 1066        * Sets a variable that is visible globally.
 1067        * This is correspondent to FTL <code><#global <i>name</i>=<i>model</i>></code>.
 1068        * This can be considered a convenient shorthand for:
 1069        * getGlobalNamespace().put(name, model)
 1070        */
 1071       public void setGlobalVariable(String name, TemplateModel model) {
 1072           globalNamespace.put(name, model);
 1073       }
 1074   
 1075       /**
 1076        * Sets a variable in the current namespace.
 1077        * This is correspondent to FTL <code><#assign <i>name</i>=<i>model</i>></code>.
 1078        * This can be considered a convenient shorthand for:
 1079        * getCurrentNamespace().put(name, model)
 1080        */
 1081       public void setVariable(String name, TemplateModel model) {
 1082           currentNamespace.put(name, model);
 1083       }
 1084   
 1085       /**
 1086        * Sets a local variable (one effective only during a macro invocation).
 1087        * This is correspondent to FTL <code><#local <i>name</i>=<i>model</i>></code>.
 1088        * @param name the identifier of the variable
 1089        * @param model the value of the variable.
 1090        * @throws IllegalStateException if the environment is not executing a
 1091        * macro body.
 1092        */
 1093       public void setLocalVariable(String name, TemplateModel model) {
 1094           if(currentMacroContext == null) {
 1095               throw new IllegalStateException("Not executing macro body");
 1096           }
 1097           currentMacroContext.setLocalVar(name, model);
 1098       }
 1099   
 1100       /**
 1101        * Returns a set of variable names that are known at the time of call. This
 1102        * includes names of all shared variables in the {@link Configuration},
 1103        * names of all global variables that were assigned during the template processing,
 1104        * names of all variables in the current name-space, names of all local variables
 1105        * and loop variables. If the passed root data model implements the
 1106        * {@link TemplateHashModelEx} interface, then all names it retrieves through a call to
 1107        * {@link TemplateHashModelEx#keys()} method are returned as well.
 1108        * The method returns a new Set object on each call that is completely
 1109        * disconnected from the Environment. That is, modifying the set will have
 1110        * no effect on the Environment object.
 1111        */
 1112       public Set getKnownVariableNames() throws TemplateModelException {
 1113           // shared vars.
 1114           Set set = getConfiguration().getSharedVariableNames();
 1115           
 1116           // root hash
 1117           if (rootDataModel instanceof TemplateHashModelEx) {
 1118               TemplateModelIterator rootNames =
 1119                   ((TemplateHashModelEx) rootDataModel).keys().iterator();
 1120               while(rootNames.hasNext()) {
 1121                   set.add(((TemplateScalarModel)rootNames.next()).getAsString());
 1122               }
 1123           }
 1124           
 1125           // globals
 1126           for (TemplateModelIterator tmi = globalNamespace.keys().iterator(); tmi.hasNext();) {
 1127               set.add(((TemplateScalarModel) tmi.next()).getAsString());
 1128           }
 1129           
 1130           // current name-space
 1131           for (TemplateModelIterator tmi = currentNamespace.keys().iterator(); tmi.hasNext();) {
 1132               set.add(((TemplateScalarModel) tmi.next()).getAsString());
 1133           }
 1134           
 1135           // locals and loop vars
 1136           if(currentMacroContext != null) {
 1137               set.addAll(currentMacroContext.getLocalVariableNames());
 1138           }
 1139           if (localContextStack != null) {
 1140               for (int i = localContextStack.size()-1; i>=0; i--) {
 1141                   LocalContext lc = (LocalContext) localContextStack.get(i);
 1142                   set.addAll(lc.getLocalVariableNames());
 1143               }
 1144           }
 1145           return set;
 1146       }
 1147   
 1148       /**
 1149        * Outputs the instruction stack. Useful for debugging.
 1150        * {@link TemplateException}s incorporate this information in their stack
 1151        * traces.
 1152        */
 1153       public void outputInstructionStack(PrintWriter pw) {
 1154           pw.println("----------");
 1155           ListIterator iter = elementStack.listIterator(elementStack.size());
 1156           if(iter.hasPrevious()) {
 1157               pw.print("==> ");
 1158               TemplateElement prev = (TemplateElement) iter.previous();
 1159               pw.print(prev.getDescription());
 1160               pw.print(" [");
 1161               pw.print(prev.getStartLocation());
 1162               pw.println("]");
 1163           }
 1164           while(iter.hasPrevious()) {
 1165               TemplateElement prev = (TemplateElement) iter.previous();
 1166               if (prev instanceof UnifiedCall || prev instanceof Include) {
 1167                   String location = prev.getDescription() + " [" + prev.getStartLocation() + "]";
 1168                   if(location != null && location.length() > 0) {
 1169                       pw.print(" in ");
 1170                       pw.println(location);
 1171                   }
 1172               }
 1173           }
 1174           pw.println("----------");
 1175           pw.flush();
 1176       }
 1177   
 1178       private void pushLocalContext(LocalContext localContext) {
 1179           if (localContextStack == null) {
 1180               localContextStack = new ArrayList();
 1181           }
 1182           localContextStack.add(localContext);
 1183       }
 1184   
 1185       private void popLocalContext() {
 1186           localContextStack.remove(localContextStack.size() - 1);
 1187       }
 1188       
 1189       ArrayList getLocalContextStack() {
 1190           return localContextStack;
 1191       }
 1192   
 1193       /**
 1194        * Returns the name-space for the name if exists, or null.
 1195        * @param name the template path that you have used with the <code>import</code> directive
 1196        *     or {@link #importLib(String, String)} call, in normalized form. That is, the path must be an absolute
 1197        *     path, and it must not contain "/../" or "/./". The leading "/" is optional.
 1198        */
 1199       public Namespace getNamespace(String name) {
 1200           if (name.startsWith("/")) name = name.substring(1);
 1201           if (loadedLibs != null) {
 1202               return (Namespace) loadedLibs.get(name);
 1203           } else {
 1204               return null;
 1205           }
 1206       }
 1207   
 1208       /**
 1209        * Returns the main name-space.
 1210        * This is correspondent of FTL <code>.main</code> hash.
 1211        */
 1212       public Namespace getMainNamespace() {
 1213           return mainNamespace;
 1214       }
 1215   
 1216       /**
 1217        * Returns the main name-space.
 1218        * This is correspondent of FTL <code>.namespace</code> hash.
 1219        */
 1220       public Namespace getCurrentNamespace() {
 1221           return currentNamespace;
 1222       }
 1223       
 1224       /**
 1225        * Returns a fictitious name-space that contains the globally visible variables
 1226        * that were created in the template, but not the variables of the data-model.
 1227        * There is no such thing in FTL; this strange method was added because of the
 1228        * JSP taglib support, since this imaginary name-space contains the page-scope
 1229        * attributes.
 1230        */
 1231       public Namespace getGlobalNamespace() {
 1232           return globalNamespace;
 1233       }
 1234       
 1235       
 1236       public TemplateHashModel getDataModel() {
 1237       	final TemplateHashModel result = new TemplateHashModel() {
 1238               public boolean isEmpty() {
 1239                   return false;
 1240               }
 1241   
 1242               public TemplateModel get(String key) throws TemplateModelException {
 1243                   TemplateModel value = rootDataModel.get(key);
 1244                   if (value == null) {
 1245                       value = getConfiguration().getSharedVariable(key);
 1246                   }
 1247                   return value;
 1248               }
 1249           };
 1250           
 1251           if (rootDataModel instanceof TemplateHashModelEx) {
 1252           	return new TemplateHashModelEx() {
 1253           		public boolean isEmpty() throws TemplateModelException {
 1254           			return result.isEmpty();
 1255           		}
 1256           		public TemplateModel get(String key) throws TemplateModelException {
 1257           			return result.get(key);
 1258           		}
 1259           		
 1260           		//NB: The methods below do not take into account
 1261           		// configuration shared variables even though
 1262           		// the hash will return them, if only for BWC reasons
 1263           		public TemplateCollectionModel values() throws TemplateModelException {
 1264           			return ((TemplateHashModelEx) rootDataModel).values();
 1265           		}
 1266           		public TemplateCollectionModel keys() throws TemplateModelException {
 1267           			return ((TemplateHashModelEx) rootDataModel).keys();
 1268           		}
 1269           		public int size() throws TemplateModelException {
 1270           			return ((TemplateHashModelEx) rootDataModel).size();
 1271           		}
 1272           	};
 1273           }
 1274           return result;
 1275       }
 1276   
 1277    
 1278       /**
 1279        * Returns the read-only hash of globally visible variables.
 1280        * This is the correspondent of FTL <code>.globals</code> hash.
 1281        * That is, you see the variables created with
 1282        * <code>&lt;#global ...></code>, and the variables of the data-model.
 1283        * To create new global variables, use {@link #setGlobalVariable setGlobalVariable}.
 1284        */
 1285       public TemplateHashModel getGlobalVariables() {
 1286           return new TemplateHashModel() {
 1287               public boolean isEmpty() {
 1288                   return false;
 1289               }
 1290               public TemplateModel get(String key) throws TemplateModelException {
 1291                   TemplateModel result = globalNamespace.get(key);
 1292                   if (result == null) {
 1293                       result = rootDataModel.get(key);
 1294                   }
 1295                   if (result == null) {
 1296                       result = getConfiguration().getSharedVariable(key);
 1297                   }
 1298                   return result;
 1299               }
 1300           };
 1301       }
 1302   
 1303       private void pushElement(TemplateElement element) {
 1304           elementStack.add(element);
 1305       }
 1306   
 1307       private void popElement() {
 1308           elementStack.remove(elementStack.size() - 1);
 1309       }
 1310       
 1311       public TemplateNodeModel getCurrentVisitorNode() {
 1312           return currentVisitorNode;
 1313       }
 1314       
 1315       /**
 1316        * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt>
 1317        */
 1318       public void setCurrentVisitorNode(TemplateNodeModel node) {
 1319           currentVisitorNode = node;
 1320       }
 1321       
 1322       TemplateModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
 1323           String nodeName = node.getNodeName();
 1324           if (nodeName == null) {
 1325               throw new TemplateException("Node name is null.", this);
 1326           }
 1327           TemplateModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
 1328       
 1329           if (result == null) {
 1330               String type = node.getNodeType();
 1331           
 1332               /* DD: Original version: */
 1333               if (type == null) {
 1334                   type = "default";
 1335               }
 1336               result = getNodeProcessor("@" + type, null, 0);
 1337           
 1338               /* DD: Jonathan's non-BC version and IMHO otherwise wrong version:
 1339               if (type != null) {
 1340                   result = getNodeProcessor("@" + type, null, 0);
 1341               }
 1342               if (result == null) {
 1343                   result = getNodeProcessor("@default", null, 0);
 1344               }
 1345               */
 1346           }
 1347           return result;    
 1348       }
 1349       
 1350       private TemplateModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex) 
 1351       throws TemplateException 
 1352       {
 1353           TemplateModel result = null;
 1354           int i;
 1355           for (i = startIndex; i<nodeNamespaces.size(); i++) {
 1356               Namespace ns = null;
 1357               try {                                   
 1358                   ns = (Namespace) nodeNamespaces.get(i);
 1359               } catch (ClassCastException cce) {
 1360                   throw new InvalidReferenceException("A using clause should contain a sequence of namespaces or strings that indicate the location of importable macro libraries.", this);
 1361               }
 1362               result = getNodeProcessor(ns, nodeName, nsURI);
 1363               if (result != null) 
 1364                   break;
 1365           }
 1366           if (result != null) {
 1367               this.nodeNamespaceIndex = i+1;
 1368               this.currentNodeName = nodeName;
 1369               this.currentNodeNS = nsURI;
 1370           }
 1371           return result;
 1372       }
 1373       
 1374       private TemplateModel getNodeProcessor(Namespace ns, String localName, String nsURI) throws TemplateException {
 1375           TemplateModel result = null;
 1376           if (nsURI == null) {
 1377               result = ns.get(localName);
 1378               if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
 1379                   result = null;
 1380               }
 1381           } else {
 1382               Template template = ns.getTemplate();
 1383               String prefix = template.getPrefixForNamespace(nsURI);
 1384               if (prefix == null) {
 1385                   // The other template cannot handle this node
 1386                   // since it has no prefix registered for the namespace
 1387                   return null;
 1388               }
 1389               if (prefix.length() >0) {
 1390                   result = ns.get(prefix + ":" + localName);
 1391                   if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
 1392                       result = null;
 1393                   }
 1394               } else {
 1395                   if (nsURI.length() == 0) {
 1396                       result = ns.get(Template.NO_NS_PREFIX + ":" + localName);
 1397                       if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
 1398                           result = null;
 1399                       }
 1400                   }
 1401                   if (nsURI.equals(template.getDefaultNS())) {
 1402                       result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName);
 1403                       if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
 1404                           result = null;
 1405                       }
 1406                   }
 1407                   if (result == null) {
 1408                       result = ns.get(localName);
 1409                       if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
 1410                           result = null;
 1411                       }
 1412                   }
 1413               }
 1414           }
 1415           return result;
 1416       }
 1417       
 1418       /**
 1419        * Emulates <code>include</code> directive, except that <code>name</code> must be tempate
 1420        * root relative.
 1421        *
 1422        * <p>It's the same as <code>include(getTemplateForInclusion(name, encoding, parse))</code>.
 1423        * But, you may want to separately call these two methods, so you can determine the source of
 1424        * exceptions more precisely, and thus achieve more intelligent error handling.
 1425        *
 1426        * @see #getTemplateForInclusion(String name, String encoding, boolean parse)
 1427        * @see #include(Template includedTemplate)
 1428        */
 1429       public void include(String name, String encoding, boolean parse)
 1430       throws IOException, TemplateException
 1431       {
 1432           include(getTemplateForInclusion(name, encoding, parse));
 1433       }
 1434   
 1435       /**
 1436        * Gets a template for inclusion; used with {@link #include(Template includedTemplate)}.
 1437        * The advantage over simply using <code>config.getTemplate(...)</code> is that it chooses
 1438        * the default encoding as the <code>include</code> directive does.
 1439        *
 1440        * @param name the name of the template, relatively to the template root directory
 1441        * (not the to the directory of the currently executing template file!).
 1442        * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
 1443        * to convert paths to template root relative paths.)
 1444        * @param encoding the encoding of the obtained template. If null,
 1445        * the encoding of the Template that is currently being processed in this
 1446        * Environment is used.
 1447        * @param parse whether to process a parsed template or just include the
 1448        * unparsed template source.
 1449        */
 1450       public Template getTemplateForInclusion(String name, String encoding, boolean parse)
 1451       throws IOException
 1452       {
 1453           if (encoding == null) {
 1454               encoding = getTemplate().getEncoding();
 1455           }
 1456           if (encoding == null) {
 1457               encoding = getConfiguration().getEncoding(this.getLocale());
 1458           }
 1459           return getConfiguration().getTemplate(name, getLocale(), encoding, parse);
 1460       }
 1461   
 1462       /**
 1463        * Processes a Template in the context of this <code>Environment</code>, including its
 1464        * output in the <code>Environment</code>'s Writer.
 1465        *
 1466        * @param includedTemplate the template to process. Note that it does <em>not</em> need
 1467        * to be a template returned by
 1468        * {@link #getTemplateForInclusion(String name, String encoding, boolean parse)}.
 1469        */
 1470       public void include(Template includedTemplate)
 1471       throws TemplateException, IOException
 1472       {
 1473           Template prevTemplate = getTemplate();
 1474           setParent(includedTemplate);
 1475           importMacros(includedTemplate);
 1476           try {
 1477               visit(includedTemplate.getRootTreeNode());
 1478           }
 1479           finally {
 1480               setParent(prevTemplate);
 1481           }
 1482       }
 1483       
 1484       /**
 1485        * Emulates <code>import</code> directive, except that <code>name</code> must be tempate
 1486        * root relative.
 1487        *
 1488        * <p>It's the same as <code>importLib(getTemplateForImporting(name), namespace)</code>.
 1489        * But, you may want to separately call these two methods, so you can determine the source of
 1490        * exceptions more precisely, and thus achieve more intelligent error handling.
 1491        *
 1492        * @see #getTemplateForImporting(String name)
 1493        * @see #importLib(Template includedTemplate, String namespace)
 1494        */
 1495       public Namespace importLib(String name, String namespace)
 1496       throws IOException, TemplateException
 1497       {
 1498           return importLib(getTemplateForImporting(name), namespace);
 1499       }
 1500   
 1501       /**
 1502        * Gets a template for importing; used with
 1503        * {@link #importLib(Template importedTemplate, String namespace)}. The advantage
 1504        * over simply using <code>config.getTemplate(...)</code> is that it chooses the encoding
 1505        * as the <code>import</code> directive does.
 1506        *
 1507        * @param name the name of the template, relatively to the template root directory
 1508        * (not the to the directory of the currently executing template file!).
 1509        * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
 1510        * to convert paths to template root relative paths.)
 1511        */
 1512       public Template getTemplateForImporting(String name) throws IOException {
 1513           return getTemplateForInclusion(name, null, true);
 1514       }
 1515       
 1516       /**
 1517        * Emulates <code>import</code> directive.
 1518        *
 1519        * @param loadedTemplate the template to import. Note that it does <em>not</em> need
 1520        * to be a template returned by {@link #getTemplateForImporting(String name)}.
 1521        */
 1522       public Namespace importLib(Template loadedTemplate, String namespace)
 1523       throws IOException, TemplateException
 1524       {
 1525           if (loadedLibs == null) {
 1526               loadedLibs = new HashMap();
 1527           }
 1528           String templateName = loadedTemplate.getName();
 1529           Namespace existingNamespace = (Namespace) loadedLibs.get(templateName);
 1530           if (existingNamespace != null) {
 1531               if (namespace != null) {
 1532                   setVariable(namespace, existingNamespace);
 1533               }
 1534           }
 1535           else {
 1536               Namespace newNamespace = new Namespace(loadedTemplate);
 1537               if (namespace != null) {
 1538                   currentNamespace.put(namespace, newNamespace);
 1539                   if (currentNamespace == mainNamespace) {
 1540                       globalNamespace.put(namespace, newNamespace);
 1541                   }
 1542               }
 1543               Namespace prevNamespace = this.currentNamespace;
 1544               this.currentNamespace = newNamespace;
 1545               loadedLibs.put(templateName, currentNamespace);
 1546               Writer prevOut = out;
 1547               this.out = NULL_WRITER;
 1548               try {
 1549                   include(loadedTemplate);
 1550               } finally {
 1551                   this.out = prevOut;
 1552                   this.currentNamespace = prevNamespace;
 1553               }
 1554           }
 1555           return (Namespace) loadedLibs.get(templateName);
 1556       }
 1557       
 1558       String renderElementToString(TemplateElement te) throws IOException, TemplateException {
 1559           Writer prevOut = out;
 1560           try {
 1561               StringWriter sw = new StringWriter();
 1562               this.out = sw;
 1563               visit(te);
 1564               return sw.toString();
 1565           } 
 1566           finally {
 1567               this.out = prevOut;
 1568           }
 1569       }
 1570   
 1571       void importMacros(Template template) {
 1572           for (Iterator it = template.getMacros().values().iterator(); it.hasNext();) {
 1573               visitMacroDef((Macro) it.next());
 1574           }
 1575       }
 1576   
 1577       /**
 1578        * @return the namespace URI registered for this prefix, or null.
 1579        * This is based on the mappings registered in the current namespace.
 1580        */
 1581       public String getNamespaceForPrefix(String prefix) {
 1582           return currentNamespace.getTemplate().getNamespaceForPrefix(prefix);
 1583       }
 1584       
 1585       public String getPrefixForNamespace(String nsURI) {
 1586           return currentNamespace.getTemplate().getPrefixForNamespace(nsURI);
 1587       }
 1588       
 1589       /**
 1590        * @return the default node namespace for the current FTL namespace
 1591        */
 1592       public String getDefaultNS() {
 1593           return currentNamespace.getTemplate().getDefaultNS();
 1594       }
 1595       
 1596       /**
 1597        * A hook that Jython uses.
 1598        */
 1599       public Object __getitem__(String key) throws TemplateModelException {
 1600           return BeansWrapper.getDefaultInstance().unwrap(getVariable(key));
 1601       }
 1602   
 1603       /**
 1604        * A hook that Jython uses.
 1605        */
 1606       public void __setitem__(String key, Object o) throws TemplateException {
 1607           setGlobalVariable(key, getObjectWrapper().wrap(o));
 1608       }
 1609   
 1610       private static final class NumberFormatKey
 1611       {
 1612           private final String pattern;
 1613           private final Locale locale;
 1614   
 1615           NumberFormatKey(String pattern, Locale locale)
 1616           {
 1617               this.pattern = pattern;
 1618               this.locale = locale;
 1619           }
 1620   
 1621           public boolean equals(Object o)
 1622           {
 1623               if(o instanceof NumberFormatKey)
 1624               {
 1625                   NumberFormatKey fk = (NumberFormatKey)o;
 1626                   return fk.pattern.equals(pattern) && fk.locale.equals(locale);
 1627               }
 1628               return false;
 1629           }
 1630   
 1631           public int hashCode()
 1632           {
 1633               return pattern.hashCode() ^ locale.hashCode();
 1634           }
 1635       }
 1636   
 1637       private static final class DateFormatKey
 1638       {
 1639           private final int dateType;
 1640           private final String pattern;
 1641           private final Locale locale;
 1642           private final TimeZone timeZone;
 1643   
 1644           DateFormatKey(int dateType, String pattern, Locale locale, TimeZone timeZone)
 1645           {
 1646               this.dateType = dateType;
 1647               this.pattern = pattern;
 1648               this.locale = locale;
 1649               this.timeZone = timeZone;
 1650           }
 1651   
 1652           public boolean equals(Object o)
 1653           {
 1654               if(o instanceof DateFormatKey)
 1655               {
 1656                   DateFormatKey fk = (DateFormatKey)o;
 1657                   return dateType == fk.dateType && fk.pattern.equals(pattern) && fk.locale.equals(locale) && fk.timeZone.equals(timeZone);
 1658               }
 1659               return false;
 1660           }
 1661   
 1662           public int hashCode()
 1663           {
 1664               return dateType ^ pattern.hashCode() ^ locale.hashCode() ^ timeZone.hashCode();
 1665           }
 1666       }
 1667       
 1668       public class Namespace extends SimpleHash {
 1669           
 1670           private Template template;
 1671           
 1672           Namespace() {
 1673               this.template = Environment.this.getTemplate();
 1674           }
 1675           
 1676           Namespace(Template template) {
 1677               this.template = template;
 1678           }
 1679           
 1680           /**
 1681            * @return the Template object with which this Namespace is associated.
 1682            */
 1683           public Template getTemplate() {
 1684               return template == null ? Environment.this.getTemplate() : template;
 1685           }
 1686       }
 1687   
 1688       static final Writer NULL_WRITER = new Writer() {
 1689               public void write(char cbuf[], int off, int len) {}
 1690               public void flush() {}
 1691               public void close() {}
 1692        };
 1693        
 1694        private static final Writer EMPTY_BODY_WRITER = new Writer() {
 1695       
 1696           public void write(char[] cbuf, int off, int len) throws IOException {
 1697               if (len > 0) {
 1698                   throw new IOException(
 1699                           "This transform does not allow nested content.");
 1700               }
 1701           }
 1702       
 1703           public void flush() {
 1704           }
 1705       
 1706           public void close() {
 1707           }
 1708       };
 1709       
 1710   }

Save This Page
Home » freemarker-2.3.13 » freemarker.core » [javadoc | source]