Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    * Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
    * 
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    * 
    * http://www.apache.org/licenses/LICENSE-2.0
    * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package freemarker.core;
  
  import java.io.Writer;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  
Object that represents the runtime environment during template processing. For every invocation of a Template.process() method, a new instance of this object is created, and then discarded when process() returns. This object stores the set of temporary variables created by the template, the value of settings set by the template, the reference to the data model root, etc. Everything that is needed to fulfill the template processing job.

Data models that need to access the Environment object that represents the template processing on the current thread can use the getCurrentEnvironment() method.

If you need to modify or read this object before or after the process call, use freemarker.template.Template.createProcessingEnvironment(java.lang.Object,java.io.Writer,freemarker.template.ObjectWrapper)

  
  public final class Environment extends Configurable {
  
      private static final ThreadLocal threadEnv = new ThreadLocal();
  
      private static final Logger LOG = Logger.getLogger("freemarker.runtime");
      private static final Logger ATTEMPT_LOGGER = Logger.getLogger("freemarker.runtime.attempt");
  
      private static final Map JAVA_NUMBER_FORMATS = new HashMap();
  
      // Do not use this object directly; clone it first! DecimalFormat isn't
      // thread-safe.
      private static final DecimalFormat C_NUMBER_FORMAT
              = new DecimalFormat(
                     "0.################",
                     new DecimalFormatSymbols(.));
     static {
         .setGroupingUsed(false);
     }
 
     private final TemplateHashModel rootDataModel;
     private final ArrayList/*<TemplateElement>*/ instructionStack = new ArrayList();
     private final ArrayList recoveredErrorStack = new ArrayList();
 
     private NumberFormat cachedNumberFormat;
     private Map cachedNumberFormats;

    
Stores the date/time/date-time formatters that are used when no format is explicitly given at the place of formatting. That is, in situations like ${lastModified} or even ${lastModified?date}, but not in situations like ${lastModified?string.iso}.

The index of the array is calculated from what kind of formatter we want (see getCachedTemplateDateFormatIndex(int,boolean,boolean)):
Zoned input: 0: U, 1: T, 2: D, 3: DT
Zoneless input: 4: U, 5: T, 6: D, 7: DT
Sys def TZ + Zoned input: 8: U, 9: T, 10: D, 11: DT
Sys def TZ + Zoneless input: 12: U, 13: T, 14: D, 15: DT

This is a lazily filled cache. It starts out as null, then when first needed the array will be created. The array elements also start out as null-s, and they are filled as the particular kind of formatter is first needed.

 
     private static final int CACHED_TDFS_ZONELESS_INPUT_OFFS = 4;
     private static final int CACHED_TDFS_DEF_SYS_TZ_OFFS =  * 2;
     private static final int CACHED_TDFS_LENGTH =  * 2;
     private static final int CACHED_TDFS_SQL_D_T_TZ_OFFS = ;
     
 
     
     private NumberFormat cNumberFormat;
    
    
Used by the "iso_" built-ins to accelerate formatting.

 
     
     private Collator cachedCollator;
 
     private Writer out;
     private Macro.Context currentMacroContext;
     private ArrayList localContextStack
     private final Namespace mainNamespace;
     private HashMap loadedLibs;
     private Configurable legacyParent;
 
     private boolean inAttemptBlock;
     private Throwable lastThrowable;
     
     private TemplateModel lastReturnValue;
     private HashMap macroToNamespaceLookup = new HashMap();
 
     private TemplateNodeModel currentVisitorNode;    
     // Things we keep track of for the fallback mechanism.
     private int nodeNamespaceIndex;
     private String currentNodeNamecurrentNodeNS;
     
     private String cachedURLEscapingCharset;
     private boolean cachedURLEscapingCharsetSet;
 
     private boolean fastInvalidReferenceExceptions;
    
    
Retrieves the environment object associated with the current thread, or null if there's no template processing going on in this thread. Data model implementations that need access to the environment can call this method to obtain the environment object that represents the template processing that is currently running on the current thread.
 
     public static Environment getCurrentEnvironment()
     {
         return (Environment.get();
     }
     
     static void setCurrentEnvironment(Environment env) {
         .set(env);
     }
 
     public Environment(Template templatefinal TemplateHashModel rootDataModelWriter out)
     {
         super(template);
         this. = new Namespace(null);
         this. =  = new Namespace(template);
         this. = out;
         this. = rootDataModel;
         importMacros(template);
     }

    
Despite its name it just returns Configurable.getParent(). If freemarker.template.Configuration.getIncompatibleImprovements() is at least 2.3.22, then that will be the same as getMainTemplate(). Otherwise the returned value follows the Environment parent switchings that occur at #include/#import and #nested directive calls, that is, it's not very meaningful outside FreeMarker internals.

Deprecated:
Use getMainTemplate() instead (or getCurrentNamespace() and then Environment.Namespace.getTemplate()); the value returned by this method is often not what you expect when it comes to macro/function invocations.
 
     public Template getTemplate() {
         return (Template)getParent();
     }
    
    
Returns the same value as pre-IcI 2.3.22 getTemplate() did.
 
     Template getTemplate230() {
         Template legacyParent = (Templatethis.;
         return legacyParent != null ? legacyParent : getTemplate(); 
     }

    
Returns the topmost freemarker.template.Template, with other words, the one for which this Environment was created. That template will never change, like #include or macro calls don't change it.

Since:
2.3.22
See also:
getCurrentNamespace()
 
     public Template getMainTemplate() {
         return .getTemplate();
     }
    
    
Returns the freemarker.template.Template that we are "lexically" inside at the moment. This template will change when entering an #include or calling a macro or function in another template, or returning to yet another template with #nested. As such, it's useful in freemarker.template.TemplateDirectiveModel to find out if from where the directive was called from.

 
     public Template getCurrentTemplate() {
         int ln = .size();
         return ln == 0 ? getMainTemplate() : ((TemplateObject.get(ln - 1)).getTemplate();
     }

    
Gets the currently executing custom directive's call place information, or null if there's no executing custom directive. This currently only works for calls made from templates with the <@...> syntax. This should only be called from the freemarker.template.TemplateDirectiveModel that was invoked with <@...>, otherwise its return value is not defined by this API (it's usually null).

Since:
2.3.22
 
         int ln = .size();
         if (ln == 0) return null;
         TemplateElement te = (TemplateElement.get(ln - 1);
         if (te instanceof UnifiedCallreturn (UnifiedCallte;
         if (te instanceof Macro && ln > 1 && .get(ln - 2) instanceof UnifiedCall) {
             return (UnifiedCall.get(ln - 2);
         }
         return null;
     }
    
    
Deletes cached values that meant to be valid only during a single template execution.
 
     private void clearCachedValues() {
          = null;
          = null;
         
          = null;
         
          = null;
          = null;
          = false;
     }
    
    
Processes the template to which this environment belongs to.
 
     public void process() throws TemplateExceptionIOException {
         Object savedEnv = .get();
         .set(this);
         try {
             // Cached values from a previous execution are possibly outdated.
             clearCachedValues();
             try {
                 doAutoImportsAndIncludes(this);
                 visit(getTemplate().getRootTreeNode());
                 // It's here as we must not flush if there was an exception.
                 if (getAutoFlush()) {
                     .flush();
                 }
             } finally {
                 // It's just to allow the GC to free memory...
                 clearCachedValues();
             }
         } finally {
             .set(savedEnv);
         }
     }
    
    
"Visit" the template element.
 
     void visit(TemplateElement element)
     throws TemplateExceptionIOException
     {
         pushElement(element);
         try {
             element.accept(this);
         }
         catch (TemplateException te) {
             handleTemplateException(te);
         }
         finally {
             popElement();
         }
     }
    
    
Instead of pushing into the element stack, we replace the top element for the time the parameter element is visited, and then we restore the top element. The main purpose of this is to get rid of elements in the error stack trace that from user perspective shouldn't have a stack frame. The typical example is [#if foo]...[@failsHere/]...[/#if], where the #if call shouldn't be in the stack trace. (Simply marking #if as hidden in stack traces would be wrong, because we still want to show #if when its test expression fails.)
 
     void visitByHiddingParent(TemplateElement element)
     throws TemplateExceptionIOException {
         TemplateElement parent = replaceTopElement(element);
         try {
             element.accept(this);
         } catch (TemplateException te) {
             handleTemplateException(te);
         } finally {
             replaceTopElement(parent);
         }
     }
 
     private TemplateElement replaceTopElement(TemplateElement element) {
         return (TemplateElement.set(.size() - 1, element);
     }
 
     private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
     
     public void visit(final TemplateElement element,
             TemplateDirectiveModel directiveModelMap args
             final List bodyParameterNamesthrows TemplateExceptionIOException {
         TemplateDirectiveBody nested;
         if(element == null) {
             nested = null;
         }
         else {
             nested = new NestedElementTemplateDirectiveBody(element);
         }
         final TemplateModel[] outArgs;
         if(bodyParameterNames == null || bodyParameterNames.isEmpty()) {
             outArgs = ;
         }
         else {
             outArgs = new TemplateModel[bodyParameterNames.size()];
         }
         if(outArgs.length > 0) {
             pushLocalContext(new LocalContext() {
                 public TemplateModel getLocalVariable(String name) {
                     int index = bodyParameterNames.indexOf(name);
                     return index != -1 ? outArgs[index] : null;
                 }
 
                 public Collection getLocalVariableNames() {
                     return bodyParameterNames;
                 }
             });
         }
         try {
             directiveModel.execute(thisargsoutArgsnested);
         }
         finally {
             if(outArgs.length > 0) {
                 popLocalContext();
             }
         }
     }
    
    
"Visit" the template element, passing the output through a TemplateTransformModel

Parameters:
element the element to visit through a transform
transform the transform to pass the element output through
args optional arguments fed to the transform
 
     void visitAndTransform(TemplateElement element,
                TemplateTransformModel transform,
                Map args)
     throws TemplateExceptionIOException
     {
         try {
             Writer tw = transform.getWriter(args);
             if (tw == nulltw = ;
             TransformControl tc =
                 tw instanceof TransformControl
                 ? (TransformControl)tw
                 : null;
 
             Writer prevOut = ;
              = tw;
             try {
                 if(tc == null || tc.onStart() != .) {
                     do {
                         if(element != null) {
                             visitByHiddingParent(element);
                         }
                     } while(tc != null && tc.afterBody() == .);
                 }
             }
             catch(Throwable t) {
                 try {
                     if(tc != null) {
                         tc.onError(t);
                     }
                     else {
                         throw t;
                     }
                 }
                 catch(TemplateException e) {
                     throw e;
                 }
                 catch(IOException e) {
                     throw e;
                 }
                 catch(RuntimeException e) {
                     throw e;
                 }
                 catch(Error e) {
                     throw e;
                 }
                 catch(Throwable e) {
                     throw new UndeclaredThrowableException(e);
                 }
             }
             finally {
                  = prevOut;
                 tw.close();
             }
         }
         catch(TemplateException te) {
             handleTemplateException(te);
         }
     }
    
    
Visit a block using buffering/recovery
 
      void visitAttemptRecover(TemplateElement attemptBlockRecoveryBlock recoveryBlock
      throws TemplateExceptionIOException {
          Writer prevOut = this.;
          StringWriter sw = new StringWriter();
          this. = sw;
          TemplateException thrownException = null;
          boolean lastFIRE = setFastInvalidReferenceExceptions(false);
          boolean lastInAttemptBlock = 
          try {
               = true;
              visitByHiddingParent(attemptBlock);
          } catch (TemplateException te) {
              thrownException = te;
          } finally {
               = lastInAttemptBlock;
              setFastInvalidReferenceExceptions(lastFIRE);
              this. = prevOut;
          }
          if (thrownException != null) {
              if (.isDebugEnabled()) {
                  .debug("Error in attempt block " + 
                          attemptBlock.getStartLocationQuoted(), thrownException);
              }
              try {
                  .add(thrownException);
                  visit(recoveryBlock);
              } finally {
                  .remove(.size() -1);
              }
          } else {
              .write(sw.toString());
          }
      }
      
          if(.isEmpty()) {
              throw new _MiscTemplateException(this".error is not available outside of a #recover block");
          }
          return ((Throwable.get(.size() -1)).getMessage();
      }
     
     
Tells if we are inside an #attempt block (but before #recover). This can be useful for freemarker.template.TemplateExceptionHandler-s, as then they may don't want to print the error to the output, as #attempt will roll it back anyway.

Since:
2.3.20
 
      public boolean isInAttemptBlock() {
          return ;
      }


    
Used for #nested.
 
         Macro.Context invokingMacroContext = getCurrentMacroContext();
         ArrayList prevLocalContextStack = ;
         TemplateElement nestedContent = invokingMacroContext.nestedContent;
         if (nestedContent != null) {
             this. = invokingMacroContext.prevMacroContext;
              = invokingMacroContext.nestedContentNamespace;
             
             final Configurable prevParent;
             final boolean parentReplacementOn = isIcI2322OrLater();
             prevParent = getParent();
             if (parentReplacementOn) {
                 setParent(.getTemplate());
             } else {
                  = .getTemplate();
             }
             
             this. = invokingMacroContext.prevLocalContextStack;
             if (invokingMacroContext.nestedContentParameterNames != null) {
                 pushLocalContext(bodyCtx);
             }
             try {
                 visit(nestedContent);
             }
             finally {
                 if (invokingMacroContext.nestedContentParameterNames != null) {
                     popLocalContext();
                 }
                 this. = invokingMacroContext;
                  = getMacroNamespace(invokingMacroContext.getMacro());
                 if (parentReplacementOn) {
                     setParent(prevParent);
                 } else {
                      = prevParent;
                 }
                 this. = prevLocalContextStack;
             }
         }
     }

    
"visit" an IteratorBlock
 
     throws TemplateExceptionIOException
     {
         pushLocalContext(ictxt);
         try {
             return ictxt.accept(this);
         }
         catch (TemplateException te) {
             handleTemplateException(te);
             return true;
         }
         finally {
             popLocalContext();
         }
     }
    
    
Used for #visit and #recurse.
 
     void invokeNodeHandlerFor(TemplateNodeModel nodeTemplateSequenceModel namespaces
     throws TemplateExceptionIOException 
     {
         if ( == null) {
             SimpleSequence ss = new SimpleSequence(1);
             ss.add();
              = ss;
         }
         int prevNodeNamespaceIndex = this.;
         String prevNodeName = this.;
         String prevNodeNS = this.;
         TemplateSequenceModel prevNodeNamespaces = ;
         TemplateNodeModel prevVisitorNode = ;
          = node;
         if (namespaces != null) {
             this. = namespaces;
         }
         try {
             TemplateModel macroOrTransform = getNodeProcessor(node);
             if (macroOrTransform instanceof Macro) {
                 invoke((MacromacroOrTransformnullnullnullnull);
             }
             else if (macroOrTransform instanceof TemplateTransformModel) {
                 visitAndTransform(null, (TemplateTransformModelmacroOrTransformnull); 
             }
             else {
                 String nodeType = node.getNodeType();
                 if (nodeType != null) {
                     // If the node's type is 'text', we just output it.
                     if ((nodeType.equals("text") && node instanceof TemplateScalarModel)) 
                     {
                            .write(((TemplateScalarModelnode).getAsString());
                     }
                     else if (nodeType.equals("document")) {
                         recurse(nodenamespaces);
                     }
                     // We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
                     // we just ignore it.
                     else if (!nodeType.equals("pi"
                          && !nodeType.equals("comment"
                          && !nodeType.equals("document_type")) 
                     {
                         throw new _MiscTemplateException(
                                 thisnoNodeHandlerDefinedDescription(nodenode.getNodeNamespace(), nodeType));
                     }
                 }
                 else {
                     throw new _MiscTemplateException(
                             thisnoNodeHandlerDefinedDescription(nodenode.getNodeNamespace(), "default"));
                 }
             }
         } 
         finally {
             this. = prevVisitorNode;
             this. = prevNodeNamespaceIndex;
             this. = prevNodeName;
             this. = prevNodeNS;
             this. = prevNodeNamespaces;
         }
     }
 
             TemplateNodeModel nodeString nsString nodeType)
     throws TemplateModelException {
         String nsPrefix;
         if (ns != null) {
             if (ns.length() > 0) {
                 nsPrefix = " and namespace ";
             } else {
                 nsPrefix = " and no namespace";
             }
         } else {
             nsPrefix = "";
             ns = "";
         }
         return new Object[] { "No macro or directive is defined for node named ",  
                 new _DelayedJQuote(node.getNodeName()), nsPrefixns,
                 ", and there is no fallback handler called @"nodeType" either." };
     }
     
     void fallback() throws TemplateExceptionIOException {
         if (macroOrTransform instanceof Macro) {
             invoke((MacromacroOrTransformnullnullnullnull);
         }
         else if (macroOrTransform instanceof TemplateTransformModel) {
             visitAndTransform(null, (TemplateTransformModelmacroOrTransformnull); 
         }
     }
    
    
Calls the macro or function with the given arguments and nested block.
 
     void invoke(Macro macro
                Map namedArgsList positionalArgs
                List bodyParameterNamesTemplateElement nestedBlockthrows TemplateExceptionIOException {
         if (macro == .) {
             return;
         }
         
         pushElement(macro);
         try {
             final Macro.Context macroCtx = macro.new Context(thisnestedBlockbodyParameterNames);
             setMacroContextLocalsFromArguments(macroCtxmacronamedArgspositionalArgs);
             
             final Macro.Context prevMacroCtx = ;
              = macroCtx;
             
             final ArrayList prevLocalContextStack = ;
              = null;
             
             final Namespace prevNamespace = ;
              = (Namespace.get(macro);
             
             try {
                 macroCtx.runMacro(this);
             } catch (ReturnInstruction.Return re) {
                 // Not an error, just a <#return>
             } catch (TemplateException te) {
                 handleTemplateException(te);
             } finally {
                  = prevMacroCtx;
                  = prevLocalContextStack;
                  = prevNamespace;
             }
         } finally {
             popElement();
         }
     }

    
Sets the local variables corresponding to the macro call arguments in the macro context.
 
     private void setMacroContextLocalsFromArguments(
             final Macro.Context macroCtx,
             final Macro macro,
             final Map namedArgsfinal List positionalArgsthrows TemplateException_MiscTemplateException {
         String catchAllParamName = macro.getCatchAll();
         if (namedArgs != null) {
             final SimpleHash catchAllParamValue;
             if (catchAllParamName != null) {
                 catchAllParamValue = new SimpleHash((ObjectWrappernull);
                 macroCtx.setLocalVar(catchAllParamNamecatchAllParamValue);
             } else {
                 catchAllParamValue = null;
             }
             
             for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext();) {
                 final Map.Entry argNameAndValExp = (Map.Entryit.next();
                 final String argName = (StringargNameAndValExp.getKey();
                 final boolean isArgNameDeclared = macro.hasArgNamed(argName);
                 if (isArgNameDeclared || catchAllParamName != null) {
                     Expression argValueExp = (ExpressionargNameAndValExp.getValue();
                     TemplateModel argValue = argValueExp.eval(this);
                     if (isArgNameDeclared) {
                         macroCtx.setLocalVar(argNameargValue);
                     } else {
                         catchAllParamValue.put(argNameargValue);
                     }
                 } else {
                     throw new _MiscTemplateException(thisnew Object[] {
                             (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
                             " has no parameter with name "new _DelayedJQuote(argName), "." });
                 }
             }
         } else if (positionalArgs != null) {
             final SimpleSequence catchAllParamValue;
             if (catchAllParamName != null) {
                 catchAllParamValue = new SimpleSequence((ObjectWrappernull);
                 macroCtx.setLocalVar(catchAllParamNamecatchAllParamValue);
             } else {
                 catchAllParamValue = null;
             }
             
             String[] argNames = macro.getArgumentNamesInternal();
             final int argsCnt = positionalArgs.size();
             if (argNames.length < argsCnt && catchAllParamName == null) {
                 throw new _MiscTemplateException(thisnew Object[] { 
                         (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
                         " only accepts "new _DelayedToString(argNames.length), " parameters, but got ",
                         new _DelayedToString(argsCnt), "."});
             }
             for (int i = 0; i < argsCnti++) {
                 Expression argValueExp = (ExpressionpositionalArgs.get(i);
                 TemplateModel argValue = argValueExp.eval(this);
                 try {
                     if (i < argNames.length) {
                         String argName = argNames[i];
                         macroCtx.setLocalVar(argNameargValue);
                     } else {
                         catchAllParamValue.add(argValue);
                     }
                 } catch (RuntimeException re) {
                     throw new _MiscTemplateException(rethis);
                 }
             }
         }
     }
    
    
Defines the given macro in the current namespace (doesn't call it).
 
     void visitMacroDef(Macro macro) {
         .put(macro);
         .put(macro.getName(), macro);
     }
     
     Namespace getMacroNamespace(Macro macro) {
         return (Namespace.get(macro);
     }
     
     void recurse(TemplateNodeModel nodeTemplateSequenceModel namespaces)
     throws TemplateExceptionIOException 
     {
         if (node == null) {
             node = this.getCurrentVisitorNode();
             if (node == null) {
                 throw new _TemplateModelException(
                         "The target node of recursion is missing or null.");
             }
         }
         TemplateSequenceModel children = node.getChildNodes();
         if (children == nullreturn;
         for (int i=0; i<children.size(); i++) {
             TemplateNodeModel child = (TemplateNodeModelchildren.get(i);
             if (child != null) {
                 invokeNodeHandlerFor(childnamespaces);
             }
         }
     }
 
         return ;
     }
     
     private void handleTemplateException(TemplateException templateException)
         throws TemplateException
     {
         // Logic to prevent double-handling of the exception in
         // nested visit() calls.
         if( == templateException) {
             throw templateException;
         }
          = templateException;
 
         // Log the exception, if logTemplateExceptions isn't false. However, even if it's false, if we are inside
         // an #attempt block, it has to be logged, as it certainly won't bubble up to the caller of FreeMarker.
         if(.isErrorEnabled() && (isInAttemptBlock() || getLogTemplateExceptions())) {
             .error("Error executing FreeMarker template"templateException);
         }
 
         // Stop exception is not passed to the handler, but
         // explicitly rethrown.
         if(templateException instanceof StopException) {
             throw templateException;
         }
 
         // Finally, pass the exception to the handler
         getTemplateExceptionHandler().handleTemplateException(templateExceptionthis);
     }
 
     public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
         super.setTemplateExceptionHandler(templateExceptionHandler);
          = null;
     }
     
     public void setLocale(Locale locale) {
         Locale prevLocale = getLocale();
         super.setLocale(locale);
         if (!locale.equals(prevLocale)) {
              = null;
              = null;
     
             if ( != null) {
                 for (int i = 0; i < i++) {
                     final TemplateDateFormat f = [i];
                     if (f != null && f.isLocaleBound()) {
                         [i] = null;
                     }
                 }
             }
 
                  = null;
             }
                  = null;
             }
 
                  = null;
             }
                  = null;
             }
 
                  = null;
             }
                  = null;
             }
             
              = null;
         }
     }
 
     public void setTimeZone(TimeZone timeZone) {
         TimeZone prevTimeZone = getTimeZone();
         super.setTimeZone(timeZone);
         
         if (!timeZone.equals(prevTimeZone)) {
             if ( != null) {
                 for (int i = 0; i < i++) {
                     [i] = null;
                 }
             }
             
              = null;
              = null;
              = null;
             
              = null;
         }
     }
     
     public void setSQLDateAndTimeTimeZone(TimeZone timeZone) {
         TimeZone prevTimeZone = getSQLDateAndTimeTimeZone();
         super.setSQLDateAndTimeTimeZone(timeZone);
         
         if (!nullSafeEquals(timeZoneprevTimeZone)) {
             if ( != null) {
                 for (int i = i < i++) {
                     [i] = null;
                 }
             }
             
              = null;
              = null;
              = null;
             
              = null;
         }
     }
     
     // Replace with Objects.equals in Java 7
     private static boolean nullSafeEquals(Object o1Object o2) {
         if (o1 == o2return true;
         if (o1 == null || o2 == nullreturn false;
         return o1.equals(o2);
     }

    
Tells if the same concrete time zone is used for SQL date-only and time-only values as for other date/time/date-time values.
 
         if ( == null) {
              = Boolean.valueOf(
                     getSQLDateAndTimeTimeZone() == null 
                     || getSQLDateAndTimeTimeZone().equals(getTimeZone()));
         }
     }
     
     public void setURLEscapingCharset(String urlEscapingCharset) {
          = false;
         super.setURLEscapingCharset(urlEscapingCharset);
     }
     
     /*
      * Note that altough it's not allowed to set this setting with the
      * <tt>setting</tt> directive, it still must be allowed to set it from Java
      * code while the template executes, since some frameworks allow templates
      * to actually change the output encoding on-the-fly.
      */
     public void setOutputEncoding(String outputEncoding) {
          = false;
         super.setOutputEncoding(outputEncoding);
     }
    
    
Returns the name of the charset that should be used for URL encoding. This will be null if the information is not available. The function caches the return value, so it's quick to call it repeately.
 
         if (!) {
              = getURLEscapingCharset();
             if ( == null) {
                  = getOutputEncoding();
             }
              = true;
         }
         return ;
     }
 
     Collator getCollator() {
         if( == null) {
              = Collator.getInstance(getLocale());
         }
         return ;
     }
    
    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL "==" operator.

Since:
2.3.20
 
     public boolean applyEqualsOperator(TemplateModel leftValueTemplateModel rightValue)
             throws TemplateException {
         return EvalUtil.compare(leftValue.rightValuethis);
     }

    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL "==" operator, except that if the two types are incompatible, they are treated as non-equal instead of throwing an exception. Comparing dates of different types (date-only VS time-only VS date-time) will still throw an exception, however.

Since:
2.3.20
    public boolean applyEqualsOperatorLenient(TemplateModel leftValueTemplateModel rightValue)
            throws TemplateException {
        return EvalUtil.compareLenient(leftValue.rightValuethis);
    }
    
    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL "<" operator.

Since:
2.3.20
    public boolean applyLessThanOperator(TemplateModel leftValueTemplateModel rightValue)
            throws TemplateException {
        return EvalUtil.compare(leftValue.rightValuethis);
    }

    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL "<" operator.

Since:
2.3.20
    public boolean applyLessThanOrEqualsOperator(TemplateModel leftValueTemplateModel rightValue)
            throws TemplateException {
        return EvalUtil.compare(leftValue.rightValuethis);
    }
    
    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL ">" operator.

Since:
2.3.20
    public boolean applyGreaterThanOperator(TemplateModel leftValueTemplateModel rightValue)
            throws TemplateException {
        return EvalUtil.compare(leftValue.rightValuethis);
    }

    
Compares two freemarker.template.TemplateModel-s according the rules of the FTL ">=" operator.

Since:
2.3.20
    public boolean applyWithGreaterThanOrEqualsOperator(TemplateModel leftValueTemplateModel rightValue)
            throws TemplateException {
        return EvalUtil.compare(leftValue.rightValuethis);
    }
    public void setOut(Writer out) {
        this. = out;
    }
    public Writer getOut() {
        return ;
    }
    String formatNumber(Number number) {
        if( == null) {
        }
        return .format(number);
    }
    public void setNumberFormat(String formatName) {
        super.setNumberFormat(formatName);
         = null;
    }
    public void setTimeFormat(String timeFormat) {
        String prevTimeFormat = getTimeFormat();
        super.setTimeFormat(timeFormat);
        if (!timeFormat.equals(prevTimeFormat)) {
            if ( != null) {
                for (int i = 0; i < i += ) {
                    [i + .] = null;
                }
            }
        }
    }
    public void setDateFormat(String dateFormat) {
        String prevDateFormat = getDateFormat();
        super.setDateFormat(dateFormat);
        if (!dateFormat.equals(prevDateFormat)) {
            if ( != null) {
                for (int i = 0; i < i += ) {
                    [i + .] = null;
                }
            }
        }
    }
    public void setDateTimeFormat(String dateTimeFormat) {
        String prevDateTimeFormat = getDateTimeFormat();
        super.setDateTimeFormat(dateTimeFormat);
        if (!dateTimeFormat.equals(prevDateTimeFormat)) {
            if ( != null) {
                for (int i = 0; i < i += ) {
                    [i + .] = null;
                }
            }
        }
    }
    public Configuration getConfiguration() {
        return getTemplate().getConfiguration();
    }
    
        return ;
    }
    
    void setLastReturnValue(TemplateModel lastReturnValue) {
        this. = lastReturnValue;
    }
    
    void clearLastReturnValue() {
        this. = null;
    }
    {
        if( == null) {
             = new HashMap();
        }
        NumberFormat format = (NumberFormat.get(pattern);
        if(format != null)
        {
            return format;
        }
        // Get format from global format cache
        synchronized()
        {
            Locale locale = getLocale();
            NumberFormatKey fk = new NumberFormatKey(patternlocale);
            format = (NumberFormat).get(fk);
            if(format == null)
            {
                // Add format to global format cache. Note this is
                // globally done once per locale per pattern.
                if("number".equals(pattern))
                {
                    format = NumberFormat.getNumberInstance(locale);
                }
                else if("currency".equals(pattern))
                {
                    format = NumberFormat.getCurrencyInstance(locale);
                }
                else if("percent".equals(pattern))
                {
                    format = NumberFormat.getPercentInstance(locale);
                }
                else if ("computer".equals(pattern))
                {
                    format = getCNumberFormat();
                }
                else
                {
                    format = new DecimalFormat(patternnew DecimalFormatSymbols(getLocale()));
                }
                .put(fkformat);
            }
        }
        // Clone it and store the clone in the local cache
        format = (NumberFormat)format.clone();
        .put(patternformat);
        return format;
    }
    String formatDate(TemplateDateModel tdmExpression tdmSourceExprthrows TemplateModelException {
        Date date = EvalUtil.modelToDate(tdmtdmSourceExpr);
        try {
            boolean isSQLDateOrTime = isSQLDateOrTimeClass(date.getClass());
            return getTemplateDateFormat(
                    tdm.getDateType(), isSQLDateOrTimeshouldUseSQLDTTimeZone(isSQLDateOrTime), tdmSourceExpr)
                    .format(tdm);
        } catch (UnknownDateTypeFormattingUnsupportedException e) {
            throw MessageUtil.newCantFormatUnknownTypeDateException(tdmSourceExpre);
        } catch (UnformattableDateException e) {
            throw MessageUtil.newCantFormatDateException(tdmSourceExpre);
        }
    }
    String formatDate(TemplateDateModel tdmString formatDescriptorExpression tdmSourceExpr)
            throws