KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > core > Configurable


1 /*
2  * Copyright (c) 2003 The Visigoth Software Society. All rights
3  * reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in
14  * the documentation and/or other materials provided with the
15  * distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowledgement:
19  * "This product includes software developed by the
20  * Visigoth Software Society (http://www.visigoths.org/)."
21  * Alternately, this acknowledgement may appear in the software itself,
22  * if and wherever such third-party acknowledgements normally appear.
23  *
24  * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25  * project contributors may be used to endorse or promote products derived
26  * from this software without prior written permission. For written
27  * permission, please contact visigoths@visigoths.org.
28  *
29  * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30  * nor may "FreeMarker" or "Visigoth" appear in their names
31  * without prior written permission of the Visigoth Software Society.
32  *
33  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36  * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  * ====================================================================
46  *
47  * This software consists of voluntary contributions made by many
48  * individuals on behalf of the Visigoth Software Society. For more
49  * information on the Visigoth Software Society, please see
50  * http://www.visigoths.org/
51  */

52
53 package freemarker.core;
54
55 import java.io.IOException JavaDoc;
56 import java.io.InputStream JavaDoc;
57 import java.util.*;
58
59 import freemarker.template.*;
60 import freemarker.template.utility.ClassUtil;
61 import freemarker.template.utility.StringUtil;
62
63 /**
64  * This is a common superclass of {@link freemarker.template.Configuration},
65  * {@link freemarker.template.Template}, and {@link Environment} classes.
66  * It provides settings that are common to each of them. FreeMarker
67  * uses a three-level setting hierarchy - the return value of every setting
68  * getter method on <code>Configurable</code> objects inherits its value from its parent
69  * <code>Configurable</code> object, unless explicitly overridden by a call to a
70  * corresponding setter method on the object itself. The parent of an
71  * <code>Environment</code> object is a <code>Template</code> object, the
72  * parent of a <code>Template</code> object is a <code>Configuration</code>
73  * object.
74  *
75  * @version $Id: Configurable.java,v 1.21 2004/12/03 13:19:03 ddekany Exp $
76  * @author Attila Szegedi
77  */

78 public class Configurable
79 {
80     public static final String JavaDoc LOCALE_KEY = "locale";
81     public static final String JavaDoc NUMBER_FORMAT_KEY = "number_format";
82     public static final String JavaDoc TIME_FORMAT_KEY = "time_format";
83     public static final String JavaDoc DATE_FORMAT_KEY = "date_format";
84     public static final String JavaDoc DATETIME_FORMAT_KEY = "datetime_format";
85     public static final String JavaDoc TIME_ZONE_KEY = "time_zone";
86     public static final String JavaDoc CLASSIC_COMPATIBLE_KEY = "classic_compatible";
87     public static final String JavaDoc TEMPLATE_EXCEPTION_HANDLER_KEY = "template_exception_handler";
88     public static final String JavaDoc ARITHMETIC_ENGINE_KEY = "arithmetic_engine";
89     public static final String JavaDoc OBJECT_WRAPPER_KEY = "object_wrapper";
90     public static final String JavaDoc BOOLEAN_FORMAT_KEY = "boolean_format";
91     public static final String JavaDoc OUTPUT_ENCODING_KEY = "output_encoding";
92     public static final String JavaDoc URL_ESCAPING_CHARSET_KEY = "url_escaping_charset";
93
94     private static final char COMMA = ',';
95     
96     private Configurable parent;
97     private Properties properties;
98     private HashMap customAttributes;
99     
100     private Locale locale;
101     private String JavaDoc numberFormat;
102     private String JavaDoc timeFormat;
103     private String JavaDoc dateFormat;
104     private String JavaDoc dateTimeFormat;
105     private TimeZone timeZone;
106     private String JavaDoc trueFormat;
107     private String JavaDoc falseFormat;
108     private Boolean JavaDoc classicCompatible;
109     private TemplateExceptionHandler templateExceptionHandler;
110     private ArithmeticEngine arithmeticEngine;
111     private ObjectWrapper objectWrapper;
112     private String JavaDoc outputEncoding;
113     private String JavaDoc urlEscapingCharset;
114     
115     public Configurable() {
116         parent = null;
117         locale = Locale.getDefault();
118         timeZone = TimeZone.getDefault();
119         numberFormat = "number";
120         timeFormat = "";
121         dateFormat = "";
122         dateTimeFormat = "";
123         trueFormat = "true";
124         falseFormat = "false";
125         classicCompatible = Boolean.FALSE;
126         templateExceptionHandler = TemplateExceptionHandler.DEBUG_HANDLER;
127         arithmeticEngine = ArithmeticEngine.BIGDECIMAL_ENGINE;
128         objectWrapper = ObjectWrapper.DEFAULT_WRAPPER;
129         // outputEncoding and urlEscapingCharset defaults to null,
130
// which means "not specified"
131

132         properties = new Properties();
133         properties.setProperty(LOCALE_KEY, locale.toString());
134         properties.setProperty(TIME_FORMAT_KEY, timeFormat);
135         properties.setProperty(DATE_FORMAT_KEY, dateFormat);
136         properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat);
137         properties.setProperty(TIME_ZONE_KEY, timeZone.getID());
138         properties.setProperty(NUMBER_FORMAT_KEY, numberFormat);
139         properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
140         properties.setProperty(TEMPLATE_EXCEPTION_HANDLER_KEY, templateExceptionHandler.getClass().getName());
141         properties.setProperty(ARITHMETIC_ENGINE_KEY, arithmeticEngine.getClass().getName());
142         properties.setProperty(BOOLEAN_FORMAT_KEY, "true,false");
143         // as outputEncoding and urlEscapingCharset defaults to null,
144
// they are not set
145

146         customAttributes = new HashMap();
147     }
148     
149     /**
150      * Creates a new instance. Normally you do not need to use this constructor,
151      * as you don't use <code>Configurable</code> directly, but its subclasses.
152      */

