All Downloads are FREE. Search and download functionalities are using the official Maven repository.

freemarker.core.Configurable Maven / Gradle / Ivy

Go to download

Google App Engine compliant variation of FreeMarker. FreeMarker is a "template engine"; a generic tool to generate text output based on templates.

There is a newer version: 2.3.33
Show newest version
/*
 * Copyright (c) 2003 The Visigoth Software Society. All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Visigoth Software Society (http://www.visigoths.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 
 *    project contributors may be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
 *    nor may "FreeMarker" or "Visigoth" appear in their names
 *    without prior written permission of the Visigoth Software Society.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Visigoth Software Society. For more
 * information on the Visigoth Software Society, please see
 * http://www.visigoths.org/
 */

package freemarker.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.*;

import freemarker.template.*;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;
import freemarker.ext.beans.BeansWrapper;

/**
 * This is a common superclass of {@link freemarker.template.Configuration},
 * {@link freemarker.template.Template}, and {@link Environment} classes.
 * It provides settings that are common to each of them. FreeMarker
 * uses a three-level setting hierarchy - the return value of every setting
 * getter method on Configurable objects inherits its value from its parent 
 * Configurable object, unless explicitly overridden by a call to a 
 * corresponding setter method on the object itself. The parent of an 
 * Environment object is a Template object, the
 * parent of a Template object is a Configuration
 * object.
 *
 * @version $Id: Configurable.java,v 1.23.2.2 2007/04/02 13:46:43 szegedia Exp $
 * @author Attila Szegedi
 */
public class Configurable
{
    public static final String LOCALE_KEY = "locale";
    public static final String NUMBER_FORMAT_KEY = "number_format";
    public static final String TIME_FORMAT_KEY = "time_format";
    public static final String DATE_FORMAT_KEY = "date_format";
    public static final String DATETIME_FORMAT_KEY = "datetime_format";
    public static final String TIME_ZONE_KEY = "time_zone";
    public static final String CLASSIC_COMPATIBLE_KEY = "classic_compatible";
    public static final String TEMPLATE_EXCEPTION_HANDLER_KEY = "template_exception_handler";
    public static final String ARITHMETIC_ENGINE_KEY = "arithmetic_engine";
    public static final String OBJECT_WRAPPER_KEY = "object_wrapper";
    public static final String BOOLEAN_FORMAT_KEY = "boolean_format";
    public static final String OUTPUT_ENCODING_KEY = "output_encoding";
    public static final String URL_ESCAPING_CHARSET_KEY = "url_escaping_charset";
    public static final String STRICT_BEAN_MODELS = "strict_bean_models";
    /** @since 2.3.17 */
    public static final String AUTO_FLUSH_KEY = "auto_flush";
    /** @since 2.3.17 */
    public static final String NEW_BUILTIN_CLASS_RESOLVER_KEY = "new_builtin_class_resolver";

    private static final char COMMA = ',';
    
    private Configurable parent;
    private Properties properties;
    private HashMap customAttributes;
    
    private Locale locale;
    private String numberFormat;
    private String timeFormat;
    private String dateFormat;
    private String dateTimeFormat;
    private TimeZone timeZone;
    private String trueFormat;
    private String falseFormat;
    private Boolean classicCompatible;
    private TemplateExceptionHandler templateExceptionHandler;
    private ArithmeticEngine arithmeticEngine;
    private ObjectWrapper objectWrapper;
    private String outputEncoding;
    private boolean outputEncodingSet;
    private String urlEscapingCharset;
    private boolean urlEscapingCharsetSet;
    private Boolean autoFlush;
    private TemplateClassResolver newBuiltinClassResolver;
    
    public Configurable() {
        parent = null;
        locale = Locale.getDefault();
        timeZone = TimeZone.getDefault();
        numberFormat = "number";
        timeFormat = "";
        dateFormat = "";
        dateTimeFormat = "";
        trueFormat = "true";
        falseFormat = "false";
        classicCompatible = Boolean.FALSE;
        templateExceptionHandler = TemplateExceptionHandler.DEBUG_HANDLER;
        arithmeticEngine = ArithmeticEngine.BIGDECIMAL_ENGINE;
        objectWrapper = ObjectWrapper.DEFAULT_WRAPPER;
        autoFlush = Boolean.TRUE;
        newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER;
        // outputEncoding and urlEscapingCharset defaults to null,
        // which means "not specified"
        
        properties = new Properties();
        properties.setProperty(LOCALE_KEY, locale.toString());
        properties.setProperty(TIME_FORMAT_KEY, timeFormat);
        properties.setProperty(DATE_FORMAT_KEY, dateFormat);
        properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat);
        properties.setProperty(TIME_ZONE_KEY, timeZone.getID());
        properties.setProperty(NUMBER_FORMAT_KEY, numberFormat);
        properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
        properties.setProperty(TEMPLATE_EXCEPTION_HANDLER_KEY, templateExceptionHandler.getClass().getName());
        properties.setProperty(ARITHMETIC_ENGINE_KEY, arithmeticEngine.getClass().getName());
        properties.setProperty(BOOLEAN_FORMAT_KEY, "true,false");
        properties.setProperty(AUTO_FLUSH_KEY, autoFlush.toString());
        properties.setProperty(NEW_BUILTIN_CLASS_RESOLVER_KEY, newBuiltinClassResolver.getClass().getName());
        // as outputEncoding and urlEscapingCharset defaults to null, 
        // they are not set
        
