001: /*
002: * Copyright (c) 2003-2006 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.template;
054:
055: import java.io.*;
056: import java.util.*;
057:
058: import freemarker.cache.*;
059: import freemarker.core.*;
060: import freemarker.template.utility.*;
061:
062: /**
063: * Main entry point into the FreeMarker API, this class encapsulates the
064: * various configuration parameters with which FreeMarker is run, as well
065: * as serves as a central template loading and caching point. Note that
066: * this class uses a default strategy for loading
067: * and caching templates. You can plug in a replacement
068: * template loading mechanism by using the {@link #setTemplateLoader(TemplateLoader)}
069: * method.
070: *
071: * This object is <em>not synchronized</em>. Thus, the settings must not be changed
072: * after you have started to access the object from multiple threads. If you use multiple
073: * threads, set everything directly after you have instantiated the <code>Configuration</code>
074: * object, and don't change the settings anymore.
075: *
076: * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
077: * @author Attila Szegedi
078: * @version $Id: Configuration.java,v 1.122.2.5 2006/04/26 21:25:19 ddekany Exp $
079: */
080:
081: public class Configuration extends Configurable implements Cloneable {
082: public static final String DEFAULT_ENCODING_KEY = "default_encoding";
083: public static final String LOCALIZED_LOOKUP_KEY = "localized_lookup";
084: public static final String STRICT_SYNTAX_KEY = "strict_syntax";
085: public static final String WHITESPACE_STRIPPING_KEY = "whitespace_stripping";
086: public static final String CACHE_STORAGE_KEY = "cache_storage";
087: public static final String TEMPLATE_UPDATE_DELAY_KEY = "template_update_delay";
088: public static final String AUTO_IMPORT_KEY = "auto_import";
089: public static final String AUTO_INCLUDE_KEY = "auto_include";
090: public static final String TAG_SYNTAX_KEY = "tag_syntax";
091: public static final int AUTO_DETECT_TAG_SYNTAX = 0;
092: public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
093: public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
094:
095: private static Configuration defaultConfig = new Configuration();
096: private static String cachedVersion;
097: private boolean strictSyntax = true, localizedLookup = true,
098: whitespaceStripping = true;
099: private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
100:
101: private TemplateCache cache;
102: private HashMap variables = new HashMap();
103: private HashMap encodingMap = new HashMap();
104: private Map autoImportMap = new HashMap();
105: private ArrayList autoImports = new ArrayList(),
106: autoIncludes = new ArrayList();
107: private String defaultEncoding = SecurityUtilities
108: .getSystemProperty("file.encoding");
109:
110: public Configuration() {
111: cache = new TemplateCache();
112: cache.setConfiguration(this );
113: cache.setDelay(5000);
114: loadBuiltInSharedVariables();
115: }
116:
117: public Object clone() {
118: try {
119: Configuration copy = (Configuration) super .clone();
120: copy.variables = new HashMap(variables);
121: copy.encodingMap = new HashMap(encodingMap);
122: copy.createTemplateCache(cache.getTemplateLoader(), cache
123: .getCacheStorage());
124: return copy;
125: } catch (CloneNotSupportedException e) {
126: throw new RuntimeException(
127: "Clone is not supported, but it should be: "
128: + e.getMessage());
129: }
130: }
131:
132: private void loadBuiltInSharedVariables() {
133: variables.put("capture_output", new CaptureOutput());
134: variables.put("compress", StandardCompress.INSTANCE);
135: variables.put("html_escape", new HtmlEscape());
136: variables.put("normalize_newlines", new NormalizeNewlines());
137: variables.put("xml_escape", new XmlEscape());
138: }
139:
140: /**
141: * Loads a preset language-to-encoding map. It assumes the usual character
142: * encodings for most languages.
143: * The previous content of the encoding map will be lost.
144: * This default map currently contains the following mappings:
145: * <table>
146: * <tr><td>ar</td><td>ISO-8859-6</td></tr>
147: * <tr><td>be</td><td>ISO-8859-5</td></tr>
148: * <tr><td>bg</td><td>ISO-8859-5</td></tr>
149: * <tr><td>ca</td><td>ISO-8859-1</td></tr>
150: * <tr><td>cs</td><td>ISO-8859-2</td></tr>
151: * <tr><td>da</td><td>ISO-8859-1</td></tr>
152: * <tr><td>de</td><td>ISO-8859-1</td></tr>
153: * <tr><td>el</td><td>ISO-8859-7</td></tr>
154: * <tr><td>en</td><td>ISO-8859-1</td></tr>
155: * <tr><td>es</td><td>ISO-8859-1</td></tr>
156: * <tr><td>et</td><td>ISO-8859-1</td></tr>
157: * <tr><td>fi</td><td>ISO-8859-1</td></tr>
158: * <tr><td>fr</td><td>ISO-8859-1</td></tr>
159: * <tr><td>hr</td><td>ISO-8859-2</td></tr>
160: * <tr><td>hu</td><td>ISO-8859-2</td></tr>
161: * <tr><td>is</td><td>ISO-8859-1</td></tr>
162: * <tr><td>it</td><td>ISO-8859-1</td></tr>
163: * <tr><td>iw</td><td>ISO-8859-8</td></tr>
164: * <tr><td>ja</td><td>Shift_JIS</td></tr>
165: * <tr><td>ko</td><td>EUC-KR</td></tr>
166: * <tr><td>lt</td><td>ISO-8859-2</td></tr>
167: * <tr><td>lv</td><td>ISO-8859-2</td></tr>
168: * <tr><td>mk</td><td>ISO-8859-5</td></tr>
169: * <tr><td>nl</td><td>ISO-8859-1</td></tr>
170: * <tr><td>no</td><td>ISO-8859-1</td></tr>
171: * <tr><td>pl</td><td>ISO-8859-2</td></tr>
172: * <tr><td>pt</td><td>ISO-8859-1</td></tr>
173: * <tr><td>ro</td><td>ISO-8859-2</td></tr>
174: * <tr><td>ru</td><td>ISO-8859-5</td></tr>
175: * <tr><td>sh</td><td>ISO-8859-5</td></tr>
176: * <tr><td>sk</td><td>ISO-8859-2</td></tr>
177: * <tr><td>sl</td><td>ISO-8859-2</td></tr>
178: * <tr><td>sq</td><td>ISO-8859-2</td></tr>
179: * <tr><td>sr</td><td>ISO-8859-5</td></tr>
180: * <tr><td>sv</td><td>ISO-8859-1</td></tr>
181: * <tr><td>tr</td><td>ISO-8859-9</td></tr>
182: * <tr><td>uk</td><td>ISO-8859-5</td></tr>
183: * <tr><td>zh</td><td>GB2312</td></tr>
184: * <tr><td>zh_TW</td><td>Big5</td></tr>
185: * </table>
186: * @see #clearEncodingMap
187: * @see #setEncoding
188: */
189: public void loadBuiltInEncodingMap() {
190: encodingMap.clear();
191: encodingMap.put("ar", "ISO-8859-6");
192: encodingMap.put("be", "ISO-8859-5");
193: encodingMap.put("bg", "ISO-8859-5");
194: encodingMap.put("ca", "ISO-8859-1");
195: encodingMap.put("cs", "ISO-8859-2");
196: encodingMap.put("da", "ISO-8859-1");
197: encodingMap.put("de", "ISO-8859-1");
198: encodingMap.put("el", "ISO-8859-7");
199: encodingMap.put("en", "ISO-8859-1");
200: encodingMap.put("es", "ISO-8859-1");
201: encodingMap.put("et", "ISO-8859-1");
202: encodingMap.put("fi", "ISO-8859-1");
203: encodingMap.put("fr", "ISO-8859-1");
204: encodingMap.put("hr", "ISO-8859-2");
205: encodingMap.put("hu", "ISO-8859-2");
206: encodingMap.put("is", "ISO-8859-1");
207: encodingMap.put("it", "ISO-8859-1");
208: encodingMap.put("iw", "ISO-8859-8");
209: encodingMap.put("ja", "Shift_JIS");
210: encodingMap.put("ko", "EUC-KR");
211: encodingMap.put("lt", "ISO-8859-2");
212: encodingMap.put("lv", "ISO-8859-2");
213: encodingMap.put("mk", "ISO-8859-5");
214: encodingMap.put("nl", "ISO-8859-1");
215: encodingMap.put("no", "ISO-8859-1");
216: encodingMap.put("pl", "ISO-8859-2");
217: encodingMap.put("pt", "ISO-8859-1");
218: encodingMap.put("ro", "ISO-8859-2");
219: encodingMap.put("ru", "ISO-8859-5");
220: encodingMap.put("sh", "ISO-8859-5");
221: encodingMap.put("sk", "ISO-8859-2");
222: encodingMap.put("sl", "ISO-8859-2");
223: encodingMap.put("sq", "ISO-8859-2");
224: encodingMap.put("sr", "ISO-8859-5");
225: encodingMap.put("sv", "ISO-8859-1");
226: encodingMap.put("tr", "ISO-8859-9");
227: encodingMap.put("uk", "ISO-8859-5");
228: encodingMap.put("zh", "GB2312");
229: encodingMap.put("zh_TW", "Big5");
230: }
231:
232: /**
233: * Clears language-to-encoding map.
234: * @see #loadBuiltInEncodingMap
235: * @see #setEncoding
236: */
237: public void clearEncodingMap() {
238: encodingMap.clear();
239: }
240:
241: /**
242: * Returns the default (singleton) Configuration object. Note that you can
243: * create as many separate configurations as you wish; this global instance
244: * is provided for convenience, or when you have no reason to use a separate
245: * instance.
246: *
247: * @deprecated The usage of the static singleton (the "default")
248: * {@link Configuration} instance can easily cause erroneous, unpredictable
249: * behavior. This is because multiple independent software components may use
250: * FreeMarker internally inside the same application, so they will interfere
251: * because of the common {@link Configuration} instance. Each such component
252: * should use its own private {@link Configuration} object instead, that it
253: * typically creates with <code>new Configuration()</code> when the component
254: * is initialized.
255: */
256: static public Configuration getDefaultConfiguration() {
257: return defaultConfig;
258: }
259:
260: /**
261: * Sets the Configuration object that will be retrieved from future calls
262: * to {@link #getDefaultConfiguration()}.
263: *
264: * @deprecated Using the "default" {@link Configuration} instance can
265: * easily lead to erroneous, unpredictable behaviour.
266: * See more {@link Configuration#getDefaultConfiguration() here...}.
267: */
268: static public void setDefaultConfiguration(Configuration config) {
269: defaultConfig = config;
270: }
271:
272: /**
273: * Sets a template loader that is used to look up and load templates.
274: * By providing your own template loader, you can customize the way
275: * templates are loaded. Several convenience methods in this class already
276: * allow you to install commonly used loaders:
277: * {@link #setClassForTemplateLoading(Class, String)},
278: * {@link #setDirectoryForTemplateLoading(File)}, and
279: * {@link #setServletContextForTemplateLoading(Object, String)}. By default,
280: * a multi-loader is used that first tries to load a template from the file
281: * in the current directory, then from a resource on the classpath.
282: */
283: public synchronized void setTemplateLoader(TemplateLoader loader) {
284: createTemplateCache(loader, cache.getCacheStorage());
285: }
286:
287: private void createTemplateCache(TemplateLoader loader,
288: CacheStorage storage) {
289: TemplateCache oldCache = cache;
290: cache = new TemplateCache(loader, storage);
291: cache.setDelay(oldCache.getDelay());
292: cache.setConfiguration(this );
293: cache.setLocalizedLookup(localizedLookup);
294: }
295:
296: /**
297: * @return the template loader that is used to look up and load templates.
298: * @see #setTemplateLoader
299: */
300: public TemplateLoader getTemplateLoader() {
301: return cache.getTemplateLoader();
302: }
303:
304: public synchronized void setCacheStorage(CacheStorage storage) {
305: createTemplateCache(cache.getTemplateLoader(), storage);
306: }
307:
308: /**
309: * Set the explicit directory from which to load templates.
310: */
311: public void setDirectoryForTemplateLoading(File dir)
312: throws IOException {
313: TemplateLoader tl = getTemplateLoader();
314: if (tl instanceof FileTemplateLoader) {
315: String path = ((FileTemplateLoader) tl).baseDir
316: .getCanonicalPath();
317: if (path.equals(dir.getCanonicalPath()))
318: return;
319: }
320: setTemplateLoader(new FileTemplateLoader(dir));
321: }
322:
323: /**
324: * Sets the servlet context from which to load templates
325: * @param sctxt the ServletContext object. Note that the type is <code>Object</code>
326: * to prevent class loading errors when user who uses FreeMarker not for
327: * servlets has no javax.servlet in the CLASSPATH.
328: * @param path the path relative to the ServletContext.
329: * If this path is absolute, it is taken to be relative
330: * to the server's URL, i.e. http://myserver.com/
331: * and if the path is relative, it is taken to be
332: * relative to the web app context, i.e.
333: * http://myserver.context.com/mywebappcontext/
334: */
335: public void setServletContextForTemplateLoading(Object sctxt,
336: String path) {
337: try {
338: if (path == null) {
339: setTemplateLoader((TemplateLoader) ClassUtil
340: .forName(
341: "freemarker.cache.WebappTemplateLoader")
342: .getConstructor(
343: new Class[] { ClassUtil
344: .forName("javax.servlet.ServletContext") })
345: .newInstance(new Object[] { sctxt }));
346: } else {
347: setTemplateLoader((TemplateLoader) ClassUtil
348: .forName(
349: "freemarker.cache.WebappTemplateLoader")
350: .getConstructor(
351: new Class[] {
352: ClassUtil
353: .forName("javax.servlet.ServletContext"),
354: String.class }).newInstance(
355: new Object[] { sctxt, path }));
356: }
357: } catch (Exception exc) {
358: throw new RuntimeException("Internal FreeMarker error: "
359: + exc);
360: }
361: }
362:
363: /**
364: * Sets a class relative to which we do the
365: * Class.getResource() call to load templates.
366: */
367: public void setClassForTemplateLoading(Class clazz,
368: String pathPrefix) {
369: setTemplateLoader(new ClassTemplateLoader(clazz, pathPrefix));
370: }
371:
372: /**
373: * Set the time in seconds that must elapse before checking whether there is a newer
374: * version of a template file.
375: * This method is thread-safe and can be called while the engine works.
376: */
377: public void setTemplateUpdateDelay(int delay) {
378: cache.setDelay(1000L * delay);
379: }
380:
381: /**
382: * Sets whether directives such as if, else, etcetera
383: * must be written as #if, #else, etcetera.
384: * Any tag not starting with <# or </# is considered as plain text
385: * and will go to the output as is. Tag starting with <# or </# must
386: * be valid FTL tag, or else the template is invalid (i.e. <#noSuchDirective>
387: * is an error).
388: */
389:
390: public void setStrictSyntaxMode(boolean b) {
391: strictSyntax = b;
392: }
393:
394: /**
395: * Tells whether directives such as if, else, etcetera
396: * must be written as #if, #else, etcetera.
397: *
398: * @see #setStrictSyntaxMode
399: */
400: public boolean getStrictSyntaxMode() {
401: return strictSyntax;
402: }
403:
404: /**
405: * Sets whether the FTL parser will try to remove
406: * superfluous white-space around certain FTL tags.
407: */
408: public void setWhitespaceStripping(boolean b) {
409: whitespaceStripping = b;
410: }
411:
412: /**
413: * Gets whether the FTL parser will try to remove
414: * superfluous white-space around certain FTL tags.
415: *
416: * @see #setWhitespaceStripping
417: */
418: public boolean getWhitespaceStripping() {
419: return whitespaceStripping;
420: }
421:
422: /**
423: * Determines the syntax of the template files (angle bracket VS square bracket)
424: * that has no <markup>ftl</markup> directive in it. The <code>tagSyntax</code>
425: * parameter must be one of:
426: * <ul>
427: * <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
428: * use the syntax of the first FreeMarker tag (can be anything, like <tt>list</tt>,
429: * <tt>include</tt>, user defined, ...etc)
430: * <li>{@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}:
431: * use the angle bracket syntax (the normal syntax)
432: * <li>{@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}:
433: * use the square bracket syntax
434: * </ul>
435: *
436: * <p>In FreeMarker 2.3.x {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX} is the
437: * default for better backward compatibility. Starting from 2.4.x {@link
438: * Configuration#AUTO_DETECT_TAG_SYNTAX} is the default, so it is recommended to use
439: * that even for 2.3.x.
440: *
441: * <p>This setting is ignored for the templates that have <tt>ftl</tt> directive in
442: * it. For those templates the syntax used for the <tt>ftl</tt> directive determines
443: * the syntax.
444: */
445: public void setTagSyntax(int tagSyntax) {
446: if (tagSyntax != AUTO_DETECT_TAG_SYNTAX
447: && tagSyntax != SQUARE_BRACKET_TAG_SYNTAX
448: && tagSyntax != ANGLE_BRACKET_TAG_SYNTAX) {
449: throw new IllegalArgumentException(
450: "This can only be set to one of three settings: Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, or Configuration.SQAUARE_BRACKET_SYNTAX");
451: }
452: this .tagSyntax = tagSyntax;
453: }
454:
455: /**
456: * @return whether the alternative square bracket
457: * syntax is set as the default
458: */
459: public int getTagSyntax() {
460: return tagSyntax;
461: }
462:
463: /**
464: * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), thisCfg.getEncoding(thisCfg.getLocale()), true)</tt>.
465: */
466: public Template getTemplate(String name) throws IOException {
467: Locale loc = getLocale();
468: return getTemplate(name, loc, getEncoding(loc), true);
469: }
470:
471: /**
472: * Equivalent to <tt>getTemplate(name, locale, thisCfg.getEncoding(locale), true)</tt>.
473: */
474: public Template getTemplate(String name, Locale locale)
475: throws IOException {
476: return getTemplate(name, locale, getEncoding(locale), true);
477: }
478:
479: /**
480: * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), encoding, true)</tt>.
481: */
482: public Template getTemplate(String name, String encoding)
483: throws IOException {
484: return getTemplate(name, getLocale(), encoding, true);
485: }
486:
487: /**
488: * Equivalent to <tt>getTemplate(name, locale, encoding, true)</tt>.
489: */
490: public Template getTemplate(String name, Locale locale,
491: String encoding) throws IOException {
492: return getTemplate(name, locale, encoding, true);
493: }
494:
495: /**
496: * Retrieves a template specified by a name and locale, interpreted using
497: * the specified character encoding, either parsed or unparsed. For the
498: * exact semantics of parameters, see
499: * {@link TemplateCache#getTemplate(String, Locale, String, boolean)}.
500: * @return the requested template.
501: * @throws FileNotFoundException if the template could not be found.
502: * @throws IOException if there was a problem loading the template.
503: * @throws ParseException (extends <code>IOException</code>) if the template is syntactically bad.
504: */
505: public Template getTemplate(String name, Locale locale,
506: String encoding, boolean parse) throws IOException {
507: Template result = cache.getTemplate(name, locale, encoding,
508: parse);
509: if (result == null) {
510: throw new FileNotFoundException("Template " + name
511: + " not found.");
512: }
513: return result;
514: }
515:
516: /**
517: * Sets the default encoding for converting bytes to characters when
518: * reading template files in a locale for which no explicit encoding
519: * was specified. Defaults to default system encoding.
520: */
521: public void setDefaultEncoding(String encoding) {
522: defaultEncoding = encoding;
523: }
524:
525: /**
526: * Gets the default encoding for converting bytes to characters when
527: * reading template files in a locale for which no explicit encoding
528: * was specified. Defaults to default system encoding.
529: */
530: public String getDefaultEncoding() {
531: return defaultEncoding;
532: }
533:
534: /**
535: * Gets the preferred character encoding for the given locale, or the
536: * default encoding if no encoding is set explicitly for the specified
537: * locale. You can associate encodings with locales using
538: * {@link #setEncoding(Locale, String)} or {@link #loadBuiltInEncodingMap()}.
539: * @param loc the locale
540: * @return the preferred character encoding for the locale.
541: */
542: public String getEncoding(Locale loc) {
543: // Try for a full name match (may include country and variant)
544: String charset = (String) encodingMap.get(loc.toString());
545: if (charset == null) {
546: if (loc.getVariant().length() > 0) {
547: Locale l = new Locale(loc.getLanguage(), loc
548: .getCountry());
549: charset = (String) encodingMap.get(l.toString());
550: if (charset != null) {
551: encodingMap.put(loc.toString(), charset);
552: }
553: }
554: charset = (String) encodingMap.get(loc.getLanguage());
555: if (charset != null) {
556: encodingMap.put(loc.toString(), charset);
557: }
558: }
559: return charset != null ? charset : defaultEncoding;
560: }
561:
562: /**
563: * Sets the character set encoding to use for templates of
564: * a given locale. If there is no explicit encoding set for some
565: * locale, then the default encoding will be used, what you can
566: * set with {@link #setDefaultEncoding}.
567: *
568: * @see #clearEncodingMap
569: * @see #loadBuiltInEncodingMap
570: */
571: public void setEncoding(Locale locale, String encoding) {
572: encodingMap.put(locale.toString(), encoding);
573: }
574:
575: /**
576: * Adds a shared variable to the configuration.
577: * Shared variables are variables that are visible
578: * as top-level variables for all templates which use this
579: * configuration, if the data model does not contain a
580: * variable with the same name.
581: *
582: * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
583: * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
584: *
585: * @param name the name used to access the data object from your template.
586: * If a shared variable with this name already exists, it will replace
587: * that.
588: * @see #setSharedVariable(String,Object)
589: * @see #setAllSharedVariables
590: */
591: public void setSharedVariable(String name, TemplateModel tm) {
592: variables.put(name, tm);
593: }
594:
595: /**
596: * Returns the set containing the names of all defined shared variables.
597: * The method returns a new Set object on each call that is completely
598: * disconnected from the Configuration. That is, modifying the set will have
599: * no effect on the Configuration object.
600: */
601: public Set getSharedVariableNames() {
602: return new HashSet(variables.keySet());
603: }
604:
605: /**
606: * Adds shared variable to the configuration.
607: * It uses {@link Configurable#getObjectWrapper()} to wrap the
608: * <code>obj</code>.
609: * @see #setSharedVariable(String,TemplateModel)
610: * @see #setAllSharedVariables
611: */
612: public void setSharedVariable(String name, Object obj)
613: throws TemplateModelException {
614: setSharedVariable(name, getObjectWrapper().wrap(obj));
615: }
616:
617: /**
618: * Adds all object in the hash as shared variable to the configuration.
619: *
620: * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
621: * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
622: *
623: * @param hash a hash model whose objects will be copied to the
624: * configuration with same names as they are given in the hash.
625: * If a shared variable with these names already exist, it will be replaced
626: * with those from the map.
627: *
628: * @see #setSharedVariable(String,Object)
629: * @see #setSharedVariable(String,TemplateModel)
630: */
631: public void setAllSharedVariables(TemplateHashModelEx hash)
632: throws TemplateModelException {
633: TemplateModelIterator keys = hash.keys().iterator();
634: TemplateModelIterator values = hash.values().iterator();
635: while (keys.hasNext()) {
636: setSharedVariable(((TemplateScalarModel) keys.next())
637: .getAsString(), values.next());
638: }
639: }
640:
641: /**
642: * Gets a shared variable. Shared variables are variables that are
643: * available to all templates. When a template is processed, and an identifier
644: * is undefined in the data model, a shared variable object with the same identifier
645: * is then looked up in the configuration. There are several predefined variables
646: * that are always available through this method, see the FreeMarker manual
647: * for a comprehensive list of them.
648: *
649: * @see #setSharedVariable(String,Object)
650: * @see #setSharedVariable(String,TemplateModel)
651: * @see #setAllSharedVariables
652: */
653: public TemplateModel getSharedVariable(String name) {
654: return (TemplateModel) variables.get(name);
655: }
656:
657: /**
658: * Removes all shared variables, except the predefined ones (compress, html_escape, etc.).
659: */
660: public void clearSharedVariables() {
661: variables.clear();
662: loadBuiltInSharedVariables();
663: }
664:
665: /**
666: * Removes all entries from the template cache, thus forcing reloading of templates
667: * on subsequent <code>getTemplate</code> calls.
668: * This method is thread-safe and can be called while the engine works.
669: */
670: public void clearTemplateCache() {
671: cache.clear();
672: }
673:
674: /**
675: * Returns if localized template lookup is enabled or not.
676: * This method is thread-safe and can be called while the engine works.
677: */
678: public boolean getLocalizedLookup() {
679: return cache.getLocalizedLookup();
680: }
681:
682: /**
683: * Enables/disables localized template lookup. Enabled by default.
684: * This method is thread-safe and can be called while the engine works.
685: */
686: public void setLocalizedLookup(boolean localizedLookup) {
687: this .localizedLookup = localizedLookup;
688: cache.setLocalizedLookup(localizedLookup);
689: }
690:
691: /**
692: * Sets a setting by name and string value.
693: *
694: * In additional to the settings understood by
695: * {@link Configurable#setSetting the super method}, it understands these:
696: * <ul>
697: * <li><code>"auto_import"</code>: Sets the list of auto-imports. Example of valid value:
698: * <br><code>/lib/form.ftl as f, /lib/widget as w, "/lib/evil name.ftl" as odd</code>
699: * See: {@link #setAutoImports}
700: * <li><code>"auto_include"</code>: Sets the list of auto-includes. Example of valid value:
701: * <br><code>/include/common.ftl, "/include/evil name.ftl"</code>
702: * See: {@link #setAutoIncludes}
703: * <li><code>"default_encoding"</code>: The name of the charset, such as <code>"UTF-8"</code>.
704: * See: {@link #setDefaultEncoding}
705: * <li><code>"localized_lookup"</code>:
706: * <code>"true"</code>, <code>"false"</code>, <code>"yes"</code>, <code>"no"</code>,
707: * <code>"t"</code>, <code>"f"</code>, <code>"y"</code>, <code>"n"</code>.
708: * Case insensitive.
709: * See: {@link #setLocalizedLookup}
710: * <li><code>"strict_syntax"</code>: <code>"true"</code>, <code>"false"</code>, etc.
711: * See: {@link #setStrictSyntaxMode}
712: * <li><code>"whitespace_stripping"</code>: <code>"true"</code>, <code>"false"</code>, etc.
713: * See: {@link #setWhitespaceStripping}
714: * <li><code>"cache_storage"</code>: If the value contains dot, then it is
715: * interpreted as class name, and the object will be created with
716: * its parameterless constructor. If the value does not contain dot,
717: * then a {@link freemarker.cache.MruCacheStorage} will be used with the
718: * maximum strong and soft sizes specified with the setting value. Examples
719: * of valid setting values:
720: * <table border=1 cellpadding=4>
721: * <tr><th>Setting value<th>max. strong size<th>max. soft size
722: * <tr><td><code>"strong:50, soft:500"</code><td>50<td>500
723: * <tr><td><code>"strong:100, soft"</code><td>100<td><code>Integer.MAX_VALUE</code>
724: * <tr><td><code>"strong:100"</code><td>100<td>0
725: * <tr><td><code>"soft:100"</code><td>0<td>100
726: * <tr><td><code>"strong"</code><td><code>Integer.MAX_VALUE</code><td>0
727: * <tr><td><code>"soft"</code><td>0<td><code>Integer.MAX_VALUE</code>
728: * </table>
729: * The value is not case sensitive. The order of <tt>soft</tt> and <tt>strong</tt>
730: * entries is not significant.
731: * See also: {@link #setCacheStorage}
732: * <li><code>"template_update_delay"</code>: Valid positive integer, the
733: * update delay measured in seconds.
734: * See: {@link #setTemplateUpdateDelay}
735: * <li><code>"tag_syntax"</code>: Must be one of:
736: * <code>"auto_detect"</code>, <code>"angle_bracket"</code>,
737: * <code>"square_bracket"</code>.
738: * </ul>
739: *
740: * @param key the name of the setting.
741: * @param value the string that describes the new value of the setting.
742: *
743: * @throws UnknownSettingException if the key is wrong.
744: * @throws TemplateException if the new value of the setting can't be set
745: * for any other reasons.
746: */
747: public void setSetting(String key, String value)
748: throws TemplateException {
749: if ("TemplateUpdateInterval".equalsIgnoreCase(key)) {
750: key = TEMPLATE_UPDATE_DELAY_KEY;
751: } else if ("DefaultEncoding".equalsIgnoreCase(key)) {
752: key = DEFAULT_ENCODING_KEY;
753: }
754: try {
755: if (DEFAULT_ENCODING_KEY.equals(key)) {
756: setDefaultEncoding(value);
757: } else if (LOCALIZED_LOOKUP_KEY.equals(key)) {
758: setLocalizedLookup(StringUtil.getYesNo(value));
759: } else if (STRICT_SYNTAX_KEY.equals(key)) {
760: setStrictSyntaxMode(StringUtil.getYesNo(value));
761: } else if (WHITESPACE_STRIPPING_KEY.equals(key)) {
762: setWhitespaceStripping(StringUtil.getYesNo(value));
763: } else if (CACHE_STORAGE_KEY.equals(key)) {
764: if (value.indexOf('.') == -1) {
765: int strongSize = 0;
766: int softSize = 0;
767: Map map = StringUtil.parseNameValuePairList(value,
768: String.valueOf(Integer.MAX_VALUE));
769: Iterator it = map.entrySet().iterator();
770: while (it.hasNext()) {
771: Map.Entry ent = (Map.Entry) it.next();
772: String pname = (String) ent.getKey();
773: int pvalue;
774: try {
775: pvalue = Integer.parseInt((String) ent
776: .getValue());
777: } catch (NumberFormatException e) {
778: throw invalidSettingValueException(key,
779: value);
780: }
781: if ("soft".equalsIgnoreCase(pname)) {
782: softSize = pvalue;
783: } else if ("strong".equalsIgnoreCase(pname)) {
784: strongSize = pvalue;
785: } else {
786: throw invalidSettingValueException(key,
787: value);
788: }
789: }
790: if (softSize == 0 && strongSize == 0) {
791: throw invalidSettingValueException(key, value);
792: }
793: setCacheStorage(new MruCacheStorage(strongSize,
794: softSize));
795: } else {
796: setCacheStorage((CacheStorage) ClassUtil.forName(
797: value).newInstance());
798: }
799: } else if (TEMPLATE_UPDATE_DELAY_KEY.equals(key)) {
800: setTemplateUpdateDelay(Integer.parseInt(value));
801: } else if (AUTO_INCLUDE_KEY.equals(key)) {
802: setAutoIncludes(new SettingStringParser(value)
803: .parseAsList());
804: } else if (AUTO_IMPORT_KEY.equals(key)) {
805: setAutoImports(new SettingStringParser(value)
806: .parseAsImportList());
807: } else if (TAG_SYNTAX_KEY.equals(key)) {
808: if ("auto_detect".equals(value)) {
809: setTagSyntax(AUTO_DETECT_TAG_SYNTAX);
810: } else if ("angle_bracket".equals(value)) {
811: setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX);
812: } else if ("square_bracket".equals(value)) {
813: setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
814: } else {
815: throw invalidSettingValueException(key, value);
816: }
817: } else {
818: super .setSetting(key, value);
819: }
820: } catch (Exception e) {
821: throw new TemplateException("Failed to set setting " + key
822: + " to value " + value, e, getEnvironment());
823: }
824: }
825:
826: /**
827: * Add an auto-imported template.
828: * The importing will happen at the top of any template that
829: * is vended by this Configuration object.
830: * @param namespace the name of the namespace into which the template is imported
831: * @param template the name of the template
832: */
833: public synchronized void addAutoImport(String namespace,
834: String template) {
835: autoImports.remove(namespace);
836: autoImports.add(namespace);
837: autoImportMap.put(namespace, template);
838: }
839:
840: /**
841: * Remove an auto-imported template
842: * @param namespace the name of the namespace into which the template was imported
843: */
844:
845: public synchronized void removeAutoImport(String namespace) {
846: autoImports.remove(namespace);
847: autoImportMap.remove(namespace);
848: }
849:
850: /**
851: * set a map of namespace names to templates for auto-importing
852: * a set of templates. Note that all previous auto-imports are removed.
853: */
854:
855: public synchronized void setAutoImports(Map map) {
856: autoImports = new ArrayList(map.keySet());
857: if (map instanceof HashMap) {
858: autoImportMap = (Map) ((HashMap) map).clone();
859: } else if (map instanceof SortedMap) {
860: autoImportMap = new TreeMap(map);
861: } else {
862: autoImportMap = new HashMap(map);
863: }
864: }
865:
866: void doAutoImports(Environment env) throws TemplateException,
867: IOException {
868: for (int i = 0; i < autoImports.size(); i++) {
869: String namespace = (String) autoImports.get(i);
870: String templateName = (String) autoImportMap.get(namespace);
871: env.importLib(templateName, namespace);
872: }
873: }
874:
875: /**
876: * add a template to be automatically included at the top of any template that
877: * is vended by this Configuration object.
878: * @param templateName the lookup name of the template.
879: */
880:
881: public synchronized void addAutoInclude(String templateName) {
882: autoIncludes.remove(templateName);
883: autoIncludes.add(templateName);
884: }
885:
886: /**
887: * set the list of automatically included templates.
888: * Note that all previous auto-includes are removed.
889: */
890: public synchronized void setAutoIncludes(List templateNames) {
891: autoIncludes.clear();
892: Iterator it = templateNames.iterator();
893: while (it.hasNext()) {
894: Object o = it.next();
895: if (!(o instanceof String)) {
896: throw new IllegalArgumentException(
897: "List items must be String-s.");
898: }
899: autoIncludes.add(o);
900: }
901: }
902:
903: /**
904: * remove a template from the auto-include list.
905: * @param templateName the lookup name of the template in question.
906: */
907:
908: public synchronized void removeAutoInclude(String templateName) {
909: autoIncludes.remove(templateName);
910: }
911:
912: /**
913: * Returns FreeMarker version number string.
914: * Examples of possible return values:
915: * <code>"2.2.5"</code>, <code>"2.3pre13"</code>,
916: * <code>"2.3pre13mod"</code>, <code>"2.3rc1"</code>, <code>"2.3"</code>,
917: * <code>"3.0"</code>.
918: *
919: * <p>Notes on FreeMarker version numbering rules:
920: * <ul>
921: * <li>"pre" and "rc" (lowercase!) means "preview" and "release
922: * candidate" respectively. It is must be followed with a
923: * number (as "1" for the first release candidate).
924: * <li>The "mod" after the version number indicates that it's an
925: * unreleased modified version of the released version.
926: * After releases, the nighly builds are such releases. E.g.
927: * the nightly build after releasing "2.2.1" but before releasing
928: * "2.2.2" is "2.2.1mod".
929: * <li>The 2nd version number must be present, and maybe 0,
930: * as in "3.0".
931: * <li>The 3rd version number is never 0. E.g. the version
932: * number string for the first release of the 2.2 series
933: * is "2.2", and NOT "2.2.0".
934: * <li>When only the 3rd version number increases
935: * (2.2 -> 2.2.1, 2.2.1 -> 2.2.2 etc.), 100% backward compatiblity
936: * with the previous version MUST be kept.
937: * This means that <tt>freemarker.jar</tt> can be replaced in an
938: * application without risk (as far as the application doesn't depend
939: * on the presence of a FreeMarker bug).
940: * Note that backward compatibility restrictions do not apply for
941: * preview releases.
942: * </ul>
943: */
944: public static String getVersionNumber() {
945: if (cachedVersion != null) {
946: return cachedVersion;
947: }
948: try {
949: Properties vp = new Properties();
950: InputStream ins = Configuration.class.getClassLoader()
951: .getResourceAsStream(
952: "freemarker/version.properties");
953: if (ins == null) {
954: throw new RuntimeException("Version file is missing.");
955: } else {
956: try {
957: vp.load(ins);
958: } finally {
959: ins.close();
960: }
961: String v = vp.getProperty("version");
962: if (v == null) {
963: throw new RuntimeException(
964: "Version file is corrupt: version key is missing.");
965: }
966: cachedVersion = v;
967: }
968: return cachedVersion;
969: } catch (IOException e) {
970: throw new RuntimeException("Failed to load version file: "
971: + e);
972: }
973: }
974:
975: void doAutoIncludes(Environment env) throws TemplateException,
976: IOException {
977: for (int i = 0; i < autoIncludes.size(); i++) {
978: String templateName = (String) autoIncludes.get(i);
979: Template template = getTemplate(templateName, env
980: .getLocale());
981: env.include(template);
982: }
983: }
984:
985: }
|