153     public Configurable(Configurable parent) {
154         this.parent = parent;
155         locale = null;
156         numberFormat = null;
157         trueFormat = null;
158         falseFormat = null;
159         classicCompatible = null;
160         templateExceptionHandler = null;
161         properties = new Properties(parent.properties);
162         customAttributes = new HashMap();
163     }
164
165     protected Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
166         Configurable copy = (Configurable)super.clone();
167         copy.properties = new Properties(properties);
168         copy.customAttributes = (HashMap)customAttributes.clone();
169         return copy;
170     }
171     
172     /**
173      * Returns the parent <tt>Configurable</tt> object of this object.
174      * The parent stores the default values for this configurable. For example,
175      * the parent of the {@link freemarker.template.Template} object is the
176      * {@link freemarker.template.Configuration} object, so setting values not
177      * specfied on template level are specified by the confuration object.
178      *
179      * @return the parent <tt>Configurable</tt> object, or null, if this is
180      * the root <tt>Configurable</tt> object.
181      */

182     public final Configurable getParent() {
183         return parent;
184     }
185     
186     /**
187      * Reparenting support. This is used by Environment when it includes a
188      * template - the included template becomes the parent configurable during
189      * its evaluation.
190      */

191     final void setParent(Configurable parent) {
192         this.parent = parent;
193     }
194     
195     /**
196      * Toggles the "Classic Compatibile" mode. For a comprehensive description
197      * of this mode, see {@link #isClassicCompatible()}.
198      */