        customAttributes = new HashMap();
    }
    
    /**
     * Creates a new instance. Normally you do not need to use this constructor,
     * as you don't use Configurable directly, but its subclasses.
     */
    public Configurable(Configurable parent) {
        this.parent = parent;
        locale = null;
        numberFormat = null;
        trueFormat = null;
        falseFormat = null;
        classicCompatible = null;
        templateExceptionHandler = null;
        properties = new Properties(parent.properties);
        customAttributes = new HashMap();
    }

    protected Object clone() throws CloneNotSupportedException {
        Configurable copy = (Configurable)super.clone();
        copy.properties = new Properties(properties);
        copy.customAttributes = (HashMap)customAttributes.clone();
        return copy;
    }
    
    /**
     * Returns the parent Configurable object of this object.
     * The parent stores the default values for this configurable. For example,
     * the parent of the {@link freemarker.template.Template} object is the
     * {@link freemarker.template.Configuration} object, so setting values not
     * specfied on template level are specified by the confuration object.
     *
     * @return the parent Configurable object, or null, if this is
     *    the root Configurable object.
     */
    public final Configurable getParent() {
        return parent;
    }
    
    /**
     * Reparenting support. This is used by Environment when it includes a
     * template - the included template becomes the parent configurable during
     * its evaluation.
     */
    final void setParent(Configurable parent) {
        this.parent = parent;
    }
    
    /**
     * Toggles the "Classic Compatibile" mode. For a comprehensive description
     * of this mode, see {@link #isClassicCompatible()}.
     */
    public void setClassicCompatible(boolean classicCompatibility) {
        this.classicCompatible = classicCompatibility ? Boolean.TRUE : Boolean.FALSE;
        properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
    }

    /**
     * Returns whether the engine runs in the "Classic Compatibile" mode.
     * When this mode is active, the engine behavior is altered in following
     * way: (these resemble the behavior of the 1.7.x line of FreeMarker engine,
     * now named "FreeMarker Classic", hence the name).
     * 
    *
  • handle undefined expressions gracefully. Namely when an expression * "expr" evaluates to null: *
      *
    • as argument of the <assign varname=expr> directive, * ${expr} directive, otherexpr == expr or * otherexpr != expr conditional expressions, or * hash[expr] expression, then it is treated as empty string. *
    • *
    • as argument of <list expr as item> or * <foreach item in expr>, the loop body is not executed * (as if it were a 0-length list) *
    • *
    • as argument of <if> directive, or otherwise where a * boolean expression is expected, it is treated as false *
    • *
    *
  • *
  • Non-boolean models are accepted in <if> directive, * or as operands of logical operators. "Empty" models (zero-length string, * empty sequence or hash) are evaluated as false, all others are evaluated as * true.
  • *
  • When boolean value is treated as a string (i.e. output in * ${...} directive, or concatenated with other string), true * values are converted to string "true", false values are converted to * empty string. *
  • *
  • Scalar models supplied to <list> and * <foreach> are treated as a one-element list consisting * of the passed model. *
  • *
  • Paths parameter of <include> will be interpreted as * absolute path. *
  • *
* In all other aspects, the engine is a 2.1 engine even in compatibility * mode - you don't lose any of the new functionality by enabling it. */ public boolean isClassicCompatible() { return classicCompatible != null ? classicCompatible.booleanValue() : parent.isClassicCompatible(); } /** * Sets the locale to assume when searching for template files with no * explicit requested locale. */ public void setLocale(Locale locale) { if (locale == null) throw new IllegalArgumentException("Setting \"locale\" can't be null"); this.locale = locale; properties.setProperty(LOCALE_KEY, locale.toString()); } /** * Returns the time zone to use when formatting time values. Defaults to * system time zone. */ public TimeZone getTimeZone() { return timeZone != null ? timeZone : parent.getTimeZone(); } /** * Sets the time zone to use when formatting time values. */ public void setTimeZone(TimeZone timeZone) { if (timeZone == null) throw new IllegalArgumentException("Setting \"time_zone\" can't be null"); this.timeZone = timeZone; properties.setProperty(TIME_ZONE_KEY, timeZone.getID()); } /** * Returns the assumed locale when searching for template files with no * explicit requested locale. Defaults to system locale. */ public Locale getLocale() { return locale != null ? locale : parent.getLocale(); } /** * Sets the number format used to convert numbers to strings. */ public void setNumberFormat(String numberFormat) { if (numberFormat == null) throw new IllegalArgumentException("Setting \"number_format\" can't be null"); this.numberFormat = numberFormat; properties.setProperty(NUMBER_FORMAT_KEY, numberFormat); } /** * Returns the default number format used to convert numbers to strings. * Defaults to "number" */ public String getNumberFormat() { return numberFormat != null ? numberFormat : parent.getNumberFormat(); } public void setBooleanFormat(String booleanFormat) { if (booleanFormat == null) { throw new IllegalArgumentException("Setting \"boolean_format\" can't be null"); } int comma = booleanFormat.indexOf(COMMA); if(comma == -1) { throw new IllegalArgumentException("Setting \"boolean_format\" must consist of two comma-separated values for true and false respectively"); } trueFormat = booleanFormat.substring(0, comma); falseFormat = booleanFormat.substring(comma + 1); properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat); } public String getBooleanFormat() { if(trueFormat == null) { return parent.getBooleanFormat(); } return trueFormat + COMMA + falseFormat; } String getBooleanFormat(boolean value) { return value ? getTrueFormat() : getFalseFormat(); } private String getTrueFormat() { return trueFormat != null ? trueFormat : parent.getTrueFormat(); } private String getFalseFormat() { return falseFormat != null ? falseFormat : parent.getFalseFormat(); } /** * Sets the date format used to convert date models representing time-only * values to strings. */ public void setTimeFormat(String timeFormat) { if (timeFormat == null) throw new IllegalArgumentException("Setting \"time_format\" can't be null"); this.timeFormat = timeFormat; properties.setProperty(TIME_FORMAT_KEY, timeFormat); } /** * Returns the date format used to convert date models representing * time-only dates to strings. * Defaults to "time" */ public String getTimeFormat() { return timeFormat != null ? timeFormat : parent.getTimeFormat(); } /** * Sets the date format used to convert date models representing date-only * dates to strings. */ public void setDateFormat(String dateFormat) { if (dateFormat == null) throw new IllegalArgumentException("Setting \"date_format\" can't be null"); this.dateFormat = dateFormat; properties.setProperty(DATE_FORMAT_KEY, dateFormat); } /** * Returns the date format used to convert date models representing * date-only dates to strings. * Defaults to "date" */ public String getDateFormat() { return dateFormat != null ? dateFormat : parent.getDateFormat(); } /** * Sets the date format used to convert date models representing datetime * dates to strings. */ public void setDateTimeFormat(String dateTimeFormat) { if (dateTimeFormat == null) throw new IllegalArgumentException("Setting \"datetime_format\" can't be null"); this.dateTimeFormat = dateTimeFormat; properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat); } /** * Returns the date format used to convert date models representing datetime * dates to strings. * Defaults to "datetime" */ public String getDateTimeFormat() { return dateTimeFormat != null ? dateTimeFormat : parent.getDateTimeFormat(); } /** * Sets the exception handler used to handle template exceptions. * * @param templateExceptionHandler the template exception handler to use for * handling {@link TemplateException}s. By default, * {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER} is used. */ public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) { if (templateExceptionHandler == null) throw new IllegalArgumentException("Setting \"template_exception_handler\" can't be null"); this.templateExceptionHandler = templateExceptionHandler; properties.setProperty(TEMPLATE_EXCEPTION_HANDLER_KEY, templateExceptionHandler.getClass().getName()); } /** * Retrieves the exception handler used to handle template exceptions. */ public TemplateExceptionHandler getTemplateExceptionHandler() { return templateExceptionHandler != null ? templateExceptionHandler : parent.getTemplateExceptionHandler(); } /** * Sets the arithmetic engine used to perform arithmetic operations. * * @param arithmeticEngine the arithmetic engine used to perform arithmetic * operations.By default, {@link ArithmeticEngine#BIGDECIMAL_ENGINE} is * used. */ public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) { if (arithmeticEngine == null) throw new IllegalArgumentException("Setting \"arithmetic_engine\" can't be null"); this.arithmeticEngine = arithmeticEngine; properties.setProperty(ARITHMETIC_ENGINE_KEY, arithmeticEngine.getClass().getName()); } /** * Retrieves the arithmetic engine used to perform arithmetic operations. */ public ArithmeticEngine getArithmeticEngine() { return arithmeticEngine != null ? arithmeticEngine : parent.getArithmeticEngine(); } /** * Sets the object wrapper used to wrap objects to template models. * * @param objectWrapper the object wrapper used to wrap objects to template * models.By default, {@link ObjectWrapper#DEFAULT_WRAPPER} is used. */ public void setObjectWrapper(ObjectWrapper objectWrapper) { if (objectWrapper == null) throw new IllegalArgumentException("Setting \"object_wrapper\" can't be null"); this.objectWrapper = objectWrapper; properties.setProperty(OBJECT_WRAPPER_KEY, objectWrapper.getClass().getName()); } /** * Retrieves the object wrapper used to wrap objects to template models. */ public ObjectWrapper getObjectWrapper() { return objectWrapper != null ? objectWrapper : parent.getObjectWrapper(); } /** * Sets the output encoding. Allows null, which means that the * output encoding is not known. */ public void setOutputEncoding(String outputEncoding) { this.outputEncoding = outputEncoding; // java.util.Properties doesn't allow null value! if (outputEncoding != null) { properties.setProperty(OUTPUT_ENCODING_KEY, outputEncoding); } else { properties.remove(OUTPUT_ENCODING_KEY); } outputEncodingSet = true; } public String getOutputEncoding() { return outputEncodingSet ? outputEncoding : (parent != null ? parent.getOutputEncoding() : null); } /** * Sets the URL escaping charset. Allows null, which means that the * output encoding will be used for URL escaping. */ public void setURLEscapingCharset(String urlEscapingCharset) { this.urlEscapingCharset = urlEscapingCharset; // java.util.Properties doesn't allow null value! if (urlEscapingCharset != null) { properties.setProperty(URL_ESCAPING_CHARSET_KEY, urlEscapingCharset); } else { properties.remove(URL_ESCAPING_CHARSET_KEY); } urlEscapingCharsetSet = true; } public String getURLEscapingCharset() { return urlEscapingCharsetSet ? urlEscapingCharset : (parent != null ? parent.getURLEscapingCharset() : null); } /** * Sets the {@link TemplateClassResolver} that is used when the * new built-in is called in a template. That is, when * a template contains the "com.example.SomeClassName"?new * expression, this object will be called to resolve the * "com.example.SomeClassName" string to a class. The default * value is {@link TemplateClassResolver#UNRESTRICTED_RESOLVER} in * FreeMarker 2.3.x, and {@link TemplateClassResolver#SAFER_RESOLVER} * starting from FreeMarker 2.4.0. If you allow users to upload templates, * it's important to use a custom restrictive {@link TemplateClassResolver}. * * @since 2.3.17 */ public void setNewBuiltinClassResolver(TemplateClassResolver newBuiltinClassResolver) { if (newBuiltinClassResolver == null) throw new IllegalArgumentException( "Setting \"" + NEW_BUILTIN_CLASS_RESOLVER_KEY + "\" can't be null."); this.newBuiltinClassResolver = newBuiltinClassResolver; properties.setProperty(NEW_BUILTIN_CLASS_RESOLVER_KEY, newBuiltinClassResolver.getClass().getName()); } /** * Retrieves the {@link TemplateClassResolver} used * to resolve classes when "SomeClassName"?new is called in a template. * * @since 2.3.17 */ public TemplateClassResolver getNewBuiltinClassResolver() { return newBuiltinClassResolver != null ? newBuiltinClassResolver : parent.getNewBuiltinClassResolver(); } /** * Sets whether the output {@link Writer} is automatically flushed at * the end of {@link Template#process(Object, Writer)} (and its * overloads). The default is {@code true}. * *

Using {@code false} is needed for example when a Web page is composed * from several boxes (like portlets, GUI panels, etc.) that aren't inserted * with #include (or with similar directives) into a master * FreeMarker template, rather they are all processed with a separate * {@link Template#process(Object, Writer)} call. In a such scenario the * automatic flushes would commit the HTTP response after each box, hence * interfering with full-page buffering, and also possibly decreasing * performance with too frequent and too early response buffer flushes. * * @since 2.3.17 */ public void setAutoFlush(boolean autoFlush) { this.autoFlush = autoFlush ? Boolean.TRUE : Boolean.FALSE; properties.setProperty(AUTO_FLUSH_KEY, String.valueOf(autoFlush)); } /** * See {@link #setAutoFlush(boolean)} * * @since 2.3.17 */ public boolean getAutoFlush() { return autoFlush != null ? autoFlush.booleanValue() : (parent != null ? parent.getAutoFlush() : true); } private static final String ALLOWED_CLASSES = "allowed_classes"; private static final String TRUSTED_TEMPLATES = "trusted_templates"; /** * Sets a setting by a name and string value. * *

List of supported names and their valid values: *

    *
  • "locale": local codes with the usual format, such as "en_US". *
  • "classic_compatible": * "true", "false", "yes", "no", * "t", "f", "y", "n". * Case insensitive. *
  • "template_exception_handler": If the value contains dot, then it is * interpreted as class name, and the object will be created with * its parameterless constructor. If the value does not contain dot, * then it must be one of these special values: * "rethrow", "debug", * "html_debug", "ignore" (case insensitive). *
  • "arithmetic_engine": If the value contains dot, then it is * interpreted as class name, and the object will be created with * its parameterless constructor. If the value does not contain dot, * then it must be one of these special values: * "bigdecimal", "conservative" (case insensitive). *
  • "object_wrapper": If the value contains dot, then it is * interpreted as class name, and the object will be created with * its parameterless constructor. If the value does not contain dot, * then it must be one of these special values: * "simple", "beans", "jython" (case insensitive). *
  • "number_format": pattern as java.text.DecimalFormat defines. *
  • "boolean_format": the textual value for boolean true and false, * separated with comma. For example "yes,no". *
  • "date_format", "time_format", "datetime_format": patterns as * java.text.SimpleDateFormat defines. *
  • "time_zone": time zone, with the format as * java.util.TimeZone.getTimeZone defines. For example "GMT-8:00" or * "America/Los_Angeles" *
  • "output_encoding": Informs FreeMarker about the charset * used for the output. As FreeMarker outputs character stream (not * byte stream), it is not aware of the output charset unless the * software that encloses it tells it explicitly with this setting. * Some templates may use FreeMarker features that require this. *
  • "url_escaping_charset": If this setting is set, then it * overrides the value of the "output_encoding" setting when * FreeMarker does URL encoding. *
  • "auto_flush": see {@link #setAutoFlush(boolean)}. * Since 2.3.17. *
  • "new_builtin_class_resolver": * see {@link #setNewBuiltinClassResolver(TemplateClassResolver)}. * Since 2.3.17. The value must be one of these (ignore the * quotation marks): *
      *
    1. "unrestricted": * Use {@link TemplateClassResolver#UNRESTRICTED_RESOLVER} *
    2. "safer": * Use {@link TemplateClassResolver#SAFER_RESOLVER} *
    3. "allows_nothing": * Use {@link TemplateClassResolver#ALLOWS_NOTHING_RESOLVER} *
    4. Something that contains colon will use * {@link OptInTemplateClassResolver} and is expected to * store comma separated values (possibly quoted) segmented * with "allowed_classes:" and/or * "trusted_templates:". Examples of valid values: * * * * * * *
      Setting value * Meaning *
      allowed_classes: com.example.C1, com.example.C2, * trusted_templates: lib/*, safe.ftl * * Only allow instantiating the com.example.C1 and * com.example.C2 classes. But, allow templates * within the lib/ directory (like * lib/foo/bar.ftl) and template safe.ftl * (that does not match foo/safe.ftl, only * exactly safe.ftl) to instantiate anything * that {@link TemplateClassResolver#SAFER_RESOLVER} allows. *
      * allowed_classes: com.example.C1, com.example.C2 * Only allow instantiating the com.example.C1 and * com.example.C2 classes. There are no * trusted templates. *
      trusted_templates: lib/*, safe.ftl * * Do not allow instantiating any classes, except in * templates inside lib/ or in template * safe.ftl. *
      * For more details see {@link OptInTemplateClassResolver}. *
    5. Otherwise if the value contains dot, it's interpreted as * a full-qualified class name, and the object will be created * with its parameterless constructor. *
    *
* * @param key the name of the setting. * @param value the string that describes the new value of the setting. * * @throws UnknownSettingException if the key is wrong. * @throws TemplateException if the new value of the setting can't be set * for any other reasons. */ public void setSetting(String key, String value) throws TemplateException { try { if (LOCALE_KEY.equals(key)) { setLocale(StringUtil.deduceLocale(value)); } else if (NUMBER_FORMAT_KEY.equals(key)) { setNumberFormat(value); } else if (TIME_FORMAT_KEY.equals(key)) { setTimeFormat(value); } else if (DATE_FORMAT_KEY.equals(key)) { setDateFormat(value); } else if (DATETIME_FORMAT_KEY.equals(key)) { setDateTimeFormat(value); } else if (TIME_ZONE_KEY.equals(key)) { setTimeZone(TimeZone.getTimeZone(value)); } else if (CLASSIC_COMPATIBLE_KEY.equals(key)) { setClassicCompatible(StringUtil.getYesNo(value)); } else if (TEMPLATE_EXCEPTION_HANDLER_KEY.equals(key)) { if (value.indexOf('.') == -1) { if ("debug".equalsIgnoreCase(value)) { setTemplateExceptionHandler( TemplateExceptionHandler.DEBUG_HANDLER); } else if ("html_debug".equalsIgnoreCase(value)) { setTemplateExceptionHandler( TemplateExceptionHandler.HTML_DEBUG_HANDLER); } else if ("ignore".equalsIgnoreCase(value)) { setTemplateExceptionHandler( TemplateExceptionHandler.IGNORE_HANDLER); } else if ("rethrow".equalsIgnoreCase(value)) { setTemplateExceptionHandler( TemplateExceptionHandler.RETHROW_HANDLER); } else { throw invalidSettingValueException(key, value); } } else { setTemplateExceptionHandler( (TemplateExceptionHandler) ClassUtil.forName(value) .newInstance()); } } else if (ARITHMETIC_ENGINE_KEY.equals(key)) { if (value.indexOf('.') == -1) { if ("bigdecimal".equalsIgnoreCase(value)) { setArithmeticEngine(ArithmeticEngine.BIGDECIMAL_ENGINE); } else if ("conservative".equalsIgnoreCase(value)) { setArithmeticEngine(ArithmeticEngine.CONSERVATIVE_ENGINE); } else { throw invalidSettingValueException(key, value); } } else { setArithmeticEngine( (ArithmeticEngine) ClassUtil.forName(value) .newInstance()); } } else if (OBJECT_WRAPPER_KEY.equals(key)) { if (value.indexOf('.') == -1) { if ("default".equalsIgnoreCase(value)) { setObjectWrapper(ObjectWrapper.DEFAULT_WRAPPER); } else if ("simple".equalsIgnoreCase(value)) { setObjectWrapper(ObjectWrapper.SIMPLE_WRAPPER); } else if ("beans".equalsIgnoreCase(value)) { setObjectWrapper(ObjectWrapper.BEANS_WRAPPER); } else if ("jython".equalsIgnoreCase(value)) { Class clazz = Class.forName( "freemarker.ext.jython.JythonWrapper"); setObjectWrapper( (ObjectWrapper) clazz.getField("INSTANCE").get(null)); } else { throw invalidSettingValueException(key, value); } } else { setObjectWrapper((ObjectWrapper) ClassUtil.forName(value) .newInstance()); } } else if (BOOLEAN_FORMAT_KEY.equals(key)) { setBooleanFormat(value); } else if (OUTPUT_ENCODING_KEY.equals(key)) { setOutputEncoding(value); } else if (URL_ESCAPING_CHARSET_KEY.equals(key)) { setURLEscapingCharset(value); } else if (STRICT_BEAN_MODELS.equals(key)) { setStrictBeanModels(StringUtil.getYesNo(value)); } else if (AUTO_FLUSH_KEY.equals(key)) { setAutoFlush(StringUtil.getYesNo(value)); } else if (NEW_BUILTIN_CLASS_RESOLVER_KEY.equals(key)) { if ("unrestricted".equals(value)) { setNewBuiltinClassResolver(TemplateClassResolver.UNRESTRICTED_RESOLVER); } else if ("safer".equals(value)) { setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); } else if ("allows_nothing".equals(value)) { setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER); } else if (value.indexOf(":") != -1) { List segments = parseAsSegmentedList(value); Set allowedClasses = null; List trustedTemplates = null; for (int i = 0; i < segments.size(); i++) { KeyValuePair kv = (KeyValuePair) segments.get(i); String segmentKey = (String) kv.getKey(); List segmentValue = (List) kv.getValue(); if (segmentKey.equals(ALLOWED_CLASSES)) { allowedClasses = new HashSet(segmentValue); } else if (segmentKey.equals(TRUSTED_TEMPLATES)) { trustedTemplates = segmentValue; } else { throw new ParseException( "Unrecognized list segment key: " + StringUtil.jQuote(segmentKey) + ". Supported keys are: \"" + ALLOWED_CLASSES + "\", \"" + TRUSTED_TEMPLATES + "\"", 0, 0); } } setNewBuiltinClassResolver( new OptInTemplateClassResolver(allowedClasses, trustedTemplates)); } else if (value.indexOf('.') == -1) { setNewBuiltinClassResolver((TemplateClassResolver) ClassUtil.forName(value) .newInstance()); } else { throw invalidSettingValueException(key, value); } } else { throw unknownSettingException(key); } } catch(Exception e) { throw new TemplateException( "Failed to set setting " + StringUtil.jQuote(key) + " to value " + StringUtil.jQuote(value) + "; see cause exception.", e, getEnvironment()); } } public void setStrictBeanModels(boolean strict) { if (!(objectWrapper instanceof BeansWrapper)) { throw new IllegalStateException("The value of the " + OBJECT_WRAPPER_KEY + " setting isn't a " + BeansWrapper.class.getName() + "."); } ((BeansWrapper) objectWrapper).setStrict(strict); } /** * Returns the textual representation of a setting. * @param key the setting key. Can be any of standard XXX_KEY * constants, or a custom key. * * @deprecated This method was always defective, and certainly it always * will be. Don't use it. (Simply, it's hardly possible in general to * convert setting values to text in a way that ensures that * {@link #setSetting(String, String)} will work with them correctly.) */ public String getSetting(String key) { return properties.getProperty(key); } /** * This meant to return the String-to-String Map of the * settings. So it actually should return a Properties object, * but it doesn't by mistake. The returned Map is read-only, * but it will reflect the further configuration changes (aliasing effect). * * @deprecated This method was always defective, and certainly it always * will be. Don't use it. (Simply, it's hardly possible in general to * convert setting values to text in a way that ensures that * {@link #setSettings(Properties)} will work with them correctly.) */ public Map getSettings() { return Collections.unmodifiableMap(properties); } protected Environment getEnvironment() { return this instanceof Environment ? (Environment) this : Environment.getCurrentEnvironment(); } protected TemplateException unknownSettingException(String name) { return new UnknownSettingException(name, getEnvironment()); } protected TemplateException invalidSettingValueException(String name, String value) { return new TemplateException("Invalid value for setting " + name + ": " + value, getEnvironment()); } public static class UnknownSettingException extends TemplateException { private UnknownSettingException(String name, Environment env) { super("Unknown setting: " + name, env); } } /** * Set the settings stored in a Properties object. * * @throws TemplateException if the Properties object contains * invalid keys, or invalid setting values, or any other error occurs * while changing the settings. */ public void setSettings(Properties props) throws TemplateException { Iterator it = props.keySet().iterator(); while (it.hasNext()) { String key = (String) it.next(); setSetting(key, props.getProperty(key).trim()); } } /** * Reads a setting list (key and element pairs) from the input stream. * The stream has to follow the usual .properties format. * * @throws TemplateException if the stream contains * invalid keys, or invalid setting values, or any other error occurs * while changing the settings. * @throws IOException if an error occurred when reading from the input stream. */ public void setSettings(InputStream propsIn) throws TemplateException, IOException { Properties p = new Properties(); p.load(propsIn); setSettings(p); } /** * Internal entry point for setting unnamed custom attributes */ void setCustomAttribute(Object key, Object value) { synchronized(customAttributes) { customAttributes.put(key, value); } } /** * Internal entry point for getting unnamed custom attributes */ Object getCustomAttribute(Object key, CustomAttribute attr) { synchronized(customAttributes) { Object o = customAttributes.get(key); if(o == null && !customAttributes.containsKey(key)) { o = attr.create(); customAttributes.put(key, o); } return o; } } /** * Sets a named custom attribute for this configurable. * * @param name the name of the custom attribute * @param value the value of the custom attribute. You can set the value to * null, however note that there is a semantic difference between an * attribute set to null and an attribute that is not present, see * {@link #removeCustomAttribute(String)}. */ public void setCustomAttribute(String name, Object value) { synchronized(customAttributes) { customAttributes.put(name, value); } } /** * Returns an array with names of all custom attributes defined directly * on this configurable. (That is, it doesn't contain the names of custom attributes * defined indirectly on its parent configurables.) The returned array is never null, * but can be zero-length. * The order of elements in the returned array is not defined and can change * between invocations. */ public String[] getCustomAttributeNames() { synchronized(customAttributes) { Collection names = new LinkedList(customAttributes.keySet()); for (Iterator iter = names.iterator(); iter.hasNext();) { if(!(iter.next() instanceof String)) { iter.remove(); } } return (String[])names.toArray(new String[names.size()]); } } /** * Removes a named custom attribute for this configurable. Note that this * is different than setting the custom attribute value to null. If you * set the value to null, {@link #getCustomAttribute(String)} will return * null, while if you remove the attribute, it will return the value of * the attribute in the parent configurable (if there is a parent * configurable, that is). * * @param name the name of the custom attribute */ public void removeCustomAttribute(String name) { synchronized(customAttributes) { customAttributes.remove(name); } } /** * Retrieves a named custom attribute for this configurable. If the * attribute is not present in the configurable, and the configurable has * a parent, then the parent is looked up as well. * * @param name the name of the custom attribute * * @return the value of the custom attribute. Note that if the custom attribute * was created with <#ftl attributes={...}>, then this value is already * unwrapped (i.e. it's a String, or a List, or a * Map, ...etc., not a FreeMarker specific class). */ public Object getCustomAttribute(String name) { Object retval; synchronized(customAttributes) { retval = customAttributes.get(name); if(retval == null && customAttributes.containsKey(name)) { return null; } } if(retval == null && parent != null) { return parent.getCustomAttribute(name); } return retval; } protected void doAutoImportsAndIncludes(Environment env) throws TemplateException, IOException { if(parent != null) parent.doAutoImportsAndIncludes(env); } protected ArrayList parseAsList(String text) throws ParseException { return new SettingStringParser(text).parseAsList(); } protected ArrayList parseAsSegmentedList(String text) throws ParseException { return new SettingStringParser(text).parseAsSegmentedList(); } protected HashMap parseAsImportList(String text) throws ParseException { return new SettingStringParser(text).parseAsImportList(); } private static class KeyValuePair { private final Object key; private final Object value; KeyValuePair(Object key, Object value) { this.key = key; this.value = value; } Object getKey() { return key; } Object getValue() { return value; } } /** * Helper class for parsing setting values given with string. */ private static class SettingStringParser { private String text; private int p; private int ln; private SettingStringParser(String text) { this.text = text; this.p = 0; this.ln = text.length(); } ArrayList parseAsSegmentedList() throws ParseException { ArrayList segments = new ArrayList(); ArrayList currentSegment = null; char c; while (true) { c = skipWS(); if (c == ' ') break; String item = fetchStringValue(); c = skipWS(); if (c == ':') { currentSegment = new ArrayList(); segments.add(new KeyValuePair(item, currentSegment)); } else { if (currentSegment == null) { throw new ParseException( "The very first list item must be followed by \":\" so " + "it will be the key for the following sub-list.", 0, 0); } currentSegment.add(item); } if (c == ' ') break; if (c != ',' && c != ':') throw new ParseException( "Expected \",\" or \":\" or the end of text but " + "found \"" + c + "\"", 0, 0); p++; } return segments; } ArrayList parseAsList() throws ParseException { char c; ArrayList seq = new ArrayList(); while (true) { c = skipWS(); if (c == ' ') break; seq.add(fetchStringValue()); c = skipWS(); if (c == ' ') break; if (c != ',') throw new ParseException( "Expected \",\" or the end of text but " + "found \"" + c + "\"", 0, 0); p++; } return seq; } HashMap parseAsImportList() throws ParseException { char c; HashMap map = new HashMap(); while (true) { c = skipWS(); if (c == ' ') break; String lib = fetchStringValue(); c = skipWS(); if (c == ' ') throw new ParseException( "Unexpected end of text: expected \"as\"", 0, 0); String s = fetchKeyword(); if (!s.equalsIgnoreCase("as")) throw new ParseException( "Expected \"as\", but found " + StringUtil.jQuote(s), 0, 0); c = skipWS(); if (c == ' ') throw new ParseException( "Unexpected end of text: expected gate hash name", 0, 0); String ns = fetchStringValue(); map.put(ns, lib); c = skipWS(); if (c == ' ') break; if (c != ',') throw new ParseException( "Expected \",\" or the end of text but " + "found \"" + c + "\"", 0, 0); p++; } return map; } String fetchStringValue() throws ParseException { String w = fetchWord(); if (w.startsWith("'") || w.startsWith("\"")) { w = w.substring(1, w.length() - 1); } return StringUtil.FTLStringLiteralDec(w); } String fetchKeyword() throws ParseException { String w = fetchWord(); if (w.startsWith("'") || w.startsWith("\"")) { throw new ParseException( "Keyword expected, but a string value found: " + w, 0, 0); } return w; } char skipWS() { char c; while (p < ln) { c = text.charAt(p); if (!Character.isWhitespace(c)) return c; p++; } return ' '; } private String fetchWord() throws ParseException { if (p == ln) throw new ParseException( "Unexpeced end of text", 0, 0); char c = text.charAt(p); int b = p; if (c == '\'' || c == '"') { boolean escaped = false; char q = c; p++; while (p < ln) { c = text.charAt(p); if (!escaped) { if (c == '\\') { escaped = true; } else if (c == q) { break; } } else { escaped = false; } p++; } if (p == ln) { throw new ParseException("Missing " + q, 0, 0); } p++; return text.substring(b, p); } else { do { c = text.charAt(p); if (!(Character.isLetterOrDigit(c) || c == '/' || c == '\\' || c == '_' || c == '.' || c == '-' || c == '!' || c == '*' || c == '?')) break; p++; } while (p < ln); if (b == p) { throw new ParseException("Unexpected character: " + c, 0, 0); } else { return text.substring(b, p); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy