KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > core > Environment


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 JavaDoc threadEnv = new ThreadLocal JavaDoc();
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     private final TemplateHashModel rootDataModel;
92     private final ArrayList elementStack = new ArrayList();
93     private final ArrayList recoveredErrorStack = new ArrayList();
94
95     private NumberFormat numberFormat;
96     private Map numberFormats;
97
98     private DateFormat timeFormat, dateFormat, dateTimeFormat;
99     private Map[] dateFormats;
100
101     private Collator collator;
102
103     private Writer out;
104     private Macro.Context currentMacroContext;
105     private ArrayList localContextStack;
106     private Namespace mainNamespace, currentNamespace, globalNamespace;
107     private HashMap loadedLibs;
108
109     private Throwable JavaDoc lastThrowable;
110     
111     private TemplateModel lastReturnValue;
112     private HashMap macroToNamespaceLookup = new HashMap();
113
114     private TemplateNodeModel currentVisitorNode;
115     private TemplateSequenceModel nodeNamespaces;
116     // Things we keep track of for the fallback mechanism.
117
private int nodeNamespaceIndex;
118     private String JavaDoc currentNodeName, currentNodeNS;
119     
120     private String JavaDoc cachedURLEscapingCharset;
121     private boolean urlEscapingCharsetCached;
122
123     /**
124      * Retrieves the environment object associated with the current
125      * thread. Data model implementations that need access to the
126      * environment can call this method to obtain the environment object
127      * that represents the template processing that is currently running
128      * on the current thread.
129      */

130     public static Environment getCurrentEnvironment()
131     {
132         return (Environment)threadEnv.get();
133     }
134
135     public Environment(Template template, final TemplateHashModel rootDataModel, Writer out)
136     {
137         super(template);
138         this.globalNamespace = new Namespace(null);
139         this.currentNamespace = mainNamespace = new Namespace(template);
140         this.out = out;
141         this.rootDataModel = rootDataModel;
142         importMacros(template);
143     }
144
145     /**
146      * Retrieves the currently processed template.
147      */

148     public Template getTemplate()
149     {
150         return (Template)getParent();
151     }
152
153     /**
154      * Deletes cached values that meant to be valid only during a single
155      * template execution.
156      */

157     private void clearCachedValues() {
158         numberFormats = null;
159         numberFormat = null;
160         dateFormats = null;
161         collator = null;
162         cachedURLEscapingCharset = null;
163         urlEscapingCharsetCached = false;
164     }
165     
166     /**
167      * Processes the template to which this environment belongs.
168      */

169     public void process() throws TemplateException, IOException {
170         Object JavaDoc savedEnv = threadEnv.get();
171         threadEnv.set(this);
172         try {
173             // Cached values from a previous execution are possibly outdated.
174
clearCachedValues();
175             try {
176                 visit(getTemplate().getRootTreeNode());
177                 // Do not flush if there was an exception.
178
out.flush();
179             } finally {
180                 // It's just to allow the GC to free memory...
181
clearCachedValues();
182             }
183         } finally {
184             threadEnv.set(savedEnv);
185         }
186     }
187     
188     /**
189      * "Visit" the template element.
190      */

191     void visit(TemplateElement element)
192     throws TemplateException, IOException
193     {
194         pushElement(element);
195         try {
196             element.accept(this);
197         }
198         catch (TemplateException te) {
199             handleTemplateException(te);
200         }
201         finally {
202             popElement();
203         }
204     }
205
206     /**
207      * "Visit" the template element, passing the output
208      * through a TemplateTransformModel
209      * @param element the element to visit through a transform
210      * @param transform the transform to pass the element output
211      * through
212      * @param args optional arguments fed to the transform
213      */