199     public void setClassicCompatible(boolean classicCompatibility) {
200         this.classicCompatible = classicCompatibility ? Boolean.TRUE : Boolean.FALSE;
201         properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
202     }
203
204     /**
205      * Returns whether the engine runs in the "Classic Compatibile" mode.
206      * When this mode is active, the engine behavior is altered in following
207      * way: (these resemble the behavior of the 1.7.x line of FreeMarker engine,
208      * now named "FreeMarker Classic", hence the name).
209      * <ul>
210      * <li>handle undefined expressions gracefully. Namely when an expression
211      * "expr" evaluates to null:
212      * <ul>
213      * <li>as argument of the <tt>&lt;assign varname=expr></tt> directive,
214      * <tt>${expr}</tt> directive, <tt>otherexpr == expr</tt> or
215      * <tt>otherexpr != expr</tt> conditional expressions, or
216      * <tt>hash[expr]</tt> expression, then it is treated as empty string.
217      * </li>
218      * <li>as argument of <tt>&lt;list expr as item></tt> or
219      * <tt>&lt;foreach item in expr></tt>, the loop body is not executed
220      * (as if it were a 0-length list)
221      * </li>
222      * <li>as argument of <tt>&lt;if></tt> directive, or otherwise where a
223      * boolean expression is expected, it is treated as false
224      * </li>
225      * </ul>
226      * </li>
227      * <li>Non-boolean models are accepted in <tt>&lt;if></tt> directive,
228      * or as operands of logical operators. "Empty" models (zero-length string,
229      * empty sequence or hash) are evaluated as false, all others are evaluated as
230      * true.</li>
231      * <li>When boolean value is treated as a string (i.e. output in
232      * <tt>${...}</tt> directive, or concatenated with other string), true
233      * values are converted to string "true", false values are converted to
234      * empty string.
235      * </li>
236      * <li>Scalar models supplied to <tt>&lt;list></tt> and
237      * <tt>&lt;foreach></tt> are treated as a one-element list consisting
238      * of the passed model.
239      * </li>
240      * <li>Paths parameter of <tt>&lt;include></tt> will be interpreted as
241      * absolute path.
242      * </li>
243      * </ul>
244      * In all other aspects, the engine is a 2.1 engine even in compatibility
245      * mode - you don't lose any of the new functionality by enabling it.
246      */

247     public boolean isClassicCompatible() {
248         return classicCompatible != null ? classicCompatible.booleanValue() : parent.isClassicCompatible();
249     }
250
251     /**
252      * Sets the locale to assume when searching for template files with no
253      * explicit requested locale.
254      */

255     public void setLocale(Locale locale) {
256         if (locale == null) throw new IllegalArgumentException JavaDoc("Setting \"locale\" can't be null");
257         this.locale = locale;
258         properties.setProperty(LOCALE_KEY, locale.toString());
259     }
260
261     /**
262      * Returns the time zone to use when formatting time values. Defaults to
263      * system time zone.
264      */

265     public TimeZone getTimeZone() {
266         return timeZone != null ? timeZone : parent.getTimeZone();
267     }
268
269     /**
270      * Sets the time zone to use when formatting time values.
271      */

272     public void setTimeZone(TimeZone timeZone) {
273         if (timeZone == null) throw new IllegalArgumentException JavaDoc("Setting \"time_zone\" can't be null");
274         this.timeZone = timeZone;
275         properties.setProperty(TIME_ZONE_KEY, timeZone.getID());
276     }
277
278     /**
279      * Returns the assumed locale when searching for template files with no
280      * explicit requested locale. Defaults to system locale.
281      */

282     public Locale getLocale() {
283         return locale != null ? locale : parent.getLocale();
284     }
285
286     /**
287      * Sets the number format used to convert numbers to strings.
288      */

289     public void setNumberFormat(String JavaDoc numberFormat) {
290         if (numberFormat == null) throw new IllegalArgumentException JavaDoc("Setting \"number_format\" can't be null");
291         this.numberFormat = numberFormat;
292         properties.setProperty(NUMBER_FORMAT_KEY, numberFormat);
293     }
294
295     /**
296      * Returns the default number format used to convert numbers to strings.
297      * Defaults to <tt>"number"</tt>
298      */

299     public String JavaDoc getNumberFormat() {
300         return numberFormat != null ? numberFormat : parent.getNumberFormat();
301     }
302
303     public void setBooleanFormat(String JavaDoc booleanFormat) {
304         if (booleanFormat == null) {
305             throw new IllegalArgumentException JavaDoc("Setting \"boolean_format\" can't be null");
306         }
307         int comma = booleanFormat.indexOf(COMMA);
308         if(comma == -1) {
309             throw new IllegalArgumentException JavaDoc("Setting \"boolean_format\" must consist of two comma-separated values for true and false respectively");
310         }
311         trueFormat = booleanFormat.substring(0, comma);
312         falseFormat = booleanFormat.substring(comma + 1);
313         properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
314     }
315     
316     public String JavaDoc getBooleanFormat() {
317         if(trueFormat == null) {
318             return parent.getBooleanFormat();
319         }
320         return trueFormat + COMMA + falseFormat;
321     }
322     
323     String JavaDoc getBooleanFormat(boolean value) {
324         return value ? getTrueFormat() : getFalseFormat();
325     }
326     
327     private String JavaDoc getTrueFormat() {
328         return trueFormat != null ? trueFormat : parent.getTrueFormat();
329     }
330     
331     private String JavaDoc getFalseFormat() {
332         return falseFormat != null ? falseFormat : parent.getFalseFormat();
333     }
334
335     /**
336      * Sets the date format used to convert date models representing time-only
337      * values to strings.
338      */

