0001: /*
0002: * Copyright (c) 2003 The Visigoth Software Society. All rights
0003: * reserved.
0004: *
0005: * Redistribution and use in source and binary forms, with or without
0006: * modification, are permitted provided that the following conditions
0007: * are met:
0008: *
0009: * 1. Redistributions of source code must retain the above copyright
0010: * notice, this list of conditions and the following disclaimer.
0011: *
0012: * 2. Redistributions in binary form must reproduce the above copyright
0013: * notice, this list of conditions and the following disclaimer in
0014: * the documentation and/or other materials provided with the
0015: * distribution.
0016: *
0017: * 3. The end-user documentation included with the redistribution, if
0018: * any, must include the following acknowledgement:
0019: * "This product includes software developed by the
0020: * Visigoth Software Society (http://www.visigoths.org/)."
0021: * Alternately, this acknowledgement may appear in the software itself,
0022: * if and wherever such third-party acknowledgements normally appear.
0023: *
0024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
0025: * project contributors may be used to endorse or promote products derived
0026: * from this software without prior written permission. For written
0027: * permission, please contact visigoths@visigoths.org.
0028: *
0029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
0030: * nor may "FreeMarker" or "Visigoth" appear in their names
0031: * without prior written permission of the Visigoth Software Society.
0032: *
0033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
0037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0044: * SUCH DAMAGE.
0045: * ====================================================================
0046: *
0047: * This software consists of voluntary contributions made by many
0048: * individuals on behalf of the Visigoth Software Society. For more
0049: * information on the Visigoth Software Society, please see
0050: * http://www.visigoths.org/
0051: */
0052:
0053: package freemarker.core;
0054:
0055: import java.io.*;
0056: import java.text.*;
0057: import java.util.*;
0058:
0059: import freemarker.ext.beans.BeansWrapper;
0060: import freemarker.log.Logger;
0061: import freemarker.template.*;
0062: import freemarker.template.utility.UndeclaredThrowableException;
0063:
0064: /**
0065: * Object that represents the runtime environment during template processing.
0066: * For every invocation of a <tt>Template.process()</tt> method, a new instance
0067: * of this object is created, and then discarded when <tt>process()</tt> returns.
0068: * This object stores the set of temporary variables created by the template,
0069: * the value of settings set by the template, the reference to the data model root,
0070: * etc. Everything that is needed to fulfill the template processing job.
0071: *
0072: * <p>Data models that need to access the <tt>Environment</tt>
0073: * object that represents the template processing on the current thread can use
0074: * the {@link #getCurrentEnvironment()} method.
0075: *
0076: * <p>If you need to modify or read this object before or after the <tt>process</tt>
0077: * call, use {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
0078: *
0079: * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
0080: * @author Attila Szegedi
0081: */
0082: public final class Environment extends Configurable {
0083:
0084: private static final ThreadLocal threadEnv = new ThreadLocal();
0085:
0086: private static final Logger logger = Logger
0087: .getLogger("freemarker.runtime");
0088:
0089: private static final Map localizedNumberFormats = new HashMap();
0090: private static final Map localizedDateFormats = new HashMap();
0091:
0092: private final TemplateHashModel rootDataModel;
0093: private final ArrayList elementStack = new ArrayList();
0094: private final ArrayList recoveredErrorStack = new ArrayList();
0095:
0096: private NumberFormat numberFormat;
0097: private Map numberFormats;
0098:
0099: private DateFormat timeFormat, dateFormat, dateTimeFormat;
0100: private Map[] dateFormats;
0101:
0102: private Collator collator;
0103:
0104: private Writer out;
0105: private Macro.Context currentMacroContext;
0106: private ArrayList localContextStack;
0107: private Namespace mainNamespace, currentNamespace, globalNamespace;
0108: private HashMap loadedLibs;
0109:
0110: private Throwable lastThrowable;
0111:
0112: private TemplateModel lastReturnValue;
0113: private HashMap macroToNamespaceLookup = new HashMap();
0114:
0115: private TemplateNodeModel currentVisitorNode;
0116: private TemplateSequenceModel nodeNamespaces;
0117: // Things we keep track of for the fallback mechanism.
0118: private int nodeNamespaceIndex;
0119: private String currentNodeName, currentNodeNS;
0120:
0121: private String cachedURLEscapingCharset;
0122: private boolean urlEscapingCharsetCached;
0123:
0124: /**
0125: * Retrieves the environment object associated with the current
0126: * thread. Data model implementations that need access to the
0127: * environment can call this method to obtain the environment object
0128: * that represents the template processing that is currently running
0129: * on the current thread.
0130: */
0131: public static Environment getCurrentEnvironment() {
0132: return (Environment) threadEnv.get();
0133: }
0134:
0135: public Environment(Template template,
0136: final TemplateHashModel rootDataModel, Writer out) {
0137: super (template);
0138: this .globalNamespace = new Namespace(null);
0139: this .currentNamespace = mainNamespace = new Namespace(template);
0140: this .out = out;
0141: this .rootDataModel = rootDataModel;
0142: importMacros(template);
0143: }
0144:
0145: /**
0146: * Retrieves the currently processed template.
0147: */
0148: public Template getTemplate() {
0149: return (Template) getParent();
0150: }
0151:
0152: /**
0153: * Deletes cached values that meant to be valid only during a single
0154: * template execution.
0155: */
0156: private void clearCachedValues() {
0157: numberFormats = null;
0158: numberFormat = null;
0159: dateFormats = null;
0160: collator = null;
0161: cachedURLEscapingCharset = null;
0162: urlEscapingCharsetCached = false;
0163: }
0164:
0165: /**
0166: * Processes the template to which this environment belongs.
0167: */
0168: public void process() throws TemplateException, IOException {
0169: Object savedEnv = threadEnv.get();
0170: threadEnv.set(this );
0171: try {
0172: // Cached values from a previous execution are possibly outdated.
0173: clearCachedValues();
0174: try {
0175: visit(getTemplate().getRootTreeNode());
0176: // Do not flush if there was an exception.
0177: out.flush();
0178: } finally {
0179: // It's just to allow the GC to free memory...
0180: clearCachedValues();
0181: }
0182: } finally {
0183: threadEnv.set(savedEnv);
0184: }
0185: }
0186:
0187: /**
0188: * "Visit" the template element.
0189: */
0190: void visit(TemplateElement element) throws TemplateException,
0191: IOException {
0192: pushElement(element);
0193: try {
0194: element.accept(this );
0195: } catch (TemplateException te) {
0196: handleTemplateException(te);
0197: } finally {
0198: popElement();
0199: }
0200: }
0201:
0202: /**
0203: * "Visit" the template element, passing the output
0204: * through a TemplateTransformModel
0205: * @param element the element to visit through a transform
0206: * @param transform the transform to pass the element output
0207: * through
0208: * @param args optional arguments fed to the transform
0209: */
0210: void visit(TemplateElement element,
0211: TemplateTransformModel transform, Map args)
0212: throws TemplateException, IOException {
0213: try {
0214: Writer tw = transform.getWriter(out, args);
0215: if (tw == null)
0216: tw = EMPTY_BODY_WRITER;
0217: TransformControl tc = tw instanceof TransformControl ? (TransformControl) tw
0218: : null;
0219:
0220: Writer prevOut = out;
0221: out = tw;
0222: try {
0223: if (tc == null
0224: || tc.onStart() != TransformControl.SKIP_BODY) {
0225: do {
0226: if (element != null) {
0227: visit(element);
0228: }
0229: } while (tc != null
0230: && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
0231: }
0232: } catch (Throwable t) {
0233: try {
0234: if (tc != null) {
0235: tc.onError(t);
0236: } else {
0237: throw t;
0238: }
0239: } catch (TemplateException e) {
0240: throw e;
0241: } catch (IOException e) {
0242: throw e;
0243: } catch (RuntimeException e) {
0244: throw e;
0245: } catch (Error e) {
0246: throw e;
0247: } catch (Throwable e) {
0248: throw new UndeclaredThrowableException(e);
0249: }
0250: } finally {
0251: out = prevOut;
0252: tw.close();
0253: }
0254: } catch (TemplateException te) {
0255: handleTemplateException(te);
0256: }
0257: }
0258:
0259: /**
0260: * Visit a block using buffering/recovery
0261: */
0262:
0263: void visit(TemplateElement attemptBlock,
0264: TemplateElement recoveryBlock) throws TemplateException,
0265: IOException {
0266: Writer prevOut = this .out;
0267: StringWriter sw = new StringWriter();
0268: this .out = sw;
0269: TemplateException thrownException = null;
0270: try {
0271: visit(attemptBlock);
0272: } catch (TemplateException te) {
0273: thrownException = te;
0274: } finally {
0275: this .out = prevOut;
0276: }
0277: if (thrownException != null) {
0278: if (logger.isErrorEnabled()) {
0279: String msg = "Error in attempt block "
0280: + attemptBlock.getStartLocation();
0281: logger.error(msg, thrownException);
0282: }
0283: try {
0284: recoveredErrorStack.add(thrownException.getMessage());
0285: visit(recoveryBlock);
0286: } finally {
0287: recoveredErrorStack
0288: .remove(recoveredErrorStack.size() - 1);
0289: }
0290: } else {
0291: out.write(sw.toString());
0292: }
0293: }
0294:
0295: String getCurrentRecoveredErrorMesssage() throws TemplateException {
0296: if (recoveredErrorStack.isEmpty()) {
0297: throw new TemplateException(
0298: ".error is not available outside of a <#recover> block",
0299: this );
0300: }
0301: return (String) recoveredErrorStack.get(recoveredErrorStack
0302: .size() - 1);
0303: }
0304:
0305: void visit(BodyInstruction.Context bctxt) throws TemplateException,
0306: IOException {
0307: Macro.Context invokingMacroContext = getCurrentMacroContext();
0308: ArrayList prevLocalContextStack = localContextStack;
0309: TemplateElement body = invokingMacroContext.body;
0310: if (body != null) {
0311: this .currentMacroContext = invokingMacroContext.prevMacroContext;
0312: currentNamespace = invokingMacroContext.bodyNamespace;
0313: Configurable prevParent = getParent();
0314: setParent(currentNamespace.getTemplate());
0315: this .localContextStack = invokingMacroContext.prevLocalContextStack;
0316: if (invokingMacroContext.bodyParameterNames != null) {
0317: pushLocalContext(bctxt);
0318: }
0319: try {
0320: visit(body);
0321: } finally {
0322: if (invokingMacroContext.bodyParameterNames != null) {
0323: popLocalContext();
0324: }
0325: this .currentMacroContext = invokingMacroContext;
0326: currentNamespace = getMacroNamespace(invokingMacroContext
0327: .getMacro());
0328: setParent(prevParent);
0329: this .localContextStack = prevLocalContextStack;
0330: }
0331: }
0332: }
0333:
0334: /**
0335: * "visit" an IteratorBlock
0336: */
0337: void visit(IteratorBlock.Context ictxt) throws TemplateException,
0338: IOException {
0339: pushLocalContext(ictxt);
0340: try {
0341: ictxt.runLoop(this );
0342: } catch (BreakInstruction.Break br) {
0343: } catch (TemplateException te) {
0344: handleTemplateException(te);
0345: } finally {
0346: popLocalContext();
0347: }
0348: }
0349:
0350: /**
0351: * "Visit" A TemplateNodeModel
0352: */
0353:
0354: void visit(TemplateNodeModel node, TemplateSequenceModel namespaces)
0355: throws TemplateException, IOException {
0356: if (nodeNamespaces == null) {
0357: SimpleSequence ss = new SimpleSequence(1);
0358: ss.add(currentNamespace);
0359: nodeNamespaces = ss;
0360: }
0361: int prevNodeNamespaceIndex = this .nodeNamespaceIndex;
0362: String prevNodeName = this .currentNodeName;
0363: String prevNodeNS = this .currentNodeNS;
0364: TemplateSequenceModel prevNodeNamespaces = nodeNamespaces;
0365: TemplateNodeModel prevVisitorNode = currentVisitorNode;
0366: currentVisitorNode = node;
0367: if (namespaces != null) {
0368: this .nodeNamespaces = namespaces;
0369: }
0370: try {
0371: TemplateModel macroOrTransform = getNodeProcessor(node);
0372: if (macroOrTransform instanceof Macro) {
0373: visit((Macro) macroOrTransform, null, null, null, null);
0374: } else if (macroOrTransform instanceof TemplateTransformModel) {
0375: visit(null, (TemplateTransformModel) macroOrTransform,
0376: null);
0377: } else {
0378: String nodeType = node.getNodeType();
0379: if (nodeType != null) {
0380: // If the node's type is 'text', we just output it.
0381: if ((nodeType.equals("text") && node instanceof TemplateScalarModel)) {
0382: out.write(((TemplateScalarModel) node)
0383: .getAsString());
0384: } else if (nodeType.equals("document")) {
0385: recurse(node, namespaces);
0386: }
0387: // We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
0388: // we just ignore it.
0389: else if (!nodeType.equals("pi")
0390: && !nodeType.equals("comment")
0391: && !nodeType.equals("document_type")) {
0392: String nsBit = "";
0393: String ns = node.getNodeNamespace();
0394: if (ns != null) {
0395: if (ns.length() > 0) {
0396: nsBit = " and namespace " + ns;
0397: } else {
0398: nsBit = " and no namespace";
0399: }
0400: }
0401: throw new TemplateException(
0402: "No macro or transform defined for node named "
0403: + node.getNodeName()
0404: + nsBit
0405: + ", and there is no fallback handler called @"
0406: + nodeType + " either.", this );
0407: }
0408: } else {
0409: String nsBit = "";
0410: String ns = node.getNodeNamespace();
0411: if (ns != null) {
0412: if (ns.length() > 0) {
0413: nsBit = " and namespace " + ns;
0414: } else {
0415: nsBit = " and no namespace";
0416: }
0417: }
0418: throw new TemplateException(
0419: "No macro or transform defined for node with name "
0420: + node.getNodeName()
0421: + nsBit
0422: + ", and there is no macro or transform called @default either.",
0423: this );
0424: }
0425: }
0426: } finally {
0427: this .currentVisitorNode = prevVisitorNode;
0428: this .nodeNamespaceIndex = prevNodeNamespaceIndex;
0429: this .currentNodeName = prevNodeName;
0430: this .currentNodeNS = prevNodeNS;
0431: this .nodeNamespaces = prevNodeNamespaces;
0432: }
0433: }
0434:
0435: void fallback() throws TemplateException, IOException {
0436: TemplateModel macroOrTransform = getNodeProcessor(
0437: currentNodeName, currentNodeNS, nodeNamespaceIndex);
0438: if (macroOrTransform instanceof Macro) {
0439: visit((Macro) macroOrTransform, null, null, null, null);
0440: } else if (macroOrTransform instanceof TemplateTransformModel) {
0441: visit(null, (TemplateTransformModel) macroOrTransform, null);
0442: }
0443: }
0444:
0445: /**
0446: * "visit" a macro.
0447: */
0448:
0449: void visit(Macro macro, Map namedArgs, List positionalArgs,
0450: List bodyParameterNames, TemplateElement nestedBlock)
0451: throws TemplateException, IOException {
0452: if (macro == Macro.DO_NOTHING_MACRO) {
0453: return;
0454: }
0455: pushElement(macro);
0456: try {
0457: Macro.Context previousMacroContext = currentMacroContext;
0458: Macro.Context mc = macro.new Context(this , nestedBlock,
0459: bodyParameterNames);
0460:
0461: String catchAll = macro.getCatchAll();
0462: TemplateModel unknownVars = null;
0463:
0464: if (namedArgs != null) {
0465: if (catchAll != null)
0466: unknownVars = new SimpleHash();
0467: for (Iterator it = namedArgs.entrySet().iterator(); it
0468: .hasNext();) {
0469: Map.Entry entry = (Map.Entry) it.next();
0470: String varName = (String) entry.getKey();
0471: boolean hasVar = macro.hasArgNamed(varName);
0472: if (hasVar || catchAll != null) {
0473: Expression arg = (Expression) entry.getValue();
0474: TemplateModel value = arg
0475: .getAsTemplateModel(this );
0476: if (hasVar) {
0477: mc.setLocalVar(varName, value);
0478: } else {
0479: ((SimpleHash) unknownVars).put(varName,
0480: value);
0481: }
0482: } else {
0483: String msg = "Macro " + macro.getName()
0484: + " has no such argument: " + varName;
0485: throw new TemplateException(msg, this );
0486: }
0487: }
0488: } else if (positionalArgs != null) {
0489: if (catchAll != null)
0490: unknownVars = new SimpleSequence();
0491: String[] argumentNames = macro.getArgumentNames();
0492: int size = positionalArgs.size();
0493: if (argumentNames.length < size && catchAll == null) {
0494: throw new TemplateException("Macro "
0495: + macro.getName() + " only accepts "
0496: + argumentNames.length + " parameters.",
0497: this );
0498: }
0499: for (int i = 0; i < size; i++) {
0500: Expression argExp = (Expression) positionalArgs
0501: .get(i);
0502: TemplateModel argModel = argExp
0503: .getAsTemplateModel(this );
0504: try {
0505: if (i < argumentNames.length) {
0506: String argName = argumentNames[i];
0507: mc.setLocalVar(argName, argModel);
0508: } else {
0509: ((SimpleSequence) unknownVars)
0510: .add(argModel);
0511: }
0512: } catch (RuntimeException re) {
0513: throw new TemplateException(re, this );
0514: }
0515: }
0516: }
0517: if (catchAll != null) {
0518: mc.setLocalVar(catchAll, unknownVars);
0519: }
0520: ArrayList prevLocalContextStack = localContextStack;
0521: localContextStack = null;
0522: Namespace prevNamespace = currentNamespace;
0523: Configurable prevParent = getParent();
0524: currentNamespace = (Namespace) macroToNamespaceLookup
0525: .get(macro);
0526: currentMacroContext = mc;
0527: try {
0528: mc.runMacro(this );
0529: } catch (ReturnInstruction.Return re) {
0530: } catch (TemplateException te) {
0531: handleTemplateException(te);
0532: } finally {
0533: currentMacroContext = previousMacroContext;
0534: localContextStack = prevLocalContextStack;
0535: currentNamespace = prevNamespace;
0536: setParent(prevParent);
0537: }
0538: } finally {
0539: popElement();
0540: }
0541: }
0542:
0543: void visitMacroDef(Macro macro) {
0544: macroToNamespaceLookup.put(macro, currentNamespace);
0545: currentNamespace.put(macro.getName(), macro);
0546: }
0547:
0548: Namespace getMacroNamespace(Macro macro) {
0549: return (Namespace) macroToNamespaceLookup.get(macro);
0550: }
0551:
0552: void recurse(TemplateNodeModel node,
0553: TemplateSequenceModel namespaces) throws TemplateException,
0554: IOException {
0555: if (node == null) {
0556: node = this .getCurrentVisitorNode();
0557: if (node == null) {
0558: throw new TemplateModelException(
0559: "The target node of recursion is missing or null.");
0560: }
0561: }
0562: TemplateSequenceModel children = node.getChildNodes();
0563: if (children == null)
0564: return;
0565: for (int i = 0; i < children.size(); i++) {
0566: TemplateNodeModel child = (TemplateNodeModel) children
0567: .get(i);
0568: if (child != null) {
0569: visit(child, namespaces);
0570: }
0571: }
0572: }
0573:
0574: Macro.Context getCurrentMacroContext() {
0575: return currentMacroContext;
0576: }
0577:
0578: private void handleTemplateException(TemplateException te)
0579: throws TemplateException {
0580: // Logic to prevent double-handling of the exception in
0581: // nested visit() calls.
0582: if (lastThrowable == te) {
0583: throw te;
0584: }
0585: lastThrowable = te;
0586:
0587: // Log the exception
0588: if (logger.isErrorEnabled()) {
0589: logger.error("", te);
0590: }
0591:
0592: // Stop exception is not passed to the handler, but
0593: // explicitly rethrown.
0594: if (te instanceof StopException) {
0595: throw te;
0596: }
0597:
0598: // Finally, pass the exception to the handler
0599: getTemplateExceptionHandler().handleTemplateException(te, this ,
0600: out);
0601: }
0602:
0603: public void setTemplateExceptionHandler(
0604: TemplateExceptionHandler templateExceptionHandler) {
0605: super .setTemplateExceptionHandler(templateExceptionHandler);
0606: lastThrowable = null;
0607: }
0608:
0609: public void setLocale(Locale locale) {
0610: super .setLocale(locale);
0611: // Clear local format cache
0612: numberFormats = null;
0613: numberFormat = null;
0614:
0615: dateFormats = null;
0616: timeFormat = dateFormat = dateTimeFormat = null;
0617:
0618: collator = null;
0619: }
0620:
0621: public void setTimeZone(TimeZone timeZone) {
0622: super .setTimeZone(timeZone);
0623: // Clear local date format cache
0624: dateFormats = null;
0625: timeFormat = dateFormat = dateTimeFormat = null;
0626: }
0627:
0628: public void setURLEscapingCharset(String urlEscapingCharset) {
0629: urlEscapingCharsetCached = false;
0630: super .setURLEscapingCharset(urlEscapingCharset);
0631: }
0632:
0633: /*
0634: * Note that altough it is not allowed to set this setting with the
0635: * <tt>setting</tt> directive, it still must be allowed to set it from Java
0636: * code while the template executes, since some frameworks allow templates
0637: * to actually change the output encoding on-the-fly.
0638: */
0639: public void setOutputEncoding(String outputEncoding) {
0640: urlEscapingCharsetCached = false;
0641: super .setOutputEncoding(outputEncoding);
0642: }
0643:
0644: /**
0645: * Returns the name of the charset that should be used for URL encoding.
0646: * This will be <code>null</code> if the information is not available.
0647: * The function caches the return value, so it is quick to call it
0648: * repeately.
0649: */
0650: String getEffectiveURLEscapingCharset() {
0651: if (!urlEscapingCharsetCached) {
0652: cachedURLEscapingCharset = getURLEscapingCharset();
0653: if (cachedURLEscapingCharset == null) {
0654: cachedURLEscapingCharset = getOutputEncoding();
0655: }
0656: urlEscapingCharsetCached = true;
0657: }
0658: return cachedURLEscapingCharset;
0659: }
0660:
0661: Collator getCollator() {
0662: if (collator == null) {
0663: collator = Collator.getInstance(getLocale());
0664: }
0665: return collator;
0666: }
0667:
0668: public void setOut(Writer out) {
0669: this .out = out;
0670: }
0671:
0672: public Writer getOut() {
0673: return out;
0674: }
0675:
0676: String formatNumber(Number number) {
0677: if (numberFormat == null) {
0678: numberFormat = getNumberFormatObject(getNumberFormat());
0679: }
0680: return numberFormat.format(number);
0681: }
0682:
0683: public void setNumberFormat(String formatName) {
0684: super .setNumberFormat(formatName);
0685: numberFormat = null;
0686: }
0687:
0688: String formatDate(Date date, int type)
0689: throws TemplateModelException {
0690: DateFormat df = getDateFormatObject(type);
0691: if (df == null) {
0692: throw new TemplateModelException(
0693: "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.");
0694: }
0695: return df.format(date);
0696: }
0697:
0698: public void setTimeFormat(String formatName) {
0699: super .setTimeFormat(formatName);
0700: timeFormat = null;
0701: }
0702:
0703: public void setDateFormat(String formatName) {
0704: super .setDateFormat(formatName);
0705: dateFormat = null;
0706: }
0707:
0708: public void setDateTimeFormat(String formatName) {
0709: super .setDateTimeFormat(formatName);
0710: dateTimeFormat = null;
0711: }
0712:
0713: public Configuration getConfiguration() {
0714: return getTemplate().getConfiguration();
0715: }
0716:
0717: TemplateModel getLastReturnValue() {
0718: return lastReturnValue;
0719: }
0720:
0721: void setLastReturnValue(TemplateModel lastReturnValue) {
0722: this .lastReturnValue = lastReturnValue;
0723: }
0724:
0725: void clearLastReturnValue() {
0726: this .lastReturnValue = null;
0727: }
0728:
0729: NumberFormat getNumberFormatObject(String pattern) {
0730: if (numberFormats == null) {
0731: numberFormats = new HashMap();
0732: }
0733:
0734: NumberFormat format = (NumberFormat) numberFormats.get(pattern);
0735: if (format != null) {
0736: return format;
0737: }
0738:
0739: // Get format from global format cache
0740: synchronized (localizedNumberFormats) {
0741: Locale locale = getLocale();
0742: NumberFormatKey fk = new NumberFormatKey(pattern, locale);
0743: format = (NumberFormat) localizedNumberFormats.get(fk);
0744: if (format == null) {
0745: // Add format to global format cache. Note this is
0746: // globally done once per locale per pattern.
0747: if ("number".equals(pattern)) {
0748: format = NumberFormat.getNumberInstance(locale);
0749: } else if ("currency".equals(pattern)) {
0750: format = NumberFormat.getCurrencyInstance(locale);
0751: } else if ("percent".equals(pattern)) {
0752: format = NumberFormat.getPercentInstance(locale);
0753: } else {
0754: format = new DecimalFormat(pattern,
0755: new DecimalFormatSymbols(getLocale()));
0756: }
0757: localizedNumberFormats.put(fk, format);
0758: }
0759: }
0760:
0761: // Clone it and store the clone in the local cache
0762: format = (NumberFormat) format.clone();
0763: numberFormats.put(pattern, format);
0764: return format;
0765: }
0766:
0767: DateFormat getDateFormatObject(int dateType)
0768: throws TemplateModelException {
0769: switch (dateType) {
0770: case TemplateDateModel.UNKNOWN: {
0771: return null;
0772: }
0773: case TemplateDateModel.TIME: {
0774: if (timeFormat == null) {
0775: timeFormat = getDateFormatObject(dateType,
0776: getTimeFormat());
0777: }
0778: return timeFormat;
0779: }
0780: case TemplateDateModel.DATE: {
0781: if (dateFormat == null) {
0782: dateFormat = getDateFormatObject(dateType,
0783: getDateFormat());
0784: }
0785: return dateFormat;
0786: }
0787: case TemplateDateModel.DATETIME: {
0788: if (dateTimeFormat == null) {
0789: dateTimeFormat = getDateFormatObject(dateType,
0790: getDateTimeFormat());
0791: }
0792: return dateTimeFormat;
0793: }
0794: default: {
0795: throw new TemplateModelException("Unrecognized date type "
0796: + dateType);
0797: }
0798: }
0799: }
0800:
0801: DateFormat getDateFormatObject(int dateType, String pattern)
0802: throws TemplateModelException {
0803: if (dateFormats == null) {
0804: dateFormats = new Map[4];
0805: dateFormats[TemplateDateModel.UNKNOWN] = new HashMap();
0806: dateFormats[TemplateDateModel.TIME] = new HashMap();
0807: dateFormats[TemplateDateModel.DATE] = new HashMap();
0808: dateFormats[TemplateDateModel.DATETIME] = new HashMap();
0809: }
0810: Map typedDateFormat = dateFormats[dateType];
0811:
0812: DateFormat format = (DateFormat) typedDateFormat.get(pattern);
0813: if (format != null) {
0814: return format;
0815: }
0816:
0817: // Get format from global format cache
0818: synchronized (localizedDateFormats) {
0819: Locale locale = getLocale();
0820: TimeZone timeZone = getTimeZone();
0821: DateFormatKey fk = new DateFormatKey(dateType, pattern,
0822: locale, timeZone);
0823: format = (DateFormat) localizedDateFormats.get(fk);
0824: if (format == null) {
0825: // Add format to global format cache. Note this is
0826: // globally done once per locale per pattern.
0827: StringTokenizer tok = new StringTokenizer(pattern, "_");
0828: int style = tok.hasMoreTokens() ? parseDateStyleToken(tok
0829: .nextToken())
0830: : DateFormat.DEFAULT;
0831: if (style != -1) {
0832: switch (dateType) {
0833: case TemplateDateModel.UNKNOWN: {
0834: throw new TemplateModelException(
0835: "Can't convert the date to string using a "
0836: + "built-in format, because it is not known which "
0837: + "parts of the date variable are in use. Use "
0838: + "?date, ?time or ?datetime built-in, or "
0839: + "?string.<format> or ?string(<format>) built-in "
0840: + "with explicit formatting pattern with this date.");
0841: }
0842: case TemplateDateModel.TIME: {
0843: format = DateFormat.getTimeInstance(style,
0844: locale);
0845: break;
0846: }
0847: case TemplateDateModel.DATE: {
0848: format = DateFormat.getDateInstance(style,
0849: locale);
0850: break;
0851: }
0852: case TemplateDateModel.DATETIME: {
0853: int timestyle = tok.hasMoreTokens() ? parseDateStyleToken(tok
0854: .nextToken())
0855: : style;
0856: if (timestyle != -1) {
0857: format = DateFormat.getDateTimeInstance(
0858: style, timestyle, locale);
0859: }
0860: break;
0861: }
0862: }
0863: }
0864: if (format == null) {
0865: try {
0866: format = new SimpleDateFormat(pattern, locale);
0867: } catch (IllegalArgumentException e) {
0868: throw new TemplateModelException("Can't parse "
0869: + pattern + " to a date format.", e);
0870: }
0871: }
0872: format.setTimeZone(timeZone);
0873: localizedDateFormats.put(fk, format);
0874: }
0875: }
0876:
0877: // Clone it and store the clone in the local cache
0878: format = (DateFormat) format.clone();
0879: typedDateFormat.put(pattern, format);
0880: return format;
0881: }
0882:
0883: int parseDateStyleToken(String token) {
0884: if ("short".equals(token)) {
0885: return DateFormat.SHORT;
0886: }
0887: if ("medium".equals(token)) {
0888: return DateFormat.MEDIUM;
0889: }
0890: if ("long".equals(token)) {
0891: return DateFormat.LONG;
0892: }
0893: if ("full".equals(token)) {
0894: return DateFormat.FULL;
0895: }
0896: return -1;
0897: }
0898:
0899: TemplateTransformModel getTransform(Expression exp)
0900: throws TemplateException {
0901: TemplateTransformModel ttm = null;
0902: TemplateModel tm = exp.getAsTemplateModel(this );
0903: if (tm instanceof TemplateTransformModel) {
0904: ttm = (TemplateTransformModel) tm;
0905: } else if (exp instanceof Identifier) {
0906: tm = getConfiguration().getSharedVariable(exp.toString());
0907: if (tm instanceof TemplateTransformModel) {
0908: ttm = (TemplateTransformModel) tm;
0909: }
0910: }
0911: return ttm;
0912: }
0913:
0914: /**
0915: * Returns the loop or macro local variable corresponding to this
0916: * variable name. Possibly null.
0917: * (Note that the misnomer is kept for backward compatibility: loop variables
0918: * are not local variables according to our terminology.)
0919: */
0920: public TemplateModel getLocalVariable(String name)
0921: throws TemplateModelException {
0922: if (localContextStack != null) {
0923: for (int i = localContextStack.size() - 1; i >= 0; i--) {
0924: LocalContext lc = (LocalContext) localContextStack
0925: .get(i);
0926: TemplateModel tm = lc.getLocalVariable(name);
0927: if (tm != null) {
0928: return tm;
0929: }
0930: }
0931: }
0932: return currentMacroContext == null ? null : currentMacroContext
0933: .getLocalVariable(name);
0934: }
0935:
0936: /**
0937: * Returns the variable that is visible in this context.
0938: * This is the correspondent to an FTL top-level variable reading expression.
0939: * That is, it tries to find the the variable in this order:
0940: * <ol>
0941: * <li>An loop variable (if we're in a loop or user defined directive body) such as foo_has_next
0942: * <li>A local variable (if we're in a macro)
0943: * <li>A variable defined in the current namespace (say, via <#assign ...>)
0944: * <li>A variable defined globally (say, via <#global ....>)
0945: * <li>Variable in the data model:
0946: * <ol>
0947: * <li>A variable in the root hash that was exposed to this
0948: rendering environment in the Template.process(...) call
0949: * <li>A shared variable set in the configuration via a call to Configuration.setSharedVariable(...)
0950: * </ol>
0951: * </li>
0952: * </ol>
0953: */
0954: public TemplateModel getVariable(String name)
0955: throws TemplateModelException {
0956: TemplateModel result = getLocalVariable(name);
0957: if (result == null) {
0958: result = currentNamespace.get(name);
0959: }
0960: if (result == null) {
0961: result = getGlobalVariable(name);
0962: }
0963: return result;
0964: }
0965:
0966: /**
0967: * Returns the globally visible variable of the given name (or null).
0968: * This is correspondent to FTL <code>.globals.<i>name</i></code>.
0969: * This will first look at variables that were assigned globally via:
0970: * <#global ...> and then at the data model exposed to the template.
0971: */
0972: public TemplateModel getGlobalVariable(String name)
0973: throws TemplateModelException {
0974: TemplateModel result = globalNamespace.get(name);
0975: if (result == null) {
0976: result = rootDataModel.get(name);
0977: }
0978: if (result == null) {
0979: result = getConfiguration().getSharedVariable(name);
0980: }
0981: return result;
0982: }
0983:
0984: /**
0985: * Sets a variable that is visible globally.
0986: * This is correspondent to FTL <code><#global <i>name</i>=<i>model</i>></code>.
0987: * This can be considered a convenient shorthand for:
0988: * getGlobalNamespace().put(name, model)
0989: */
0990: public void setGlobalVariable(String name, TemplateModel model) {
0991: globalNamespace.put(name, model);
0992: }
0993:
0994: /**
0995: * Sets a variable in the current namespace.
0996: * This is correspondent to FTL <code><#assign <i>name</i>=<i>model</i>></code>.
0997: * This can be considered a convenient shorthand for:
0998: * getCurrentNamespace().put(name, model)
0999: */
1000: public void setVariable(String name, TemplateModel model) {
1001: currentNamespace.put(name, model);
1002: }
1003:
1004: /**
1005: * Sets a local variable (one effective only during a macro invocation).
1006: * This is correspondent to FTL <code><#local <i>name</i>=<i>model</i>></code>.
1007: * @param name the identifier of the variable
1008: * @param model the value of the variable.
1009: * @throws IllegalStateException if the environment is not executing a
1010: * macro body.
1011: */
1012: public void setLocalVariable(String name, TemplateModel model) {
1013: if (currentMacroContext == null) {
1014: throw new IllegalStateException("Not executing macro body");
1015: }
1016: currentMacroContext.setLocalVar(name, model);
1017: }
1018:
1019: /**
1020: * Returns a set of variable names that are known at the time of call. This
1021: * includes names of all shared variables in the {@link Configuration},
1022: * names of all global variables that were assigned during the template processing,
1023: * names of all variables in the current name-space, names of all local variables
1024: * and loop variables. If the passed root data model implements the
1025: * {@link TemplateHashModelEx} interface, then all names it retrieves through a call to
1026: * {@link TemplateHashModelEx#keys()} method are returned as well.
1027: * The method returns a new Set object on each call that is completely
1028: * disconnected from the Environment. That is, modifying the set will have
1029: * no effect on the Environment object.
1030: */
1031: public Set getKnownVariableNames() throws TemplateModelException {
1032: // shared vars.
1033: Set set = getConfiguration().getSharedVariableNames();
1034:
1035: // root hash
1036: if (rootDataModel instanceof TemplateHashModelEx) {
1037: TemplateModelIterator rootNames = ((TemplateHashModelEx) rootDataModel)
1038: .keys().iterator();
1039: while (rootNames.hasNext()) {
1040: set.add(((TemplateScalarModel) rootNames.next())
1041: .getAsString());
1042: }
1043: }
1044:
1045: // globals
1046: for (TemplateModelIterator tmi = globalNamespace.keys()
1047: .iterator(); tmi.hasNext();) {
1048: set.add(((TemplateScalarModel) tmi.next()).getAsString());
1049: }
1050:
1051: // current name-space
1052: for (TemplateModelIterator tmi = currentNamespace.keys()
1053: .iterator(); tmi.hasNext();) {
1054: set.add(((TemplateScalarModel) tmi.next()).getAsString());
1055: }
1056:
1057: // locals and loop vars
1058: if (currentMacroContext != null) {
1059: set.addAll(currentMacroContext.getLocalVariableNames());
1060: }
1061: if (localContextStack != null) {
1062: for (int i = localContextStack.size() - 1; i >= 0; i--) {
1063: LocalContext lc = (LocalContext) localContextStack
1064: .get(i);
1065: set.addAll(lc.getLocalVariableNames());
1066: }
1067: }
1068: return set;
1069: }
1070:
1071: /**
1072: * Outputs the instruction stack. Useful for debugging.
1073: * {@link TemplateException}s incorporate this information in their stack
1074: * traces.
1075: */
1076: public void outputInstructionStack(PrintWriter pw) {
1077: pw.println("----------");
1078: ListIterator iter = elementStack.listIterator(elementStack
1079: .size());
1080: if (iter.hasPrevious()) {
1081: pw.print("==> ");
1082: TemplateElement prev = (TemplateElement) iter.previous();
1083: pw.print(prev.getDescription());
1084: pw.print(" [");
1085: pw.print(prev.getStartLocation());
1086: pw.println("]");
1087: }
1088: while (iter.hasPrevious()) {
1089: TemplateElement prev = (TemplateElement) iter.previous();
1090: if (prev instanceof UnifiedCall || prev instanceof Include) {
1091: String location = prev.getDescription() + " ["
1092: + prev.getStartLocation() + "]";
1093: if (location != null && location.length() > 0) {
1094: pw.print(" in ");
1095: pw.println(location);
1096: }
1097: }
1098: }
1099: pw.println("----------");
1100: pw.flush();
1101: }
1102:
1103: private void pushLocalContext(LocalContext localContext) {
1104: if (localContextStack == null) {
1105: localContextStack = new ArrayList();
1106: }
1107: localContextStack.add(localContext);
1108: }
1109:
1110: private void popLocalContext() {
1111: localContextStack.remove(localContextStack.size() - 1);
1112: }
1113:
1114: ArrayList getLocalContextStack() {
1115: return localContextStack;
1116: }
1117:
1118: /**
1119: * Returns the name-space for the name if exists, or null.
1120: * @param name the template path that you have used with the <code>import</code> directive
1121: * or {@link #importLib(String, String)} call, in normalized form. That is, the path must be an absolute
1122: * path, and it must not contain "/../" or "/./". The leading "/" is optional.
1123: */
1124: public Namespace getNamespace(String name) {
1125: if (name.startsWith("/"))
1126: name = name.substring(1);
1127: if (loadedLibs != null) {
1128: return (Namespace) loadedLibs.get(name);
1129: } else {
1130: return null;
1131: }
1132: }
1133:
1134: /**
1135: * Returns the main name-space.
1136: * This is correspondent of FTL <code>.main</code> hash.
1137: */
1138: public Namespace getMainNamespace() {
1139: return mainNamespace;
1140: }
1141:
1142: /**
1143: * Returns the main name-space.
1144: * This is correspondent of FTL <code>.namespace</code> hash.
1145: */
1146: public Namespace getCurrentNamespace() {
1147: return currentNamespace;
1148: }
1149:
1150: /**
1151: * Returns a fictitious name-space that contains the globally visible variables
1152: * that were created in the template, but not the variables of the data-model.
1153: * There is no such thing in FTL; this strange method was added because of the
1154: * JSP taglib support, since this imaginary name-space contains the page-scope
1155: * attributes.
1156: */
1157: public Namespace getGlobalNamespace() {
1158: return globalNamespace;
1159: }
1160:
1161: /**
1162: * Returns the data model hash.
1163: * This is correspondent of FTL <code>.datamodel</code> hash.
1164: * That is, it contains both the variables of the root hash passed to the
1165: * <code>Template.process(...)</code>, and the shared variables in the
1166: * <code>Configuration</code>.
1167: */
1168: public TemplateHashModel getDataModel() {
1169: return new TemplateHashModel() {
1170: public boolean isEmpty() {
1171: return false;
1172: }
1173:
1174: public TemplateModel get(String key)
1175: throws TemplateModelException {
1176: TemplateModel result = rootDataModel.get(key);
1177: if (result == null) {
1178: result = getConfiguration().getSharedVariable(key);
1179: }
1180: return result;
1181: }
1182: };
1183: }
1184:
1185: /**
1186: * Returns the read-only hash of globally visible variables.
1187: * This is the correspondent of FTL <code>.globals</code> hash.
1188: * That is, you see the variables created with
1189: * <code><#global ...></code>, and the variables of the data-model.
1190: * To create new global variables, use {@link #setGlobalVariable setGlobalVariable}.
1191: */
1192: public TemplateHashModel getGlobalVariables() {
1193: return new TemplateHashModel() {
1194: public boolean isEmpty() {
1195: return false;
1196: }
1197:
1198: public TemplateModel get(String key)
1199: throws TemplateModelException {
1200: TemplateModel result = globalNamespace.get(key);
1201: if (result == null) {
1202: result = rootDataModel.get(key);
1203: }
1204: if (result == null) {
1205: result = getConfiguration().getSharedVariable(key);
1206: }
1207: return result;
1208: }
1209: };
1210: }
1211:
1212: private void pushElement(TemplateElement element) {
1213: elementStack.add(element);
1214: }
1215:
1216: private void popElement() {
1217: elementStack.remove(elementStack.size() - 1);
1218: }
1219:
1220: public TemplateNodeModel getCurrentVisitorNode() {
1221: return currentVisitorNode;
1222: }
1223:
1224: /**
1225: * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt>
1226: */
1227: public void setCurrentVisitorNode(TemplateNodeModel node) {
1228: currentVisitorNode = node;
1229: }
1230:
1231: TemplateModel getNodeProcessor(TemplateNodeModel node)
1232: throws TemplateException {
1233: String nodeName = node.getNodeName();
1234: if (nodeName == null) {
1235: throw new TemplateException("Node name is null.", this );
1236: }
1237: TemplateModel result = getNodeProcessor(nodeName, node
1238: .getNodeNamespace(), 0);
1239:
1240: if (result == null) {
1241: String type = node.getNodeType();
1242:
1243: /* DD: Original version: */
1244: if (type == null) {
1245: type = "default";
1246: }
1247: result = getNodeProcessor("@" + type, null, 0);
1248:
1249: /* DD: Jonathan's non-BC version and IMHO otherwise wrong version:
1250: if (type != null) {
1251: result = getNodeProcessor("@" + type, null, 0);
1252: }
1253: if (result == null) {
1254: result = getNodeProcessor("@default", null, 0);
1255: }
1256: */
1257: }
1258: return result;
1259: }
1260:
1261: private TemplateModel getNodeProcessor(final String nodeName,
1262: final String nsURI, int startIndex)
1263: throws TemplateException {
1264: TemplateModel result = null;
1265: int i;
1266: for (i = startIndex; i < nodeNamespaces.size(); i++) {
1267: Namespace ns = null;
1268: try {
1269: ns = (Namespace) nodeNamespaces.get(i);
1270: } catch (ClassCastException cce) {
1271: throw new InvalidReferenceException(
1272: "A using clause should contain a sequence of namespaces or strings that indicate the location of importable macro libraries.",
1273: this );
1274: }
1275: result = getNodeProcessor(ns, nodeName, nsURI);
1276: if (result != null)
1277: break;
1278: }
1279: if (result != null) {
1280: this .nodeNamespaceIndex = i + 1;
1281: this .currentNodeName = nodeName;
1282: this .currentNodeNS = nsURI;
1283: }
1284: return result;
1285: }
1286:
1287: private TemplateModel getNodeProcessor(Namespace ns,
1288: String localName, String nsURI) throws TemplateException {
1289: TemplateModel result = null;
1290: if (nsURI == null) {
1291: result = ns.get(localName);
1292: if (!(result instanceof Macro)
1293: && !(result instanceof TemplateTransformModel)) {
1294: result = null;
1295: }
1296: } else {
1297: Template template = ns.getTemplate();
1298: String prefix = template.getPrefixForNamespace(nsURI);
1299: if (prefix == null) {
1300: // The other template cannot handle this node
1301: // since it has no prefix registered for the namespace
1302: return null;
1303: }
1304: if (prefix.length() > 0) {
1305: result = ns.get(prefix + ":" + localName);
1306: if (!(result instanceof Macro)
1307: && !(result instanceof TemplateTransformModel)) {
1308: result = null;
1309: }
1310: } else {
1311: if (nsURI.length() == 0) {
1312: result = ns.get(Template.NO_NS_PREFIX + ":"
1313: + localName);
1314: if (!(result instanceof Macro)
1315: && !(result instanceof TemplateTransformModel)) {
1316: result = null;
1317: }
1318: }
1319: if (nsURI.equals(template.getDefaultNS())) {
1320: result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX
1321: + ":" + localName);
1322: if (!(result instanceof Macro)
1323: && !(result instanceof TemplateTransformModel)) {
1324: result = null;
1325: }
1326: }
1327: if (result == null) {
1328: result = ns.get(localName);
1329: if (!(result instanceof Macro)
1330: && !(result instanceof TemplateTransformModel)) {
1331: result = null;
1332: }
1333: }
1334: }
1335: }
1336: return result;
1337: }
1338:
1339: /**
1340: * Emulates <code>include</code> directive, except that <code>name</code> must be tempate
1341: * root relative.
1342: *
1343: * <p>It's the same as <code>include(getTemplateForInclusion(name, encoding, parse))</code>.
1344: * But, you may want to separately call these two methods, so you can determine the source of
1345: * exceptions more precisely, and thus achieve more intelligent error handling.
1346: *
1347: * @see #getTemplateForInclusion(String name, String encoding, boolean parse)
1348: * @see #include(Template includedTemplate)
1349: */
1350: public void include(String name, String encoding, boolean parse)
1351: throws IOException, TemplateException {
1352: include(getTemplateForInclusion(name, encoding, parse));
1353: }
1354:
1355: /**
1356: * Gets a template for inclusion; used with {@link #include(Template includedTemplate)}.
1357: * The advantage over simply using <code>config.getTemplate(...)</code> is that it chooses
1358: * the default encoding as the <code>include</code> directive does.
1359: *
1360: * @param name the name of the template, relatively to the template root directory
1361: * (not the to the directory of the currently executing template file!).
1362: * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
1363: * to convert paths to template root relative paths.)
1364: * @param encoding the encoding of the obtained template. If null,
1365: * the encoding of the Template that is currently being processed in this
1366: * Environment is used.
1367: * @param parse whether to process a parsed template or just include the
1368: * unparsed template source.
1369: */
1370: public Template getTemplateForInclusion(String name,
1371: String encoding, boolean parse) throws IOException {
1372: if (encoding == null) {
1373: encoding = getTemplate().getEncoding();
1374: }
1375: if (encoding == null) {
1376: encoding = getConfiguration().getEncoding(this .getLocale());
1377: }
1378: return getConfiguration().getTemplate(name, getLocale(),
1379: encoding, parse);
1380: }
1381:
1382: /**
1383: * Processes a Template in the context of this <code>Environment</code>, including its
1384: * output in the <code>Environment</code>'s Writer.
1385: *
1386: * @param includedTemplate the template to process. Note that it does <em>not</em> need
1387: * to be a template returned by
1388: * {@link #getTemplateForInclusion(String name, String encoding, boolean parse)}.
1389: */
1390: public void include(Template includedTemplate)
1391: throws TemplateException, IOException {
1392: Template prevTemplate = getTemplate();
1393: setParent(includedTemplate);
1394: importMacros(includedTemplate);
1395: try {
1396: visit(includedTemplate.getRootTreeNode());
1397: } finally {
1398: setParent(prevTemplate);
1399: }
1400: }
1401:
1402: /**
1403: * Emulates <code>import</code> directive, except that <code>name</code> must be tempate
1404: * root relative.
1405: *
1406: * <p>It's the same as <code>importLib(getTemplateForImporting(name), namespace)</code>.
1407: * But, you may want to separately call these two methods, so you can determine the source of
1408: * exceptions more precisely, and thus achieve more intelligent error handling.
1409: *
1410: * @see #getTemplateForImporting(String name)
1411: * @see #importLib(Template includedTemplate, String namespace)
1412: */
1413: public Namespace importLib(String name, String namespace)
1414: throws IOException, TemplateException {
1415: return importLib(getTemplateForImporting(name), namespace);
1416: }
1417:
1418: /**
1419: * Gets a template for importing; used with
1420: * {@link #importLib(Template importedTemplate, String namespace)}. The advantage
1421: * over simply using <code>config.getTemplate(...)</code> is that it chooses the encoding
1422: * as the <code>import</code> directive does.
1423: *
1424: * @param name the name of the template, relatively to the template root directory
1425: * (not the to the directory of the currently executing template file!).
1426: * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
1427: * to convert paths to template root relative paths.)
1428: */
1429: public Template getTemplateForImporting(String name)
1430: throws IOException {
1431: return getTemplateForInclusion(name, null, true);
1432: }
1433:
1434: /**
1435: * Emulates <code>import</code> directive.
1436: *
1437: * @param loadedTemplate the template to import. Note that it does <em>not</em> need
1438: * to be a template returned by {@link #getTemplateForImporting(String name)}.
1439: */
1440: public Namespace importLib(Template loadedTemplate, String namespace)
1441: throws IOException, TemplateException {
1442: if (loadedLibs == null) {
1443: loadedLibs = new HashMap();
1444: }
1445: String templateName = loadedTemplate.getName();
1446: Namespace existingNamespace = (Namespace) loadedLibs
1447: .get(templateName);
1448: if (existingNamespace != null) {
1449: if (namespace != null) {
1450: setVariable(namespace, existingNamespace);
1451: }
1452: } else {
1453: Namespace newNamespace = new Namespace(loadedTemplate);
1454: if (namespace != null) {
1455: currentNamespace.put(namespace, newNamespace);
1456: if (currentNamespace == mainNamespace) {
1457: globalNamespace.put(namespace, newNamespace);
1458: }
1459: }
1460: Namespace prevNamespace = this .currentNamespace;
1461: this .currentNamespace = newNamespace;
1462: loadedLibs.put(templateName, currentNamespace);
1463: Writer prevOut = out;
1464: this .out = NULL_WRITER;
1465: try {
1466: include(loadedTemplate);
1467: } finally {
1468: this .out = prevOut;
1469: this .currentNamespace = prevNamespace;
1470: }
1471: }
1472: return (Namespace) loadedLibs.get(templateName);
1473: }
1474:
1475: String renderElementToString(TemplateElement te)
1476: throws IOException, TemplateException {
1477: Writer prevOut = out;
1478: try {
1479: StringWriter sw = new StringWriter();
1480: this .out = sw;
1481: visit(te);
1482: return sw.toString();
1483: } finally {
1484: this .out = prevOut;
1485: }
1486: }
1487:
1488: void importMacros(Template template) {
1489: for (Iterator it = template.getMacros().values().iterator(); it
1490: .hasNext();) {
1491: visitMacroDef((Macro) it.next());
1492: }
1493: }
1494:
1495: /**
1496: * @return the namespace URI registered for this prefix, or null.
1497: * This is based on the mappings registered in the current namespace.
1498: */
1499: public String getNamespaceForPrefix(String prefix) {
1500: return currentNamespace.getTemplate().getNamespaceForPrefix(
1501: prefix);
1502: }
1503:
1504: public String getPrefixForNamespace(String nsURI) {
1505: return currentNamespace.getTemplate().getPrefixForNamespace(
1506: nsURI);
1507: }
1508:
1509: /**
1510: * @return the default node namespace for the current FTL namespace
1511: */
1512: public String getDefaultNS() {
1513: return currentNamespace.getTemplate().getDefaultNS();
1514: }
1515:
1516: /**
1517: * A hook that Jython uses.
1518: */
1519: public Object __getitem__(String key) throws TemplateModelException {
1520: return BeansWrapper.getDefaultInstance().unwrap(
1521: getVariable(key));
1522: }
1523:
1524: /**
1525: * A hook that Jython uses.
1526: */
1527: public void __setitem__(String key, Object o)
1528: throws TemplateException {
1529: setGlobalVariable(key, getObjectWrapper().wrap(o));
1530: }
1531:
1532: private static final class NumberFormatKey {
1533: private final String pattern;
1534: private final Locale locale;
1535:
1536: NumberFormatKey(String pattern, Locale locale) {
1537: this .pattern = pattern;
1538: this .locale = locale;
1539: }
1540:
1541: public boolean equals(Object o) {
1542: if (o instanceof NumberFormatKey) {
1543: NumberFormatKey fk = (NumberFormatKey) o;
1544: return fk.pattern.equals(pattern)
1545: && fk.locale.equals(locale);
1546: }
1547: return false;
1548: }
1549:
1550: public int hashCode() {
1551: return pattern.hashCode() ^ locale.hashCode();
1552: }
1553: }
1554:
1555: private static final class DateFormatKey {
1556: private final int dateType;
1557: private final String pattern;
1558: private final Locale locale;
1559: private final TimeZone timeZone;
1560:
1561: DateFormatKey(int dateType, String pattern, Locale locale,
1562: TimeZone timeZone) {
1563: this .dateType = dateType;
1564: this .pattern = pattern;
1565: this .locale = locale;
1566: this .timeZone = timeZone;
1567: }
1568:
1569: public boolean equals(Object o) {
1570: if (o instanceof DateFormatKey) {
1571: DateFormatKey fk = (DateFormatKey) o;
1572: return dateType == fk.dateType
1573: && fk.pattern.equals(pattern)
1574: && fk.locale.equals(locale)
1575: && fk.timeZone.equals(timeZone);
1576: }
1577: return false;
1578: }
1579:
1580: public int hashCode() {
1581: return dateType ^ pattern.hashCode() ^ locale.hashCode()
1582: ^ timeZone.hashCode();
1583: }
1584: }
1585:
1586: public class Namespace extends SimpleHash {
1587:
1588: private Template template;
1589:
1590: Namespace() {
1591: this .template = Environment.this .getTemplate();
1592: }
1593:
1594: Namespace(Template template) {
1595: this .template = template;
1596: }
1597:
1598: /**
1599: * @return the Template object with which this Namespace is associated.
1600: */
1601: public Template getTemplate() {
1602: return template == null ? Environment.this .getTemplate()
1603: : template;
1604: }
1605: }
1606:
1607: static final Writer NULL_WRITER = new Writer() {
1608: public void write(char cbuf[], int off, int len) {
1609: }
1610:
1611: public void flush() {
1612: }
1613:
1614: public void close() {
1615: }
1616: };
1617:
1618: private static final Writer EMPTY_BODY_WRITER = new Writer() {
1619:
1620: public void write(char[] cbuf, int off, int len)
1621: throws IOException {
1622: if (len > 0) {
1623: throw new IOException(
1624: "This transform does not allow nested content.");
1625: }
1626: }
1627:
1628: public void flush() {
1629: }
1630:
1631: public void close() {
1632: }
1633: };
1634:
1635: }
|