214     void visit(TemplateElement element,
215                TemplateTransformModel transform,
216                Map args)
217     throws TemplateException, IOException
218     {
219         try {
220             Writer tw = transform.getWriter(out, args);
221             if (tw == null) tw = EMPTY_BODY_WRITER;
222             TransformControl tc =
223                 tw instanceof TransformControl
224                 ? (TransformControl)tw
225                 : null;
226
227             Writer prevOut = out;
228             out = tw;
229             try {
230                 if(tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
231                     do {
232                         if(element != null) {
233                             visit(element);
234                         }
235                     } while(tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
236                 }
237             }
238             catch(Throwable JavaDoc t) {
239                 try {
240                     if(tc != null) {
241                         tc.onError(t);
242                     }
243                     else {
244                         throw t;
245                     }
246                 }
247                 catch(TemplateException e) {
248                     throw e;
249                 }
250                 catch(IOException e) {
251                     throw e;
252                 }
253                 catch(RuntimeException JavaDoc e) {
254                     throw e;
255                 }
256                 catch(Error JavaDoc e) {
257                     throw e;
258                 }
259                 catch(Throwable JavaDoc e) {
260                     throw new UndeclaredThrowableException(e);
261                 }
262             }
263             finally {
264                 out = prevOut;
265                 tw.close();
266             }
267         }
268         catch(TemplateException te) {
269             handleTemplateException(te);
270         }
271     }
272     
273     /**
274      * Visit a block using buffering/recovery
275      */

276     
277      void visit(TemplateElement attemptBlock, TemplateElement recoveryBlock)
278      throws TemplateException, IOException {
279          Writer prevOut = this.out;
280          StringWriter sw = new StringWriter();
281          this.out = sw;
282          TemplateException thrownException = null;
283          try {
284              visit(attemptBlock);
285          } catch (TemplateException te) {
286              thrownException = te;
287          } finally {
288              this.out = prevOut;
289          }
290          if (thrownException != null) {
291              if (logger.isErrorEnabled()) {
292                  String JavaDoc msg = "Error in attempt block " + attemptBlock.getStartLocation();
293                  logger.error(msg, thrownException);
294              }
295              try {
296                  recoveredErrorStack.add(thrownException.getMessage());
297                  visit(recoveryBlock);
298              } finally {
299                  recoveredErrorStack.remove(recoveredErrorStack.size() -1);
300              }
301          } else {
302              out.write(sw.toString());
303          }
304      }
305      
306      String JavaDoc getCurrentRecoveredErrorMesssage() throws TemplateException {
307          if(recoveredErrorStack.isEmpty()) {
308              throw new TemplateException(
309                  ".error is not available outside of a <#recover> block", this);
310          }
311          return (String JavaDoc) recoveredErrorStack.get(recoveredErrorStack.size() -1);
312      }
313
314
315     void visit(BodyInstruction.Context bctxt) throws TemplateException, IOException {
316         Macro.Context invokingMacroContext = getCurrentMacroContext();
317         ArrayList prevLocalContextStack = localContextStack;
318         TemplateElement body = invokingMacroContext.body;
319         if (body != null) {
320             this.currentMacroContext = invokingMacroContext.prevMacroContext;
321             currentNamespace = invokingMacroContext.bodyNamespace;
322             Configurable prevParent = getParent();
323             setParent(currentNamespace.getTemplate());
324             this.localContextStack = invokingMacroContext.prevLocalContextStack;
325             if (invokingMacroContext.bodyParameterNames != null) {
326                 pushLocalContext(bctxt);
327             }
328             try {
329                 visit(body);
330             }
331             finally {
332                 if (invokingMacroContext.bodyParameterNames != null) {
333                     popLocalContext();
334                 }
335                 this.currentMacroContext = invokingMacroContext;
336                 currentNamespace = getMacroNamespace(invokingMacroContext.getMacro());
337                 setParent(prevParent);
338                 this.localContextStack = prevLocalContextStack;
339             }
340         }
341     }
342
343     /**
344      * "visit" an IteratorBlock
345      */

346     void visit(IteratorBlock.Context ictxt)
347     throws TemplateException, IOException
348     {
349         pushLocalContext(ictxt);
350         try {
351             ictxt.runLoop(this);
352         }
353         catch (BreakInstruction.Break br) {
354         }
355         catch (TemplateException te) {
356             handleTemplateException(te);
357         }
358         finally {
359             popLocalContext();
360         }
361     }
362     
363     /**
364      * "Visit" A TemplateNodeModel
365      */

366     
367     void visit(TemplateNodeModel node, TemplateSequenceModel namespaces)
368     throws TemplateException, IOException
369     {
370         if (nodeNamespaces == null) {
371             SimpleSequence ss = new SimpleSequence(1);
372             ss.add(currentNamespace);
373             nodeNamespaces = ss;
374         }
375         int prevNodeNamespaceIndex = this.nodeNamespaceIndex;
376         String JavaDoc prevNodeName = this.currentNodeName;
377         String JavaDoc prevNodeNS = this.currentNodeNS;
378         TemplateSequenceModel prevNodeNamespaces = nodeNamespaces;
379         TemplateNodeModel prevVisitorNode = currentVisitorNode;
380         currentVisitorNode = node;
381         if (namespaces != null) {
382             this.nodeNamespaces = namespaces;
383         }
384         try {
385             TemplateModel macroOrTransform = getNodeProcessor(node);
386             if (macroOrTransform instanceof Macro) {
387                 visit((Macro) macroOrTransform, null, null, null, null);
388             }
389             else if (macroOrTransform instanceof TemplateTransformModel) {
390                 visit(null, (TemplateTransformModel) macroOrTransform, null);
391             }
392             else {
393                 String JavaDoc nodeType = node.getNodeType();
394                 if (nodeType != null) {
395                     // If the node's type is 'text', we just output it.
396
if ((nodeType.equals("text") && node instanceof TemplateScalarModel))
397                     {
398                            out.write(((TemplateScalarModel) node).getAsString());
399                     }
400                     else if (nodeType.equals("document")) {
401                         recurse(node, namespaces);
402                     }
403                     // We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
404
// we just ignore it.
405
else if (!nodeType.equals("pi")
406                          && !nodeType.equals("comment")
407                          && !nodeType.equals("document_type"))
408                     {
409                         String JavaDoc nsBit = "";
410                         String JavaDoc ns = node.getNodeNamespace();
411                         if (ns != null) {
412                             if (ns.length() >0) {
413                                 nsBit = " and namespace " + ns;
414                             } else {
415                                 nsBit = " and no namespace";
416                             }
417                         }
418                         throw new TemplateException("No macro or transform defined for node named "
419                                     + node.getNodeName() + nsBit
420                                     + ", and there is no fallback handler called @" + nodeType + " either.",
421                                     this);
422                     }
423                 }
424                 else {
425                     String JavaDoc nsBit = "";
426                     String JavaDoc ns = node.getNodeNamespace();
427                     if (ns != null) {
428                         if (ns.length() >0) {
429                             nsBit = " and namespace " + ns;
430                         } else {
431                             nsBit = " and no namespace";
432                         }
433                     }
434                     throw new TemplateException("No macro or transform defined for node with name "
435                                 + node.getNodeName() + nsBit
436                                 + ", and there is no macro or transform called @default either.",
437                                 this);
438                 }
439             }
440         }
441         finally {
442             this.currentVisitorNode = prevVisitorNode;
443             this.nodeNamespaceIndex = prevNodeNamespaceIndex;
444             this.currentNodeName = prevNodeName;
445             this.currentNodeNS = prevNodeNS;
446             this.nodeNamespaces = prevNodeNamespaces;
447         }
448     }
449     
450     void fallback() throws TemplateException, IOException {
451         TemplateModel macroOrTransform = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
452         if (macroOrTransform instanceof Macro) {
453             visit((Macro) macroOrTransform, null, null, null, null);
454         }
455         else if (macroOrTransform instanceof TemplateTransformModel) {
456             visit(null, (TemplateTransformModel) macroOrTransform, null);
457         }
458     }
459     
460     /**
461      * "visit" a macro.
462      */

463     
464     void visit(Macro macro,
465                Map namedArgs,
466                List positionalArgs,
467                List bodyParameterNames,
468                TemplateElement nestedBlock)
469        throws TemplateException, IOException
470     {
471         if (macro == Macro.DO_NOTHING_MACRO) {
472             return;
473         }
474         pushElement(macro);
475         try {
476             Macro.Context previousMacroContext = currentMacroContext;
477             Macro.Context mc = macro.new Context(this, nestedBlock, bodyParameterNames);
478
479             String JavaDoc catchAll = macro.getCatchAll();
480             TemplateModel unknownVars = null;
481             
482             if (namedArgs != null) {
483                 if (catchAll != null)
484                     unknownVars = new SimpleHash();
485                 for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext();) {
486                     Map.Entry entry = (Map.Entry) it.next();
487                     String JavaDoc varName = (String JavaDoc) entry.getKey();
488                     boolean hasVar = macro.hasArgNamed(varName);
489                     if (hasVar || catchAll != null) {
490                         Expression arg = (Expression) entry.getValue();
491                         TemplateModel value = arg.getAsTemplateModel(this);
492                         if (hasVar) {
493                             mc.setLocalVar(varName, value);
494                         } else {
495                             ((SimpleHash)unknownVars).put(varName, value);
496                         }
497                     } else {
498                         String JavaDoc msg = "Macro " + macro.getName() + " has no such argument: " + varName;
499                         throw new TemplateException(msg, this);
500                     }
501                 }
502             }
503             else if (positionalArgs != null) {
504                 if (catchAll != null)
505                     unknownVars = new SimpleSequence();
506                 String JavaDoc[] argumentNames = macro.getArgumentNames();
507                 int size = positionalArgs.size();
508                 if (argumentNames.length < size && catchAll == null) {
509                     throw new TemplateException("Macro " + macro.getName()
510                       + " only accepts " + argumentNames.length + " parameters.", this);
511                 }
512                 for (int i = 0; i < size; i++) {
513                     Expression argExp = (Expression) positionalArgs.get(i);
514                     TemplateModel argModel = argExp.getAsTemplateModel(this);
515                     try {
516                         if (i < argumentNames.length) {
517                             String JavaDoc argName = argumentNames[i];
518                             mc.setLocalVar(argName, argModel);
519                         } else {
520                             ((SimpleSequence)unknownVars).add(argModel);
521                         }
522                     } catch (RuntimeException JavaDoc re) {
523                         throw new TemplateException(re, this);
524                     }
525                 }
526             }
527             if (catchAll != null) {
528                 mc.setLocalVar(catchAll, unknownVars);
529             }
530             ArrayList prevLocalContextStack = localContextStack;
531             localContextStack = null;
532             Namespace prevNamespace = currentNamespace;
533             Configurable prevParent = getParent();
534             currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
535             currentMacroContext = mc;
536             try {
537                 mc.runMacro(this);
538             }
539             catch (ReturnInstruction.Return re) {
540             }
541             catch (TemplateException te) {
542                 handleTemplateException(te);
543             } finally {
544                 currentMacroContext = previousMacroContext;
545                 localContextStack = prevLocalContextStack;
546                 currentNamespace = prevNamespace;
547                 setParent(prevParent);
548             }
549         } finally {
550             popElement();
551         }
552     }
553     
554     void visitMacroDef(Macro macro) {
555         macroToNamespaceLookup.put(macro, currentNamespace);
556         currentNamespace.put(macro.getName(), macro);
557     }
558     
559     Namespace getMacroNamespace(Macro macro) {
560         return (Namespace) macroToNamespaceLookup.get(macro);
561     }
562     
563     void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces)
564     throws TemplateException, IOException
565     {
566         if (node == null) {
567             node = this.getCurrentVisitorNode();
568         }
569         TemplateSequenceModel children = node.getChildNodes();
570         if (children == null) return;
571         for (int i=0; i<children.size(); i++) {
572             TemplateNodeModel child = (TemplateNodeModel) children.get(i);
573             if (child != null) {
574                 visit(child, namespaces);
575             }
576         }
577     }
578
579     Macro.Context getCurrentMacroContext() {
580         return currentMacroContext;
581     }
582     
583     private void handleTemplateException(TemplateException te)
584         throws TemplateException
585     {
586         // Logic to prevent double-handling of the exception in
587
// nested visit() calls.
588
if(lastThrowable == te) {
589             throw te;
590         }
591         lastThrowable = te;
592
593         // Log the exception
594
if(logger.isErrorEnabled()) {
595             logger.error("", te);
596         }
597
598         // Stop exception is not passed to the handler, but
599
// explicitly rethrown.
600
if(te instanceof StopException) {
601             throw te;
602         }
603
604         // Finally, pass the exception to the handler
605
getTemplateExceptionHandler().handleTemplateException(te, this, out);
606     }
607
608     public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
609         super.setTemplateExceptionHandler(templateExceptionHandler);
610         lastThrowable = null;
611     }
612     
613     public void setLocale(Locale locale) {
614         super.setLocale(locale);
615         // Clear local format cache
616
numberFormats = null;
617         numberFormat = null;
618
619         dateFormats = null;
620         timeFormat = dateFormat = dateTimeFormat = null;
621
622         collator = null;
623     }
624
625     public void setTimeZone(TimeZone timeZone) {
626         super.setTimeZone(timeZone);
627         // Clear local date format cache
628
dateFormats = null;
629         timeFormat = dateFormat = dateTimeFormat = null;
630     }
631     
632     public void setURLEscapingCharset(String JavaDoc urlEscapingCharset) {
633         urlEscapingCharsetCached = false;
634         super.setURLEscapingCharset(urlEscapingCharset);
635     }
636     
637     /*
638      * Note that altough it is not allowed to set this setting with the
639      * <tt>setting</tt> directive, it still must be allowed to set it from Java
640      * code while the template executes, since some frameworks allow templates
641      * to actually change the output encoding on-the-fly.
642      */

643     public void setOutputEncoding(String JavaDoc outputEncoding) {
644         urlEscapingCharsetCached = false;
645         super.setOutputEncoding(outputEncoding);
646     }
647     
648     /**
649      * Returns the name of the charset that should be used for URL encoding.
650      * This will be <code>null</code> if the information is not available.
651      * The function caches the return value, so it is quick to call it
652      * repeately.
653      */

654     String JavaDoc getEffectiveURLEscapingCharset() {
655         if (!urlEscapingCharsetCached) {
656             cachedURLEscapingCharset = getURLEscapingCharset();
657             if (cachedURLEscapingCharset == null) {
658                 cachedURLEscapingCharset = getOutputEncoding();
659             }
660             urlEscapingCharsetCached = true;
661         }
662         return cachedURLEscapingCharset;
663     }
664
665     Collator getCollator() {
666         if(collator == null) {
667             collator = Collator.getInstance(getLocale());
668         }
669         return collator;
670     }
671
672     public void setOut(Writer out) {
673         this.out = out;
674     }
675
676     public Writer getOut() {
677         return out;
678     }
679
680     String JavaDoc formatNumber(Number JavaDoc number) {
681         if(numberFormat == null) {
682             numberFormat = getNumberFormatObject(getNumberFormat());
683         }
684         return numberFormat.format(number);
685     }
686
687     public void setNumberFormat(String JavaDoc formatName) {
688         super.setNumberFormat(formatName);
689         numberFormat = null;
690     }
691
692     String JavaDoc formatDate(Date date, int type) throws TemplateModelException {
693         DateFormat df = getDateFormatObject(type);
694         if(df == null) {
695             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.");
696         }
697         return df.format(date);
698     }
699
700     public void setTimeFormat(String JavaDoc formatName) {
701         super.setTimeFormat(formatName);
702         timeFormat = null;
703     }
704
705     public void setDateFormat(String JavaDoc formatName) {
706         super.setDateFormat(formatName);
707         dateFormat = null;
708     }
709
710     public void setDateTimeFormat(String JavaDoc formatName) {
711         super.setDateTimeFormat(formatName);
712         dateTimeFormat = null;
713     }
714
715     public Configuration getConfiguration() {
716         return getTemplate().getConfiguration();
717     }
718     
719     TemplateModel getLastReturnValue() {
720         return lastReturnValue;
721     }
722     
723     void setLastReturnValue(TemplateModel lastReturnValue) {
724         this.lastReturnValue = lastReturnValue;
725     }
726     
727     void clearLastReturnValue() {
728         this.lastReturnValue = null;
729     }
730
731     NumberFormat getNumberFormatObject(String JavaDoc pattern)
732     {
733         if(numberFormats == null) {
734             numberFormats = new HashMap();
735         }
736
737         NumberFormat format = (NumberFormat) numberFormats.get(pattern);
738         if(format != null)
739         {
740             return format;
741         }
742
743         // Get format from global format cache
744
synchronized(localizedNumberFormats)
745         {
746             Locale locale = getLocale();
747             NumberFormatKey fk = new NumberFormatKey(pattern, locale);
748             format = (NumberFormat)localizedNumberFormats.get(fk);
749             if(format == null)
750             {
751                 // Add format to global format cache. Note this is
752
// globally done once per locale per pattern.
753
if("number".equals(pattern))
754                 {
755                     format = NumberFormat.getNumberInstance(locale);
756                 }
757                 else if("currency".equals(pattern))
758                 {
759                     format = NumberFormat.getCurrencyInstance(locale);
760                 }
761                 else if("percent".equals(pattern))
762                 {
763                     format = NumberFormat.getPercentInstance(locale);
764                 }
765                 else
766                 {
767                     format = new DecimalFormat(pattern, new DecimalFormatSymbols(getLocale()));
768                 }
769                 localizedNumberFormats.put(fk, format);
770             }
771         }
772
773         // Clone it and store the clone in the local cache
774
format = (NumberFormat)format.clone();
775         numberFormats.put(pattern, format);
776         return format;
777     }
778
779     DateFormat getDateFormatObject(int dateType)
780     throws
781         TemplateModelException
782     {
783         switch(dateType) {
784             case TemplateDateModel.UNKNOWN: {
785                 return null;
786             }
787             case TemplateDateModel.TIME: {
788                 if(timeFormat == null) {
789                     timeFormat = getDateFormatObject(dateType, getTimeFormat());
790                 }
791                 return timeFormat;
792             }
793             case TemplateDateModel.DATE: {
794                 if(dateFormat == null) {
795                     dateFormat = getDateFormatObject(dateType, getDateFormat());
796                 }
797                 return dateFormat;
798             }
799             case TemplateDateModel.DATETIME: {
800                 if(dateTimeFormat == null) {
801                     dateTimeFormat = getDateFormatObject(dateType, getDateTimeFormat());
802                 }
803                 return dateTimeFormat;
804             }
805             default: {
806                 throw new TemplateModelException("Unrecognized date type " + dateType);
807             }
808         }
809     }
810     
811     DateFormat getDateFormatObject(int dateType, String JavaDoc pattern)
812     throws
813         TemplateModelException
814     {
815         if(dateFormats == null) {
816             dateFormats = new Map[4];
817             dateFormats[TemplateDateModel.UNKNOWN] = new HashMap();
818             dateFormats[TemplateDateModel.TIME] = new HashMap();
819             dateFormats[TemplateDateModel.DATE] = new HashMap();
820             dateFormats[TemplateDateModel.DATETIME] = new HashMap();
821         }
822         Map typedDateFormat = dateFormats[dateType];
823
824         DateFormat format = (DateFormat) typedDateFormat.get(pattern);
825         if(format != null) {
826             return format;
827         }
828
829         // Get format from global format cache
830
synchronized(localizedDateFormats) {
831             Locale locale = getLocale();
832             TimeZone timeZone = getTimeZone();
833             DateFormatKey fk = new DateFormatKey(dateType, pattern, locale, timeZone);
834             format = (DateFormat)localizedDateFormats.get(fk);
835             if(format == null) {
836                 // Add format to global format cache. Note this is
837
// globally done once per locale per pattern.
838
StringTokenizer tok = new StringTokenizer(pattern, "_");
839                 int style = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : DateFormat.DEFAULT;
840                 if(style != -1) {
841                     switch(dateType) {
842                         case TemplateDateModel.UNKNOWN: {
843                             throw new TemplateModelException(
844                                 "Can't convert the date to string using a " +
845                                 "built-in format, because it is not known which " +
846                                 "parts of the date variable are in use. Use " +
847                                 "?date, ?time or ?datetime built-in, or " +
848                                 "?string.<format> or ?string(<format>) built-in "+
849                                 "with explicit formatting pattern with this date.");
850                         }
851                         case TemplateDateModel.TIME: {
852                             format = DateFormat.getTimeInstance(style, locale);
853                             break;
854                         }
855                         case TemplateDateModel.DATE: {
856                             format = DateFormat.getDateInstance(style, locale);
857                             break;
858                         }
859                         case TemplateDateModel.DATETIME: {
860                             int timestyle = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : style;
861                             if(timestyle != -1) {
862                                 format = DateFormat.getDateTimeInstance(style, timestyle, locale);
863                             }
864                             break;
865                         }
866                     }
867                 }
868                 if(format == null) {
869                     try {
870                         format = new SimpleDateFormat(pattern, locale);
871                     }
872                     catch(IllegalArgumentException JavaDoc e) {
873                         throw new TemplateModelException("Can't parse " + pattern + " to a date format.", e);
874                     }
875                 }
876                 format.setTimeZone(timeZone);
877                 localizedDateFormats.put(fk, format);
878             }
879         }
880
881         // Clone it and store the clone in the local cache
882
format = (DateFormat)format.clone();
883         typedDateFormat.put(pattern, format);
884         return format;
885     }
886
887     int parseDateStyleToken(String JavaDoc token) {
888         if("short".equals(token)) {
889             return DateFormat.SHORT;
890         }
891         if("medium".equals(token)) {
892             return DateFormat.MEDIUM;
893         }
894         if("long".equals(token)) {
895             return DateFormat.LONG;
896         }
897         return -1;
898     }
899
900     TemplateTransformModel getTransform(Expression exp) throws TemplateException {
901         TemplateTransformModel ttm = null;
902         TemplateModel tm = exp.getAsTemplateModel(this);
903         if (tm instanceof TemplateTransformModel) {
904             ttm = (TemplateTransformModel) tm;
905         }
906         else if (exp instanceof Identifier) {
907             tm = getConfiguration().getSharedVariable(exp.toString());
908             if (tm instanceof TemplateTransformModel) {
909                 ttm = (TemplateTransformModel) tm;
910             }
911         }
912         return ttm;
913     }
914
915     /**
916      * Returns the loop or macro local variable corresponding to this
917      * variable name. Possibly null.
918      * (Note that the misnomer is kept for backward compatibility: loop variables
919      * are not local variables according to our terminology.)
920      */