339     public void setTimeFormat(String JavaDoc timeFormat) {
340         if (timeFormat == null) throw new IllegalArgumentException JavaDoc("Setting \"time_format\" can't be null");
341         this.timeFormat = timeFormat;
342         properties.setProperty(TIME_FORMAT_KEY, timeFormat);
343     }
344
345     /**
346      * Returns the date format used to convert date models representing
347      * time-only dates to strings.
348      * Defaults to <tt>"time"</tt>
349      */

350     public String JavaDoc getTimeFormat() {
351         return timeFormat != null ? timeFormat : parent.getTimeFormat();
352     }
353
354     /**
355      * Sets the date format used to convert date models representing date-only
356      * dates to strings.
357      */

358     public void setDateFormat(String JavaDoc dateFormat) {
359         if (dateFormat == null) throw new IllegalArgumentException JavaDoc("Setting \"date_format\" can't be null");
360         this.dateFormat = dateFormat;
361         properties.setProperty(DATE_FORMAT_KEY, dateFormat);
362     }
363
364     /**
365      * Returns the date format used to convert date models representing
366      * date-only dates to strings.
367      * Defaults to <tt>"date"</tt>
368      */

369     public String JavaDoc getDateFormat() {
370         return dateFormat != null ? dateFormat : parent.getDateFormat();
371     }
372
373     /**
374      * Sets the date format used to convert date models representing datetime
375      * dates to strings.
376      */

377     public void setDateTimeFormat(String JavaDoc dateTimeFormat) {
378         if (dateTimeFormat == null) throw new IllegalArgumentException JavaDoc("Setting \"datetime_format\" can't be null");
379         this.dateTimeFormat = dateTimeFormat;
380         properties.setProperty(DATETIME_FORMAT_KEY, dateTimeFormat);
381     }
382
383     /**
384      * Returns the date format used to convert date models representing datetime
385      * dates to strings.
386      * Defaults to <tt>"datetime"</tt>
387      */

388     public String JavaDoc getDateTimeFormat() {
389         return dateTimeFormat != null ? dateTimeFormat : parent.getDateTimeFormat();
390     }
391
392     /**
393      * Sets the exception handler used to handle template exceptions.
394      *
395      * @param templateExceptionHandler the template exception handler to use for
396      * handling {@link TemplateException}s. By default,
397      * {@link TemplateExceptionHandler#HTML_DEBUG_HANDLER} is used.
398      */

399     public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) {
400         if (templateExceptionHandler == null) throw new IllegalArgumentException JavaDoc("Setting \"template_exception_handler\" can't be null");
401         this.templateExceptionHandler = templateExceptionHandler;
402         properties.setProperty(TEMPLATE_EXCEPTION_HANDLER_KEY, templateExceptionHandler.getClass().getName());
403     }
404
405     /**
406      * Retrieves the exception handler used to handle template exceptions.
407      */

408     public TemplateExceptionHandler getTemplateExceptionHandler() {
409         return templateExceptionHandler != null
410                 ? templateExceptionHandler : parent.getTemplateExceptionHandler();
411     }
412
413     /**
414      * Sets the arithmetic engine used to perform arithmetic operations.
415      *
416      * @param arithmeticEngine the arithmetic engine used to perform arithmetic
417      * operations.By default, {@link ArithmeticEngine#BIGDECIMAL_ENGINE} is
418      * used.
419      */

