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 <#assign ...>)
1027 * <li>A variable defined globally (say, via <#global ....>)
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 * <#global ...> 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><#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 }