921     public TemplateModel getLocalVariable(String JavaDoc name) throws TemplateModelException {
922         if (localContextStack != null) {
923             for (int i = localContextStack.size()-1; i>=0; i--) {
924                 LocalContext lc = (LocalContext) localContextStack.get(i);
925                 TemplateModel tm = lc.getLocalVariable(name);
926                 if (tm != null) {
927                     return tm;
928                 }
929             }
930         }
931         return currentMacroContext == null ? null : currentMacroContext.getLocalVariable(name);
932     }
933
934     /**
935      * Returns the variable that is visible in this context.
936      * This is the correspondent to an FTL top-level variable reading expression.
937      * That is, it tries to find the the variable in this order:
938      * <ol>
939      * <li>An loop variable (if we're in a loop or user defined directive body) such as foo_has_next
940      * <li>A local variable (if we're in a macro)
941      * <li>A variable defined in the current namespace (say, via &lt;#assign ...&gt;)
942      * <li>A variable defined globally (say, via &lt;#global ....&gt;)
943      * <li>Variable in the data model:
944      * <ol>
945      * <li>A variable in the root hash that was exposed to this
946                  rendering environment in the Template.process(...) call
947      * <li>A shared variable set in the configuration via a call to Configuration.setSharedVariable(...)
948      * </ol>
949      * </li>
950      * </ol>
951      */

952     public TemplateModel getVariable(String JavaDoc name) throws TemplateModelException {
953         TemplateModel result = getLocalVariable(name);
954         if (result == null) {
955             result = currentNamespace.get(name);
956         }
957         if (result == null) {
958             result = getGlobalVariable(name);
959         }
960         return result;
961     }
962
963     /**
964      * Returns the globally visible variable of the given name (or null).
965      * This is correspondent to FTL <code>.globals.<i>name</i></code>.
966      * This will first look at variables that were assigned globally via:
967      * &lt;#global ...&gt; and then at the data model exposed to the template.
968      */

969     public TemplateModel getGlobalVariable(String JavaDoc name) throws TemplateModelException {
970         TemplateModel result = globalNamespace.get(name);
971         if (result == null) {
972             result = rootDataModel.get(name);
973         }
974         if (result == null) {
975             result = getConfiguration().getSharedVariable(name);
976         }
977         return result;
978     }
979
980     /**
981      * Sets a variable that is visible globally.
982      * This is correspondent to FTL <code><#global <i>name</i>=<i>model</i>></code>.
983      * This can be considered a convenient shorthand for:
984      * getGlobalNamespace().put(name, model)
985      */

986     public void setGlobalVariable(String JavaDoc name, TemplateModel model) {
987         globalNamespace.put(name, model);
988     }
989
990     /**
991      * Sets a variable in the current namespace.
992      * This is correspondent to FTL <code><#assign <i>name</i>=<i>model</i>></code>.
993      * This can be considered a convenient shorthand for:
994      * getCurrentNamespace().put(name, model)
995      */

996     public void setVariable(String JavaDoc name, TemplateModel model) {
997         currentNamespace.put(name, model);
998     }
999
1000    /**
1001     * Sets a local variable (one effective only during a macro invocation).
1002     * This is correspondent to FTL <code><#local <i>name</i>=<i>model</i>></code>.
1003     * @param name the identifier of the variable
1004     * @param model the value of the variable.
1005     * @throws IllegalStateException if the environment is not executing a
1006     * macro body.
1007     */

1008    public void setLocalVariable(String JavaDoc name, TemplateModel model) {
1009        if(currentMacroContext == null) {
1010            throw new IllegalStateException JavaDoc("Not executing macro body");
1011        }
1012        currentMacroContext.setLocalVar(name, model);
1013    }
1014
1015    /**
1016     * Returns a set of variable names that are known at the time of call. This
1017     * includes names of all shared variables in the {@link Configuration},
1018     * names of all global variables that were assigned during the template processing,
1019     * names of all variables in the current name-space, names of all local variables
1020     * and loop variables. If the passed root data model implements the
1021     * {@link TemplateHashModelEx} interface, then all names it retrieves through a call to
1022     * {@link TemplateHashModelEx#keys()} method are returned as well.
1023     * The method returns a new Set object on each call that is completely
1024     * disconnected from the Environment. That is, modifying the set will have
1025     * no effect on the Environment object.
1026     */

1027    public Set getKnownVariableNames() throws TemplateModelException {
1028        // shared vars.
1029
Set set = getConfiguration().getSharedVariableNames();
1030        
1031        // root hash
1032
if (rootDataModel instanceof TemplateHashModelEx) {
1033            TemplateModelIterator rootNames =
1034                ((TemplateHashModelEx) rootDataModel).keys().iterator();
1035            while(rootNames.hasNext()) {
1036                set.add(((TemplateScalarModel)rootNames.next()).getAsString());
1037            }
1038        }
1039        
1040        // globals
1041
for (TemplateModelIterator tmi = globalNamespace.keys().iterator(); tmi.hasNext();) {
1042            set.add(((TemplateScalarModel) tmi.next()).getAsString());
1043        }
1044        
1045        // current name-space
1046
for (TemplateModelIterator tmi = currentNamespace.keys().iterator(); tmi.hasNext();) {
1047            set.add(((TemplateScalarModel) tmi.next()).getAsString());
1048        }
1049        
1050        // locals and loop vars
1051
if(currentMacroContext != null) {
1052            set.addAll(currentMacroContext.getLocalVariableNames());
1053        }
1054        if (localContextStack != null) {
1055            for (int i = localContextStack.size()-1; i>=0; i--) {
1056                LocalContext lc = (LocalContext) localContextStack.get(i);
1057                set.addAll(lc.getLocalVariableNames());
1058            }
1059        }
1060        return set;
1061    }
1062
1063    /**
1064     * Outputs the instruction stack. Useful for debugging.
1065     * {@link TemplateException}s incorporate this information in their stack
1066     * traces.
1067     */

1068    public void outputInstructionStack(PrintWriter pw) {
1069        pw.println("----------");
1070        ListIterator iter = elementStack.listIterator(elementStack.size());
1071        if(iter.hasPrevious()) {
1072            pw.print("==> ");
1073            TemplateElement prev = (TemplateElement) iter.previous();
1074            pw.print(prev.getDescription());
1075            pw.print(" [");
1076            pw.print(prev.getStartLocation());
1077            pw.println("]");
1078        }
1079        while(iter.hasPrevious()) {
1080            TemplateElement prev = (TemplateElement) iter.previous();
1081            if (prev instanceof UnifiedCall || prev instanceof Include) {
1082                String JavaDoc location = prev.getDescription() + " [" + prev.getStartLocation() + "]";
1083                if(location != null && location.length() > 0) {
1084                    pw.print(" in ");
1085                    pw.println(location);
1086                }
1087            }
1088        }
1089        pw.println("----------");
1090        pw.flush();
1091    }
1092
1093    private void pushLocalContext(LocalContext localContext) {
1094        if (localContextStack == null) {
1095            localContextStack = new ArrayList();
1096        }
1097        localContextStack.add(localContext);
1098    }
1099
1100    private void popLocalContext() {
1101        localContextStack.remove(localContextStack.size() - 1);
1102    }
1103    
1104    ArrayList getLocalContextStack() {
1105        return localContextStack;
1106    }
1107
1108    /**
1109     * Returns the name-space for the name if exists, or null.
1110     * @param name the template path that you have used with the <code>import</code> directive
1111     * or {@link #importLib} call, in normalized form. That is, the path must be an absolute
1112     * path, and it must not contain "/../" or "/./". The leading "/" is optional.
1113     */

1114    public Namespace getNamespace(String JavaDoc name) {
1115        if (name.startsWith("/")) name = name.substring(1);
1116        if (loadedLibs != null) {
1117            return (Namespace) loadedLibs.get(name);
1118        } else {
1119            return null;
1120        }
1121    }
1122
1123    /**
1124     * Returns the main name-space.
1125     * This is correspondent of FTL <code>.main</code> hash.
1126     */

1127    public Namespace getMainNamespace() {
1128        return mainNamespace;
1129    }
1130
1131    /**
1132     * Returns the main name-space.
1133     * This is correspondent of FTL <code>.namespace</code> hash.
1134     */

1135    public Namespace getCurrentNamespace() {
1136        return currentNamespace;
1137    }
1138    
1139    /**
1140     * Returns a fictitious name-space that contains the globally visible variables
1141     * that were created in the template, but not the variables of the data-model.
1142     * There is no such thing in FTL; this strange method was added because of the
1143     * JSP taglib support, since this imaginary name-space contains the page-scope
1144     * attributes.
1145     */

1146    public Namespace getGlobalNamespace() {
1147        return globalNamespace;
1148    }
1149
1150    /**
1151     * Returns the data model hash.
1152     * This is correspondent of FTL <code>.datamodel</code> hash.
1153     * That is, it contains both the variables of the root hash passed to the
1154     * <code>Template.process(...)</code>, and the shared variables in the
1155     * <code>Configuration</code>.
1156     */

1157    public TemplateHashModel getDataModel() {
1158        return new TemplateHashModel() {
1159                public boolean isEmpty() {
1160                    return false;
1161                }
1162                public TemplateModel get(String JavaDoc key) throws TemplateModelException {
1163                    TemplateModel result = rootDataModel.get(key);
1164                    if (result == null) {
1165                        result = getConfiguration().getSharedVariable(key);
1166                    }
1167                    return result;
1168                }
1169            };
1170    }
1171
1172    /**
1173     * Returns the read-only hash of globally visible variables.
1174     * This is the correspondent of FTL <code>.globals</code> hash.
1175     * That is, you see the variables created with
1176     * <code>&lt;#global ...></code>, and the variables of the data-model.
1177     * To create new global variables, use {@link #setGlobalVariable setGlobalVariable}.
1178     */

1179    public TemplateHashModel getGlobalVariables() {
1180        return new TemplateHashModel() {
1181            public boolean isEmpty() {
1182                return false;
1183            }
1184            public TemplateModel get(String JavaDoc key) throws TemplateModelException {
1185                TemplateModel result = globalNamespace.get(key);
1186                if (result == null) {
1187                    result = rootDataModel.get(key);
1188                }
1189                if (result == null) {
1190                    result = getConfiguration().getSharedVariable(key);
1191                }
1192                return result;
1193            }
1194        };
1195    }
1196
1197    private void pushElement(TemplateElement element) {
1198        elementStack.add(element);
1199    }
1200
1201    private void popElement() {
1202        elementStack.remove(elementStack.size() - 1);
1203    }
1204    
1205    public TemplateNodeModel getCurrentVisitorNode() {
1206        return currentVisitorNode;
1207    }
1208    
1209    /**
1210     * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt>
1211     */

1212    public void setCurrentVisitorNode(TemplateNodeModel node) {
1213        currentVisitorNode = node;
1214    }
1215    
1216    TemplateModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
1217        String JavaDoc nodeName = node.getNodeName();
1218        if (nodeName == null) {
1219            throw new TemplateException("Node name is null.", this);
1220        }
1221        TemplateModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
1222        if (result == null) {
1223            String JavaDoc type = node.getNodeType();
1224            if (type == null) {
1225                type = "default";
1226            }
1227            result = getNodeProcessor("@" + type, null, 0);
1228        }
1229        return result;
1230    }
1231    
1232    private TemplateModel getNodeProcessor(final String JavaDoc nodeName, final String JavaDoc nsURI, int startIndex)
1233    throws TemplateException
1234    {
1235        TemplateModel result = null;
1236        int i;
1237        for (i = startIndex; i<nodeNamespaces.size(); i++) {
1238            Namespace ns = null;
1239            try {
1240                ns = (Namespace) nodeNamespaces.get(i);
1241            } catch (ClassCastException JavaDoc cce) {
1242                throw new InvalidReferenceException("A using clause should contain a sequence of namespaces or strings that indicate the location of importable macro libraries.", this);
1243            }
1244            result = getNodeProcessor(ns, nodeName, nsURI);
1245            if (result != null)
1246                break;
1247        }
1248        if (result != null) {
1249            this.nodeNamespaceIndex = i+1;
1250            this.currentNodeName = nodeName;
1251            this.currentNodeNS = nsURI;
1252        }
1253        return result;
1254    }
1255    
1256    private TemplateModel getNodeProcessor(Namespace ns, String JavaDoc localName, String JavaDoc nsURI) throws TemplateException {
1257        TemplateModel result = null;
1258        if (nsURI == null) {
1259            result = ns.get(localName);
1260            if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
1261                result = null;
1262            }
1263        } else {
1264            Template template = ns.getTemplate();
1265            String JavaDoc prefix = template.getPrefixForNamespace(nsURI);
1266            if (prefix == null) {
1267                // The other template cannot handle this node
1268
// since it has no prefix registered for the namespace
1269
return null;
1270            }
1271            if (prefix.length() >0) {
1272                result = ns.get(prefix + ":" + localName);
1273                if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
1274                    result = null;
1275                }
1276            } else {
1277                if (nsURI.length() == 0) {
1278                    result = ns.get(Template.NO_NS_PREFIX + ":" + localName);
1279                    if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
1280                        result = null;
1281                    }
1282                }
1283                if (nsURI.equals(template.getDefaultNS())) {
1284                    result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName);
1285                    if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
1286                        result = null;
1287                    }
1288                }
1289                if (result == null) {
1290                    result = ns.get(localName);
1291                    if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) {
1292                        result = null;
1293                    }
1294                }
1295            }
1296        }
1297        return result;
1298    }
1299    
1300    /**
1301     * Emulates <code>include</code> directive, except that <code>name</code> must be tempate
1302     * root relative.
1303     *
1304     * <p>It's the same as <code>include(getTemplateForInclusion(name, encoding, parse))</code>.
1305     * But, you may want to separately call these two methods, so you can determine the source of
1306     * exceptions more precisely, and thus achieve more intelligent error handling.
1307     *
1308     * @see #getTemplateForInclusion(String name, String encoding, boolean parse)
1309     * @see #include(Template includedTemplate)
1310     */

1311    public void include(String JavaDoc name, String JavaDoc encoding, boolean parse)
1312    throws IOException, TemplateException
1313    {
1314        include(getTemplateForInclusion(name, encoding, parse));
1315    }
1316
1317    /**
1318     * Gets a template for inclusion; used with {@link #include(Template includedTemplate)}.
1319     * The advantage over simply using <code>config.getTemplate(...)</code> is that it chooses
1320     * the default encoding as the <code>include</code> directive does.
1321     *
1322     * @param name the name of the template, relatively to the template root directory
1323     * (not the to the directory of the currently executing template file!).
1324     * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
1325     * to convert paths to template root relative paths.)
1326     * @param encoding the encoding of the obtained template. If null,
1327     * the encoding of the Template that is currently being processed in this
1328     * Environment is used.
1329     * @param parse whether to process a parsed template or just include the
1330     * unparsed template source.
1331     */

1332    public Template getTemplateForInclusion(String JavaDoc name, String JavaDoc encoding, boolean parse)
1333    throws IOException
1334    {
1335        if (encoding == null) {
1336            encoding = getTemplate().getEncoding();
1337        }
1338        if (encoding == null) {
1339            encoding = getConfiguration().getEncoding(this.getLocale());
1340        }
1341        return getConfiguration().getTemplate(name, getLocale(), encoding, parse);
1342    }
1343
1344    /**
1345     * Processes a Template in the context of this <code>Environment</code>, including its
1346     * output in the <code>Environment</code>'s Writer.
1347     *
1348     * @param includedTemplate the template to process. Note that it does <em>not</em> need
1349     * to be a template returned by
1350     * {@link #getTemplateForInclusion(String name, String encoding, boolean parse)}.
1351     */

1352    public void include(Template includedTemplate)
1353    throws TemplateException, IOException
1354    {
1355        Template prevTemplate = getTemplate();
1356        setParent(includedTemplate);
1357        importMacros(includedTemplate);
1358        try {
1359            visit(includedTemplate.getRootTreeNode());
1360        }
1361        finally {
1362            setParent(prevTemplate);
1363        }
1364    }
1365    
1366    /**
1367     * Emulates <code>import</code> directive, except that <code>name</code> must be tempate
1368     * root relative.
1369     *
1370     * <p>It's the same as <code>importLib(getTemplateForImporting(name), namespace)</code>.
1371     * But, you may want to separately call these two methods, so you can determine the source of
1372     * exceptions more precisely, and thus achieve more intelligent error handling.
1373     *
1374     * @see #getTemplateForImporting(String name)
1375     * @see #importLib(Template includedTemplate, String namespace)
1376     */

1377    public Namespace importLib(String JavaDoc name, String JavaDoc namespace)
1378    throws IOException, TemplateException
1379    {
1380        return importLib(getTemplateForImporting(name), namespace);
1381    }
1382
1383    /**
1384     * Gets a template for importing; used with
1385     * {@link #importLib(Template importedTemplate, String namespace)}. The advantage
1386     * over simply using <code>config.getTemplate(...)</code> is that it chooses the encoding
1387     * as the <code>import</code> directive does.
1388     *
1389     * @param name the name of the template, relatively to the template root directory
1390     * (not the to the directory of the currently executing template file!).
1391     * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
1392     * to convert paths to template root relative paths.)
1393     */