420     public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
421         if (arithmeticEngine == null) throw new IllegalArgumentException JavaDoc("Setting \"arithmetic_engine\" can't be null");
422         this.arithmeticEngine = arithmeticEngine;
423         properties.setProperty(ARITHMETIC_ENGINE_KEY, arithmeticEngine.getClass().getName());
424     }
425
426     /**
427      * Retrieves the arithmetic engine used to perform arithmetic operations.
428      */

429     public ArithmeticEngine getArithmeticEngine() {
430         return arithmeticEngine != null
431                 ? arithmeticEngine : parent.getArithmeticEngine();
432     }
433
434     /**
435      * Sets the object wrapper used to wrap objects to template models.
436      *
437      * @param objectWrapper the object wrapper used to wrap objects to template
438      * models.By default, {@link ObjectWrapper#DEFAULT_WRAPPER} is used.
439      */

440     public void setObjectWrapper(ObjectWrapper objectWrapper) {
441         if (objectWrapper == null) throw new IllegalArgumentException JavaDoc("Setting \"object_wrapper\" can't be null");
442         this.objectWrapper = objectWrapper;
443         properties.setProperty(OBJECT_WRAPPER_KEY, objectWrapper.getClass().getName());
444     }
445
446     /**
447      * Retrieves the object wrapper used to wrap objects to template models.
448      */

449     public ObjectWrapper getObjectWrapper() {
450         return objectWrapper != null
451                 ? objectWrapper : parent.getObjectWrapper();
452     }
453     
454     public void setOutputEncoding(String JavaDoc outputEncoding) {
455         this.outputEncoding = outputEncoding;
456         properties.setProperty(OUTPUT_ENCODING_KEY, outputEncoding);
457     }
458     
459     public String JavaDoc getOutputEncoding() {
460         return outputEncoding != null
461                 ? outputEncoding
462                 : (parent != null ? parent.getOutputEncoding() : null);
463     }
464     
465     public void setURLEscapingCharset(String JavaDoc urlEscapingCharset) {
466         this.urlEscapingCharset = urlEscapingCharset;
467         properties.setProperty(URL_ESCAPING_CHARSET_KEY, urlEscapingCharset);
468     }
469     
470     public String JavaDoc getURLEscapingCharset() {
471         return urlEscapingCharset != null
472                 ? urlEscapingCharset
473                 : (parent != null ? parent.getURLEscapingCharset() : null);
474     }
475     
476     /**
477      * Sets a setting by a name and string value.
478      *
479      * <p>List of supported names and their valid values:
480      * <ul>
481      * <li><code>"locale"</code>: local codes with the usual format, such as <code>"en_US"</code>.
482      * <li><code>"classic_compatible"</code>:
483      * <code>"true"</code>, <code>"false"</code>, <code>"yes"</code>, <code>"no"</code>,
484      * <code>"t"</code>, <code>"f"</code>, <code>"y"</code>, <code>"n"</code>.
485      * Case insensitive.
486      * <li><code>"template_exception_handler"</code>: If the value contains dot, then it is
487      * interpreted as class name, and the object will be created with
488      * its parameterless constructor. If the value does not contain dot,
489      * then it must be one of these special values:
490      * <code>"rethrow"</code>, <code>"debug"</code>,
491      * <code>"html_debug"</code>, <code>"ignore"</code> (case insensitive).
492      * <li><code>"arithmetic_engine"</code>: If the value contains dot, then it is
493      * interpreted as class name, and the object will be created with
494      * its parameterless constructor. If the value does not contain dot,
495      * then it must be one of these special values:
496      * <code>"bigdecimal"</code>, <code>"conservative"</code> (case insensitive).
497      * <li><code>"object_wrapper"</code>: If the value contains dot, then it is
498      * interpreted as class name, and the object will be created with
499      * its parameterless constructor. If the value does not contain dot,
500      * then it must be one of these special values:
501      * <code>"simple"</code>, <code>"beans"</code>, <code>"jython"</code> (case insensitive).
502      * <li><code>"number_format"</code>: pattern as <code>java.text.DecimalFormat</code> defines.
503      * <li><code>"boolean_format"</code>: the textual value for boolean true and false,
504      * separated with comma. For example <code>"yes,no"</code>.
505      * <li><code>"date_format", "time_format", "datetime_format"</code>: patterns as
506      * <code>java.text.SimpleDateFormat</code> defines.
507      * <li><code>"time_zone"</code>: time zone, with the format as
508      * <code>java.util.TimeZone.getTimeZone</code> defines. For example <code>"GMT-8:00"</code> or
509      * <code>"America/Los_Angeles"</code>
510      * <li><code>"output_encoding"</code>: Informs FreeMarker about the charset
511      * used for the output. As FreeMarker outputs character stream (not
512      * byte stream), it is not aware of the output charset unless the
513      * software that encloses it tells it explicitly with this setting.
514      * Some templates may use FreeMarker features that require this.</code>
515      * <li><code>"url_escaping_charset"</code>: If this setting is set, then it
516      * overrides the value of the <code>"output_encoding"</code> setting when
517      * FreeMarker does URL encoding.
518      * </ul>
519      *
520      * @param key the name of the setting.
521      * @param value the string that describes the new value of the setting.
522      *
523      * @throws UnknownSettingException if the key is wrong.
524      * @throws TemplateException if the new value of the setting can't be set
525      * for any other reasons.
526      */

