KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > core > BuiltIn


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 freemarker.template.*;
56
57 import java.io.UnsupportedEncodingException JavaDoc;
58 import java.text.DateFormat JavaDoc;
59 import java.text.NumberFormat JavaDoc;
60 import java.util.Date JavaDoc;
61 import java.util.HashMap JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64
65 import freemarker.template.utility.ClassUtil;
66 import freemarker.template.utility.StringUtil;
67 import freemarker.core.NodeBuiltins.*;
68 import freemarker.core.NumericalBuiltins.*;
69 import freemarker.core.SequenceBuiltins.*;
70 import freemarker.core.StringBuiltins.*;
71
72 /**
73  * The ? operator used to get the
74  * functionality of built-in unary operators
75  * @author <a HREF="mailto:jon@revusky.com">Jonathan Revusky</a>
76  */

77 abstract class BuiltIn extends Expression implements Cloneable JavaDoc {
78     Expression target;
79     String JavaDoc key;
80
81     static final HashMap JavaDoc builtins = new HashMap JavaDoc();
82
83     static {
84         // These are the only ones we have now.
85
// We throw a parse exception if it's not one of these.
86
builtins.put("ancestors", new ancestorsBI());
87         builtins.put("byte", new byteBI());
88         builtins.put("cap_first", new cap_firstBI());
89         builtins.put("capitalize", new capitalizeBI());
90         builtins.put("children", new childrenBI());
91         builtins.put("chop_linebreak", new chop_linebreakBI());
92         builtins.put("contains", new containsBI());
93         builtins.put("date", new dateBI(TemplateDateModel.DATE));
94         builtins.put("datetime", new dateBI(TemplateDateModel.DATETIME));
95         builtins.put("default", new defaultBI());
96         builtins.put("double", new doubleBI());
97         builtins.put("ends_with", new ends_withBI());
98         builtins.put("eval", new evalBI());
99         builtins.put("exists", new existsBI());
100         builtins.put("first", new firstBI());
101         builtins.put("float", new floatBI());
102         builtins.put("has_content", new has_contentBI());
103         builtins.put("html", new htmlBI());
104         builtins.put("if_exists", new if_existsBI());
105         builtins.put("index_of", new index_ofBI());
106         builtins.put("int", new intBI());
107         builtins.put("interpret", new Interpret());
108         builtins.put("is_boolean", new is_booleanBI());
109         builtins.put("is_collection", new is_collectionBI());
110         builtins.put("is_date", new is_dateBI());
111         builtins.put("is_directive", new is_directiveBI());
112         builtins.put("is_enumerable", new is_enumerableBI());
113         builtins.put("is_hash_ex", new is_hash_exBI());
114         builtins.put("is_hash", new is_hashBI());
115         builtins.put("is_indexable", new is_indexableBI());
116         builtins.put("is_macro", new is_macroBI());
117         builtins.put("is_method", new is_methodBI());
118         builtins.put("is_node", new is_nodeBI());
119         builtins.put("is_number", new is_numberBI());
120         builtins.put("is_sequence", new is_sequenceBI());
121         builtins.put("is_string", new is_stringBI());
122         builtins.put("is_transform", new is_transformBI());
123         builtins.put("j_string", new j_stringBI());
124         builtins.put("js_string", new js_stringBI());
125         builtins.put("keys", new keysBI());
126         builtins.put("last_index_of", new last_index_ofBI());
127         builtins.put("last", new lastBI());
128         builtins.put("left_pad", new left_padBI());
129         builtins.put("length", new lengthBI());
130         builtins.put("long", new longBI());
131         builtins.put("lower_case", new lower_caseBI());
132         builtins.put("namespace", new namespaceBI());
133         builtins.put("new", new NewBI());
134         builtins.put("node_name", new node_nameBI());
135         builtins.put("node_namespace", new node_namespaceBI());
136         builtins.put("node_type", new node_typeBI());
137         builtins.put("number", new numberBI());
138         builtins.put("parent", new parentBI());
139         builtins.put("replace", new replaceBI());
140         builtins.put("reverse", new reverseBI());
141         builtins.put("right_pad", new right_padBI());
142         builtins.put("root", new rootBI());
143         builtins.put("rtf", new rtfBI());
144         builtins.put("seq_contains", new seq_containsBI());
145         builtins.put("seq_index_of", new seq_index_ofBI(1));
146         builtins.put("seq_last_index_of", new seq_index_ofBI(-1));
147         builtins.put("short", new shortBI());
148         builtins.put("size", new sizeBI());
149         builtins.put("sort_by", new sort_byBI());
150         builtins.put("sort", new sortBI());
151         builtins.put("split", new splitBI());
152         builtins.put("starts_with", new starts_withBI());
153         builtins.put("string", new stringBI());
154         builtins.put("time", new dateBI(TemplateDateModel.TIME));
155         builtins.put("trim", new trimBI());
156         builtins.put("uncap_first", new uncap_firstBI());
157         builtins.put("upper_case", new upper_caseBI());
158         builtins.put("url", new urlBI());
159         builtins.put("values", new valuesBI());
160         builtins.put("web_safe", builtins.get("html")); // deprecated; use ?html instead
161
builtins.put("word_list", new word_listBI());
162         builtins.put("xml", new xmlBI());
163         try {
164             Class.forName("java.util.regex.Pattern");
165             builtins.put("matches", instantiate("freemarker.core.RegexBuiltins$matchesBI"));
166             builtins.put("groups", instantiate("freemarker.core.RegexBuiltins$groupsBI"));
167             builtins.put("replace", instantiate("freemarker.core.RegexBuiltins$replace_reBI"));
168             builtins.put("split", instantiate("freemarker.core.RegexBuiltins$split_reBI"));
169         } catch (Exception JavaDoc e) {}
170     }
171
172     private static Object JavaDoc instantiate(String JavaDoc className) throws Exception JavaDoc
173     {
174         return ClassUtil.forName(className).newInstance();
175     }
176     
177     static BuiltIn newBuiltIn(Expression target, String JavaDoc key, Token tok, String JavaDoc templateName) throws ParseException {
178         BuiltIn bi = (BuiltIn) builtins.get(key);
179         if (bi == null) {
180             String JavaDoc locationInfo = "Error on line " + tok.beginLine + ", column " + tok.beginColumn + ", in template " + templateName + "\n";
181             StringBuffer JavaDoc buf = new StringBuffer JavaDoc("Found " + key + ", expecting one of: ");
182             for (Iterator JavaDoc it= builtins.keySet().iterator(); it.hasNext();) {
183                 if (it.hasNext()) {
184                     buf.append(" ");
185                 } else {
186                     buf.append( " or ");
187                 }
188                 buf.append(it.next());
189                 if (it.hasNext()) {
190                     buf.append(", ");
191                 }
192             }
193             throw new ParseException(locationInfo + buf, target);
194         }
195         try {
196             bi = (BuiltIn) bi.clone();
197         }
198         catch (CloneNotSupportedException JavaDoc e) {
199             throw new InternalError JavaDoc();
200         }
201         bi.target = target;
202         bi.key = key;
203         return bi;
204     }
205
206     public String JavaDoc getCanonicalForm() {
207         return target.getCanonicalForm() + "?" + key;
208     }
209
210     boolean isLiteral() {
211         return false; // be on the safe side.
212
}
213
214     Expression _deepClone(String JavaDoc name, Expression subst) {
215         try {
216             BuiltIn clone = (BuiltIn)clone();
217             clone.target = target.deepClone(name, subst);
218             return clone;
219         }
220         catch (CloneNotSupportedException JavaDoc e) {
221             throw new InternalError JavaDoc();
222         }
223     }
224
225
226     static class lengthBI extends BuiltIn {
227         TemplateModel _getAsTemplateModel(Environment env)
228         throws TemplateException
229         {
230             return new SimpleNumber(target.getStringValue(env).length());
231         }
232     }
233
234     static class dateBI extends BuiltIn {
235         private final int dateType;
236         
237         dateBI(int dateType) {
238             this.dateType = dateType;
239         }
240         
241         TemplateModel _getAsTemplateModel(Environment env)
242                 throws TemplateException
243         {
244             TemplateModel model = target.getAsTemplateModel(env);
245             if (model instanceof TemplateDateModel) {
246                 TemplateDateModel dmodel = (TemplateDateModel)model;
247                 int dtype = dmodel.getDateType();
248                 // Any date model can be coerced into its own type
249
if(dateType == dtype) {
250                     return model;
251                 }
252                 // unknown and datetime can be coerced into any date type
253
if(dtype == TemplateDateModel.UNKNOWN || dtype == TemplateDateModel.DATETIME) {
254                     return new SimpleDate(dmodel.getAsDate(), dateType);
255                 }
256                 throw new TemplateException(
257                     "Cannot convert " + TemplateDateModel.TYPE_NAMES.get(dtype)
258                     + " into " + TemplateDateModel.TYPE_NAMES.get(dateType), env);
259             }
260             // Otherwise, interpret as a string and attempt
261
// to parse it into a date.
262
String JavaDoc s = target.getStringValue(env);
263             return new DateParser(s, env);
264         }
265         
266         private class DateParser
267         implements
268             TemplateDateModel,
269             TemplateMethodModel,
270             TemplateHashModel
271         {
272             private final String JavaDoc text;
273             private final Environment env;
274             private final DateFormat JavaDoc defaultFormat;
275             private Date JavaDoc cachedValue;
276             
277             DateParser(String JavaDoc text, Environment env)
278             throws
279                 TemplateModelException
280             {
281                 this.text = text;
282                 this.env = env;
283                 this.defaultFormat = env.getDateFormatObject(dateType);
284             }
285             
286             public Date JavaDoc getAsDate() throws TemplateModelException {
287                 if(cachedValue == null) {
288                     cachedValue = parse(defaultFormat);
289                 }
290                 return cachedValue;
291             }
292             
293             public int getDateType() {
294                 return dateType;
295             }
296
297             public TemplateModel get(String JavaDoc pattern) throws TemplateModelException {
298                 return new SimpleDate(
299                     parse(env.getDateFormatObject(dateType, pattern)),
300                     dateType);
301             }
302
303             public Object JavaDoc exec(List JavaDoc arguments)
304                 throws TemplateModelException {
305                 if (arguments.size() != 1) {
306                     throw new TemplateModelException(
307                             "string?" + key + "(...) requires exactly 1 argument.");
308                 }
309                 return get((String JavaDoc) arguments.get(0));
310             }
311
312             public boolean isEmpty()
313             {
314                 return false;
315             }
316
317             private Date JavaDoc parse(DateFormat JavaDoc df)
318             throws
319                 TemplateModelException
320             {
321                 try {
322                     return df.parse(text);
323                 }
324                 catch(java.text.ParseException JavaDoc e) {
325                     String JavaDoc mess = "Error: " + getStartLocation()
326                                  + "\nExpecting a date here, found: " + text;
327                     throw new TemplateModelException(mess);
328                 }
329             }
330         }
331     }
332
333     static class stringBI extends BuiltIn {
334         TemplateModel _getAsTemplateModel(Environment env)
335                 throws TemplateException
336         {
337             TemplateModel model = target.getAsTemplateModel(env);
338             if (model instanceof TemplateNumberModel) {
339                 return new NumberFormatter(EvaluationUtil.getNumber((TemplateNumberModel)model, target, env), env);
340             }
341             if (model instanceof TemplateDateModel) {
342                 TemplateDateModel dm = (TemplateDateModel)model;
343                 int dateType = dm.getDateType();
344                 return new DateFormatter(EvaluationUtil.getDate(dm, target, env), dateType, env);
345             }
346             if (model instanceof SimpleScalar) {
347                 return model;
348             }
349             if (model instanceof TemplateBooleanModel) {
350                 return new BooleanFormatter((TemplateBooleanModel) model, env);
351             }
352             if (model instanceof TemplateScalarModel) {
353                 return new SimpleScalar(((TemplateScalarModel) model).getAsString());
354             }
355             throw invalidTypeException(model, target, env, "number, date, or string");
356         }
357
358         private static class NumberFormatter
359         implements
360             TemplateScalarModel,
361             TemplateHashModel,
362             TemplateMethodModel
363         {
364             private final Number JavaDoc number;
365             private final Environment env;
366             private final NumberFormat JavaDoc defaultFormat;
367             private String JavaDoc cachedValue;
368
369             NumberFormatter(Number JavaDoc number, Environment env)
370             {
371                 this.number = number;
372                 this.env = env;
373                 defaultFormat = env.getNumberFormatObject(env.getNumberFormat());
374             }
375
376             public String JavaDoc getAsString()
377             {
378                 if(cachedValue == null) {
379                     cachedValue = defaultFormat.format(number);
380                 }
381                 return cachedValue;
382             }
383
384             public TemplateModel get(String JavaDoc key)
385             {
386                 return new SimpleScalar(env.getNumberFormatObject(key).format(number));
387             }
388             
389             public Object JavaDoc exec(List JavaDoc arguments)
390                 throws TemplateModelException {
391                 if (arguments.size() != 1) {
392                     throw new TemplateModelException(
393                             "number?string(...) requires exactly 1 argument.");
394                 }
395                 return get((String JavaDoc) arguments.get(0));
396             }
397
398             public boolean isEmpty()
399             {
400                 return false;
401             }
402         }
403         
404         private static class DateFormatter
405         implements
406             TemplateScalarModel,
407             TemplateHashModel,
408             TemplateMethodModel
409         {
410             private final Date JavaDoc date;
411             private final int dateType;
412             private final Environment env;
413             private final DateFormat JavaDoc defaultFormat;
414             private String JavaDoc cachedValue;
415
416             DateFormatter(Date JavaDoc date, int dateType, Environment env)
417             throws
418                 TemplateModelException
419             {
420                 this.date = date;
421                 this.dateType = dateType;
422                 this.env = env;
423                 defaultFormat = env.getDateFormatObject(dateType);
424             }
425
426             public String JavaDoc getAsString()
427             throws
428                 TemplateModelException
429             {
430                 if(dateType == TemplateDateModel.UNKNOWN) {
431                     throw new TemplateModelException("Can't convert the date to string, because it is not known which parts of the date variable are in use. Use ?date, ?time or ?datetime built-in, or ?string.<format> or ?string(format) built-in with this date.");
432                 }
433                 if(cachedValue == null) {
434                     cachedValue = defaultFormat.format(date);
435                 }
436                 return cachedValue;
437             }
438
439             public TemplateModel get(String JavaDoc key)
440             throws
441                 TemplateModelException
442             {
443                 return new SimpleScalar(env.getDateFormatObject(dateType, key).format(date));
444             }
445             
446             public Object JavaDoc exec(List JavaDoc arguments)
447                 throws TemplateModelException {
448                 if (arguments.size() != 1) {
449                     throw new TemplateModelException(
450                             "date?string(...) requires exactly 1 argument.");
451                 }
452                 return get((String JavaDoc) arguments.get(0));
453             }
454
455             public boolean isEmpty()
456             {
457                 return false;
458             }
459         }
460
461         private static class BooleanFormatter
462         implements
463             TemplateScalarModel,
464             TemplateMethodModel
465         {
466             private final TemplateBooleanModel bool;
467             private final Environment env;
468             
469             BooleanFormatter(TemplateBooleanModel bool, Environment env) {
470                 this.bool = bool;
471                 this.env = env;
472             }
473
474             public String JavaDoc getAsString() throws TemplateModelException {
475                 if (bool instanceof TemplateScalarModel) {
476                     return ((TemplateScalarModel) bool).getAsString();
477                 } else {
478                     return env.getBooleanFormat(bool.getAsBoolean());
479                 }
480             }
481
482             public Object JavaDoc exec(List JavaDoc arguments)
483                     throws TemplateModelException {
484                 if (arguments.size() != 2) {
485                     throw new TemplateModelException(
486                             "boolean?string(...) requires exactly "
487                             + "2 arguments.");
488                 }
489                 return new SimpleScalar(
490                     (String JavaDoc) arguments.get(bool.getAsBoolean() ? 0 : 1));
491             }
492         }
493     }
494
495     static class trimBI extends StringBuiltIn {
496         TemplateModel calculateResult(String JavaDoc s, Environment env) {
497             return new SimpleScalar(s.trim());
498         }
499     }
500
501     static class htmlBI extends StringBuiltIn {
502         TemplateModel calculateResult(String JavaDoc s, Environment env) {
503             return new SimpleScalar(StringUtil.HTMLEnc(s));
504         }
505     }
506
507     static class xmlBI extends StringBuiltIn {
508         TemplateModel calculateResult(String JavaDoc s, Environment env) {
509             return new SimpleScalar(StringUtil.XMLEnc(s));
510         }
511     }
512
513     static class rtfBI extends StringBuiltIn {
514         TemplateModel calculateResult(String JavaDoc s, Environment env) {
515             return new SimpleScalar(StringUtil.RTFEnc(s));
516         }
517     }
518
519     static class urlBI extends StringBuiltIn {
520         
521         TemplateModel calculateResult(String JavaDoc s, Environment env) {
522             return new urlBIResult(s, env);
523         }
524         
525         static class urlBIResult implements
526                 TemplateScalarModel, TemplateMethodModel {
527             
528             private final String JavaDoc target;
529             private final Environment env;
530             private String JavaDoc cachedResult;
531
532             private urlBIResult(String JavaDoc target, Environment env) {
533                 this.target = target;
534                 this.env = env;
535             }
536             
537             public String JavaDoc getAsString() throws TemplateModelException {
538                 if (cachedResult == null) {
539                     String JavaDoc cs = env.getEffectiveURLEscapingCharset();
540                     if (cs == null) {
541                         throw new TemplateModelException(
542                                 "To do URL encoding, the framework that encloses "
543                                 + "FreeMarker must specify the output encoding "
544                                 + "or the URL encoding charset, so ask the "
545                                 + "programmers to fix it. Or, as a last chance, "
546                                 + "you can set the url_encoding_charset setting in "
547                                 + "the template, e.g. "
548                                 + "<#setting url_escaping_charset='ISO-8859-1'>, or "
549                                 + "give the charset explicitly to the buit-in, e.g. "
550                                 + "foo?url('ISO-8859-1').");
551                     }
552                     try {
553                         cachedResult = StringUtil.URLEnc(target, cs);
554                     } catch (UnsupportedEncodingException JavaDoc e) {
555                         throw new TemplateModelException(
556                                 "Failed to execute URL encoding.", e);
557                     }
558                 }
559                 return cachedResult;
560             }
561
562             public Object JavaDoc exec(List JavaDoc args) throws TemplateModelException {
563                 if (args.size() != 1) {
564                     throw new TemplateModelException("The \"url\" built-in "
565                             + "needs exactly 1 parameter, the charset.");
566                 }
567                 try {
568                     return new SimpleScalar(
569                             StringUtil.URLEnc(target, (String JavaDoc) args.get(0)));
570                 } catch (UnsupportedEncodingException JavaDoc e) {
571                     throw new TemplateModelException(
572                             "Failed to execute URL encoding.", e);
573                 }
574             }
575             
576         }
577     }
578     
579     static class keysBI extends BuiltIn {
580         TemplateModel _getAsTemplateModel(Environment env)
581                 throws TemplateException
582         {
583             TemplateModel model = target.getAsTemplateModel(env);
584             if (model instanceof TemplateHashModelEx) {
585                 TemplateCollectionModel keys = ((TemplateHashModelEx) model).keys();
586                 assertNonNull(keys, this, env);
587                 if (!(keys instanceof TemplateSequenceModel))
588                     keys = new CollectionAndSequence(keys);
589                 return keys;
590             }
591             throw invalidTypeException(model, target, env, "extended hash");
592         }
593     }
594
595     static class valuesBI extends BuiltIn {
596         TemplateModel _getAsTemplateModel(Environment env)
597                 throws TemplateException
598         {
599             TemplateModel model = target.getAsTemplateModel(env);
600             if (model instanceof TemplateHashModelEx) {
601                 TemplateCollectionModel values = ((TemplateHashModelEx) model).values();
602                 assertNonNull(values, this, env);
603                 if (!(values instanceof TemplateSequenceModel))
604                     values = new CollectionAndSequence(values);
605                 return values;
606             }
607             throw invalidTypeException(model, target, env, "extended hash");
608         }
609     }
610
611     static class sizeBI extends BuiltIn {
612         TemplateModel _getAsTemplateModel(Environment env)
613                 throws TemplateException
614         {
615             TemplateModel model = target.getAsTemplateModel(env);
616             if (model instanceof TemplateSequenceModel) {
617                 int size = ((TemplateSequenceModel) model).size();
618                 return new SimpleNumber(size);
619             }
620             if (model instanceof TemplateHashModelEx) {
621                 int size = ((TemplateHashModelEx) model).size();
622                 return new SimpleNumber(size);
623             }
624             throw invalidTypeException(model, target, env, "extended hash or sequence");
625         }
626     }
627
628     static class existsBI extends BuiltIn {
629         TemplateModel _getAsTemplateModel(Environment env)
630                 throws TemplateException
631         {
632             try {
633                 TemplateModel model = target.getAsTemplateModel(env);
634                 return model==null ? TemplateBooleanModel.FALSE : TemplateBooleanModel.TRUE;
635             } catch (InvalidReferenceException ire) {
636                 if (target instanceof ParentheticalExpression) {
637                     return TemplateBooleanModel.FALSE;
638                 }
639                 throw ire;
640             }
641         }
642
643         boolean isTrue(Environment env) throws TemplateException {
644             return _getAsTemplateModel(env) == TemplateBooleanModel.TRUE;
645         }
646     }
647
648     static class has_contentBI extends BuiltIn {
649         TemplateModel _getAsTemplateModel(Environment env)
650                 throws TemplateException
651         {
652             try {
653                 TemplateModel model = target.getAsTemplateModel(env);
654                 return Expression.isEmpty(model) ?
655                     TemplateBooleanModel.FALSE : TemplateBooleanModel.TRUE;
656             } catch (InvalidReferenceException ire) {
657                 if (target instanceof ParentheticalExpression) {
658                     return TemplateBooleanModel.FALSE;
659                 }
660                 throw ire;
661             }
662         }
663
664         boolean isTrue(Environment env) throws TemplateException {
665             return _getAsTemplateModel(env) == TemplateBooleanModel.TRUE;
666         }
667     }
668
669     static class if_existsBI extends BuiltIn {
670         TemplateModel _getAsTemplateModel(Environment env)
671                 throws TemplateException
672         {
673             try {
674                 TemplateModel model = target.getAsTemplateModel(env);
675                 return model == null ? TemplateModel.NOTHING : model;
676             } catch (InvalidReferenceException ire) {
677                 if (target instanceof ParentheticalExpression) {
678                     return TemplateModel.NOTHING;
679                 }
680                 throw ire;
681             }
682         }
683     }
684
685     static class is_stringBI extends BuiltIn {
686         TemplateModel _getAsTemplateModel(Environment env) throws TemplateException {
687             TemplateModel tm = target.getAsTemplateModel(env);
688             assertNonNull(tm, target, env);
689             return (tm instanceof TemplateScalarModel) ?
690                 TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
691         }
692     }
693
694     static class is_numberBI extends BuiltIn {
695         TemplateModel _getAsTemplateModel(Environment env) throws TemplateException {
696             TemplateModel tm = target.getAsTemplateModel(env);
697             assertNonNull(tm, target, env);
698             return (tm instanceof TemplateNumberModel) ?
699                 TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
700         }
701     }
702
703     static class is_nodeBI extends BuiltIn {
704         TemplateModel _getAsTemplateModel(Environm