1394    public Template getTemplateForImporting(String JavaDoc name) throws IOException {
1395        return getTemplateForInclusion(name, null, true);
1396    }
1397    
1398    /**
1399     * Emulates <code>import</code> directive.
1400     *
1401     * @param loadedTemplate the template to import. Note that it does <em>not</em> need
1402     * to be a template returned by {@link #getTemplateForImporting(String name)}.
1403     */

1404    public Namespace importLib(Template loadedTemplate, String JavaDoc namespace)
1405    throws IOException, TemplateException
1406    {
1407        if (loadedLibs == null) {
1408            loadedLibs = new HashMap();
1409        }
1410        String JavaDoc templateName = loadedTemplate.getName();
1411        Namespace existingNamespace = (Namespace) loadedLibs.get(templateName);
1412        if (existingNamespace != null) {
1413            if (namespace != null) {
1414                setVariable(namespace, existingNamespace);
1415            }
1416        }
1417        else {
1418            Namespace newNamespace = new Namespace(loadedTemplate);
1419            if (namespace != null) {
1420                currentNamespace.put(namespace, newNamespace);
1421                if (currentNamespace == mainNamespace) {
1422                    globalNamespace.put(namespace, newNamespace);
1423                }
1424            }
1425            Namespace prevNamespace = this.currentNamespace;
1426            this.currentNamespace = newNamespace;
1427            loadedLibs.put(templateName, currentNamespace);
1428            Writer prevOut = out;
1429            this.out = NULL_WRITER;
1430            try {
1431                include(loadedTemplate);
1432            } finally {
1433                this.out = prevOut;
1434                this.currentNamespace = prevNamespace;
1435            }
1436        }
1437        return (Namespace) loadedLibs.get(templateName);
1438    }
1439    
1440    String JavaDoc renderElementToString(TemplateElement te) throws IOException, TemplateException {
1441        Writer prevOut = out;
1442        try {
1443            StringWriter sw = new StringWriter();
1444            this.out = sw;
1445            visit(te);
1446            return sw.toString();
1447        }
1448        finally {
1449            this.out = prevOut;
1450        }
1451    }
1452
1453    void importMacros(Template template) {
1454        for (Iterator it = template.getMacros().values().iterator(); it.hasNext();) {
1455            visitMacroDef((Macro) it.next());
1456        }
1457    }
1458
1459    /**
1460     * @return the namespace URI registered for this prefix, or null.
1461     * This is based on the mappings registered in the current namespace.
1462     */

1463    public String JavaDoc getNamespaceForPrefix(String JavaDoc prefix) {
1464        return currentNamespace.getTemplate().getNamespaceForPrefix(prefix);
1465    }
1466    
1467    public String JavaDoc getPrefixForNamespace(String JavaDoc nsURI) {
1468        return currentNamespace.getTemplate().getPrefixForNamespace(nsURI);
1469    }
1470    
1471    /**
1472     * @return the default node namespace for the current FTL namespace
1473     */

1474    public String JavaDoc getDefaultNS() {
1475        return currentNamespace.getTemplate().getDefaultNS();
1476    }
1477    
1478    /**
1479     * A hook that Jython uses.
1480     */

1481    public Object JavaDoc __getitem__(String JavaDoc key) throws TemplateModelException {
1482        return BeansWrapper.getDefaultInstance().unwrap(getVariable(key));
1483    }
1484
1485    /**
1486     * A hook that Jython uses.
1487     */

1488    public void __setitem__(String JavaDoc key, Object JavaDoc o) throws TemplateException {
1489        setGlobalVariable(key, getObjectWrapper().wrap(o));
1490    }
1491
1492    private static final class NumberFormatKey
1493    {
1494        private final String JavaDoc pattern;
1495        private final Locale locale;
1496
1497        NumberFormatKey(String JavaDoc pattern, Locale locale)
1498        {
1499            this.pattern = pattern;
1500            this.locale = locale;
1501        }
1502
1503        public boolean equals(Object JavaDoc o)
1504        {
1505            if(o instanceof NumberFormatKey)
1506            {
1507                NumberFormatKey fk = (NumberFormatKey)o;
1508                return fk.pattern.equals(pattern) && fk.locale.equals(locale);
1509            }
1510            return false;
1511        }
1512
1513        public int hashCode()
1514        {
1515            return pattern.hashCode() ^ locale.hashCode();
1516        }
1517    }
1518
1519    private static final class DateFormatKey
1520    {
1521        private final int dateType;
1522        private final String JavaDoc pattern;
1523        private final Locale locale;
1524        private final TimeZone timeZone;
1525
1526        DateFormatKey(int dateType, String JavaDoc pattern, Locale locale, TimeZone timeZone)
1527        {
1528            this.dateType = dateType;
1529            this.pattern = pattern;
1530            this.locale = locale;
1531            this.timeZone = timeZone;
1532        }
1533
1534        public boolean equals(Object JavaDoc o)
1535        {
1536            if(o instanceof DateFormatKey)
1537            {
1538                DateFormatKey fk = (DateFormatKey)o;
1539                return dateType == fk.dateType && fk.pattern.equals(pattern) && fk.locale.equals(locale) && fk.timeZone.equals(timeZone);
1540            }
1541            return false;
1542        }
1543
1544        public int hashCode()
1545        {
1546            return dateType ^ pattern.hashCode() ^ locale.hashCode() ^ timeZone.hashCode();
1547        }
1548    }
1549    
1550    public class Namespace extends SimpleHash {
1551        
1552        private Template template;
1553        
1554        Namespace() {
1555            this.template = Environment.this.getTemplate();
1556        }
1557        
1558        Namespace(Template template) {
1559            this.template = template;
1560        }
1561        
1562        /**
1563         * @return the Template object with which this Namespace is associated.
1564         */

1565        public Template getTemplate() {
1566            return template == null ? Environment.this.getTemplate() : template;
1567        }
1568    }
1569
1570    static final Writer NULL_WRITER = new Writer() {
1571            public void write(char cbuf[], int off, int len) {}
1572            public void flush() {}
1573            public void close() {}
1574     };
1575     
1576     private static final Writer EMPTY_BODY_WRITER = new Writer() {
1577    
1578        public void write(char[] cbuf, int off, int len) throws IOException {
1579            if (len > 0) {
1580                throw new IOException(
1581                        "This transform does not allow nested content.");
1582            }
1583        }
1584    
1585        public void flush() {
1586        }
1587    
1588        public void close() {
1589        }
1590    };
1591    
1592}
1593
Popular Tags