527     public void setSetting(String JavaDoc key, String JavaDoc value) throws TemplateException {
528         try {
529             if (LOCALE_KEY.equals(key)) {
530                 setLocale(StringUtil.deduceLocale(value));
531             } else if (NUMBER_FORMAT_KEY.equals(key)) {
532                 setNumberFormat(value);
533             } else if (TIME_FORMAT_KEY.equals(key)) {
534                 setTimeFormat(value);
535             } else if (DATE_FORMAT_KEY.equals(key)) {
536                 setDateFormat(value);
537             } else if (DATETIME_FORMAT_KEY.equals(key)) {
538                 setDateTimeFormat(value);
539             } else if (TIME_ZONE_KEY.equals(key)) {
540                 setTimeZone(TimeZone.getTimeZone(value));
541             } else if (CLASSIC_COMPATIBLE_KEY.equals(key)) {
542                 setClassicCompatible(StringUtil.getYesNo(value));
543             } else if (TEMPLATE_EXCEPTION_HANDLER_KEY.equals(key)) {
544                 if (value.indexOf('.') == -1) {
545                     if ("debug".equalsIgnoreCase(value)) {
546                         setTemplateExceptionHandler(
547                                 TemplateExceptionHandler.DEBUG_HANDLER);
548                     } else if ("html_debug".equalsIgnoreCase(value)) {
549                         setTemplateExceptionHandler(
550                                 TemplateExceptionHandler.HTML_DEBUG_HANDLER);
551                     } else if ("ignore".equalsIgnoreCase(value)) {
552                         setTemplateExceptionHandler(
553                                 TemplateExceptionHandler.IGNORE_HANDLER);
554                     } else if ("rethrow".equalsIgnoreCase(value)) {
555                         setTemplateExceptionHandler(
556                                 TemplateExceptionHandler.RETHROW_HANDLER);
557                     } else {
558                         throw invalidSettingValueException(key, value);
559                     }
560                 } else {
561                     setTemplateExceptionHandler(
562                             (TemplateExceptionHandler) ClassUtil.forName(value)
563                             .newInstance());
564                 }
565             } else if (ARITHMETIC_ENGINE_KEY.equals(key)) {
566                 if (value.indexOf('.') == -1) {
567                     if ("bigdecimal".equalsIgnoreCase(value)) {
568                         setArithmeticEngine(ArithmeticEngine.BIGDECIMAL_ENGINE);
569                     } else if ("conservative".equalsIgnoreCase(value)) {
570                         setArithmeticEngine(ArithmeticEngine.CONSERVATIVE_ENGINE);
571                     } else {
572                         throw invalidSettingValueException(key, value);
573                     }
574                 } else {
575                     setArithmeticEngine(
576                             (ArithmeticEngine) ClassUtil.forName(value)
577                             .newInstance());
578                 }
579             } else if (OBJECT_WRAPPER_KEY.equals(key)) {
580                 if (value.indexOf('.') == -1) {
581                     if ("default".equalsIgnoreCase(value)) {
582                         setObjectWrapper(ObjectWrapper.DEFAULT_WRAPPER);
583                     } else if ("simple".equalsIgnoreCase(value)) {
584                         setObjectWrapper(ObjectWrapper.SIMPLE_WRAPPER);
585                     } else if ("beans".equalsIgnoreCase(value)) {
586                         setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
587                     } else if ("jython".equalsIgnoreCase(value)) {
588                         Class JavaDoc clazz = Class.forName(
589                                 "freemarker.ext.jython.JythonWrapper");
590                         setObjectWrapper(
591                                 (ObjectWrapper) clazz.getField("INSTANCE").get(null));
592                     } else {
593                         throw invalidSettingValueException(key, value);
594                     }
595                     
596                 } else {
597                     setObjectWrapper((ObjectWrapper) ClassUtil.forName(value)
598                             .newInstance());
599                 }
600             } else if (BOOLEAN_FORMAT_KEY.equals(key)) {
601                 setBooleanFormat(value);
602             } else if (OUTPUT_ENCODING_KEY.equals(key)) {
603                 setOutputEncoding(value);
604             } else if (URL_ESCAPING_CHARSET_KEY.equals(key)) {
605                 setURLEscapingCharset(value);
606             } else {
607                 throw unknownSettingException(key);
608             }
609         } catch(TemplateException e) {
610             throw e;
611         } catch(Exception JavaDoc e) {
612             throw new TemplateException(
613                     "Failed to set setting " + key + " to value " + value,
614                     e, getEnvironment());
615         }
616     }
617     
618     /**
619      * Returns the textual representation of a setting.
620      * @param key the setting key. Can be any of standard <tt>XXX_KEY</tt>
621      * constants, or a custom key.
622      *
623      * @deprecated This method was always defective, and certainly it always
624      * will be. Don't use it. (Simply, it's hardly possible in general to
625      * convert setting values to text in a way that ensures that
626      * {@link #setSetting(String, String)} will work with them correctly.)
627      */

628     public String JavaDoc getSetting(String JavaDoc key) {
629         return properties.getProperty(key);
630     }
631     
632     /**
633      * This meant to return the String-to-String <code>Map</code> of the
634      * settings. So it actually should return a <code>Properties</code> object,
635      * but it doesn't by mistake. The returned <code>Map</code> is read-only,
636      * but it will reflect the further configuration changes (aliasing effect).
637      *
638      * @deprecated This method was always defective, and certainly it always
639      * will be. Don't use it. (Simply, it's hardly possible in general to
640      * convert setting values to text in a way that ensures that
641      * {@link #setSettings(Properties)} will work with them correctly.)
642      */

643     public Map getSettings() {
644         return Collections.unmodifiableMap(properties);
645     }
646     
647     protected Environment getEnvironment() {
648         return this instanceof Environment
649             ? (Environment) this
650             : Environment.getCurrentEnvironment();
651     }
652     
653     protected TemplateException unknownSettingException(String JavaDoc name) {
654         return new UnknownSettingException(name, getEnvironment());
655     }
656
657     protected TemplateException invalidSettingValueException(String JavaDoc name, String JavaDoc value) {
658         return new TemplateException("Invalid value for setting " + name + ": " + value, getEnvironment());
659     }
660     
661     public class UnknownSettingException extends TemplateException {
662         private UnknownSettingException(String JavaDoc name, Environment env) {
663             super("Unknown setting: " + name, env);
664         }
665     }
666
667     /**
668      * Set the settings stored in a <code>Properties</code> object.
669      *
670      * @throws TemplateException if the <code>Properties</code> object contains
671      * invalid keys, or invalid setting values, or any other error occurs
672      * while changing the settings.
673      */

674     public void setSettings(Properties props) throws TemplateException {
675         Iterator it = props.keySet().iterator();
676         while (it.hasNext()) {
677             String JavaDoc key = (String JavaDoc) it.next();
678             setSetting(key, props.getProperty(key).trim());
679         }
680     }
681     
682     /**
683      * Reads a setting list (key and element pairs) from the input stream.
684      * The stream has to follow the usual <code>.properties</code> format.
685      *
686      * @throws TemplateException if the stream contains
687      * invalid keys, or invalid setting values, or any other error occurs
688      * while changing the settings.
689      * @throws IOException if an error occurred when reading from the input stream.
690      */

691     public void setSettings(InputStream JavaDoc propsIn) throws TemplateException, IOException JavaDoc {
692         Properties p = new Properties();
693         p.load(propsIn);
694         setSettings(p);
695     }
696
697     /**
698      * Internal entry point for setting unnamed custom attributes
699      */

700     void setCustomAttribute(Object JavaDoc key, Object JavaDoc value) {
701         synchronized(customAttributes) {
702             customAttributes.put(key, value);
703         }
704     }
705
706     /**
707      * Internal entry point for getting unnamed custom attributes
708      */

709     Object JavaDoc getCustomAttribute(Object JavaDoc key, CustomAttribute attr) {
710         synchronized(customAttributes) {
711             Object JavaDoc o = customAttributes.get(key);
712             if(o == null && !customAttributes.containsKey(key)) {
713                 o = attr.create();
714                 customAttributes.put(key, o);
715             }
716             return o;
717         }
718     }
719     
720     /**
721      * Sets a named custom attribute for this configurable.
722      *
723      * @param name the name of the custom attribute
724      * @param value the value of the custom attribute. You can set the value to
725      * null, however note that there is a semantic difference between an
726      * attribute set to null and an attribute that is not present, see
727      * {@link #removeCustomAttribute(String)}.
728      */

729     public void setCustomAttribute(String JavaDoc name, Object JavaDoc value) {
730         synchronized(customAttributes) {
731             customAttributes.put(name, value);
732         }
733     }
734     
735     /**
736      * Returns an array with names of all custom attributes defined directly
737      * on this configurable. (That is, it doesn't contain the names of custom attributes
738      * defined indirectly on its parent configurables.) The returned array is never null,
739      * but can be zero-length.
740      * The order of elements in the returned array is not defined and can change
741      * between invocations.
742      */

743     public String JavaDoc[] getCustomAttributeNames() {
744         synchronized(customAttributes) {
745             Collection names = new LinkedList(customAttributes.keySet());
746             for (Iterator iter = names.iterator(); iter.hasNext();) {
747                 if(!(iter.next() instanceof String JavaDoc)) {
748                     iter.remove();
749                 }
750             }
751             return (String JavaDoc[])names.toArray(new String JavaDoc[names.size()]);
752         }
753     }
754     
755     /**
756      * Removes a named custom attribute for this configurable. Note that this
757      * is different than setting the custom attribute value to null. If you
758      * set the value to null, {@link #getCustomAttribute(String)} will return
759      * null, while if you remove the attribute, it will return the value of
760      * the attribute in the parent configurable (if there is a parent
761      * configurable, that is).
762      *
763      * @param name the name of the custom attribute
764      *
765      * @return the value of the custom attribute, or null
766      */

767     public void removeCustomAttribute(String JavaDoc name) {
768         synchronized(customAttributes) {
769             customAttributes.remove(name);
770         }
771     }
772
773     /**
774      * Retrieves a named custom attribute for this configurable. If the
775      * attribute is not present in the configurable, and the configurable has
776      * a parent, then the parent is looked up as well.
777      *
778      * @param name the name of the custom attribute
779      *
780      * @return the value of the custom attribute. Note that if the custom attribute
781      * was created with <tt>&lt;#ftl&nbsp;attributes={...}></tt>, then this value is already
782      * unwrapped (i.e. it's a <code>String</code>, or a <code>List</code>, or a
783      * <code>Map</code>, ...etc., not a FreeMarker specific class).
784      */

785     public Object JavaDoc getCustomAttribute(String JavaDoc name) {
786         Object JavaDoc retval;
787         synchronized(customAttributes) {
788             retval = customAttributes.get(name);
789             if(retval == null && customAttributes.containsKey(name)) {
790                 return null;
791             }
792         }
793         if(retval == null && parent != null) {
794             return parent.getCustomAttribute(name);
795         }
796         return retval;
797     }
798 }
799
Popular Tags