blob: 3963928d7975a7f0f84d6e517c5716ccaf0276a2 [file] [log] [blame]
/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import freemarker.core.DateBuiltins.iso_BI;
import freemarker.core.DateBuiltins.iso_tz_BI;
import freemarker.core.NodeBuiltins.ancestorsBI;
import freemarker.core.NodeBuiltins.childrenBI;
import freemarker.core.NodeBuiltins.node_nameBI;
import freemarker.core.NodeBuiltins.node_namespaceBI;
import freemarker.core.NodeBuiltins.node_typeBI;
import freemarker.core.NodeBuiltins.parentBI;
import freemarker.core.NodeBuiltins.rootBI;
import freemarker.core.NumericalBuiltins.absBI;
import freemarker.core.NumericalBuiltins.byteBI;
import freemarker.core.NumericalBuiltins.ceilingBI;
import freemarker.core.NumericalBuiltins.doubleBI;
import freemarker.core.NumericalBuiltins.floatBI;
import freemarker.core.NumericalBuiltins.floorBI;
import freemarker.core.NumericalBuiltins.intBI;
import freemarker.core.NumericalBuiltins.is_infiniteBI;
import freemarker.core.NumericalBuiltins.is_nanBI;
import freemarker.core.NumericalBuiltins.longBI;
import freemarker.core.NumericalBuiltins.number_to_dateBI;
import freemarker.core.NumericalBuiltins.roundBI;
import freemarker.core.NumericalBuiltins.shortBI;
import freemarker.core.SequenceBuiltins.chunkBI;
import freemarker.core.SequenceBuiltins.firstBI;
import freemarker.core.SequenceBuiltins.lastBI;
import freemarker.core.SequenceBuiltins.reverseBI;
import freemarker.core.SequenceBuiltins.seq_containsBI;
import freemarker.core.SequenceBuiltins.seq_index_ofBI;
import freemarker.core.SequenceBuiltins.sortBI;
import freemarker.core.SequenceBuiltins.sort_byBI;
import freemarker.core.StringBuiltins.cap_firstBI;
import freemarker.core.StringBuiltins.capitalizeBI;
import freemarker.core.StringBuiltins.chop_linebreakBI;
import freemarker.core.StringBuiltins.evalBI;
import freemarker.core.StringBuiltins.j_stringBI;
import freemarker.core.StringBuiltins.js_stringBI;
import freemarker.core.StringBuiltins.json_stringBI;
import freemarker.core.StringBuiltins.lower_caseBI;
import freemarker.core.StringBuiltins.numberBI;
import freemarker.core.StringBuiltins.substringBI;
import freemarker.core.StringBuiltins.uncap_firstBI;
import freemarker.core.StringBuiltins.upper_caseBI;
import freemarker.core.StringBuiltins.word_listBI;
import freemarker.log.Logger;
import freemarker.template.Configuration;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.utility.DateUtil;
import freemarker.template.utility.StringUtil;
/**
* The {@code ?} operator used for things like {@code foo?upper_case}.
* @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
*/
abstract class BuiltIn extends Expression implements Cloneable {
private static final Logger logger = Logger.getLogger("freemarker.runtime");
protected Expression target;
protected String key;
static final HashMap builtins = new HashMap();
static {
builtins.put("abs", new absBI());
builtins.put("ancestors", new ancestorsBI());
builtins.put("byte", new byteBI());
builtins.put("c", new MiscellaneousBuiltins.cBI());
builtins.put("cap_first", new cap_firstBI());
builtins.put("capitalize", new capitalizeBI());
builtins.put("ceiling", new ceilingBI());
builtins.put("children", new childrenBI());
builtins.put("chop_linebreak", new chop_linebreakBI());
builtins.put("contains", new StringBuiltins.containsBI());
builtins.put("date", new MiscellaneousBuiltins.dateBI(TemplateDateModel.DATE));
builtins.put("datetime", new MiscellaneousBuiltins.dateBI(TemplateDateModel.DATETIME));
builtins.put("default", new ExistenceBuiltins.defaultBI());
builtins.put("double", new doubleBI());
builtins.put("ends_with", new StringBuiltins.ends_withBI());
builtins.put("eval", new evalBI());
builtins.put("exists", new ExistenceBuiltins.existsBI());
builtins.put("first", new firstBI());
builtins.put("float", new floatBI());
builtins.put("floor", new floorBI());
builtins.put("chunk", new chunkBI());
builtins.put("has_content", new ExistenceBuiltins.has_contentBI());
builtins.put("html", new StringBuiltins.htmlBI());
builtins.put("if_exists", new ExistenceBuiltins.if_existsBI());
builtins.put("index_of", new StringBuiltins.index_ofBI(false));
builtins.put("int", new intBI());
builtins.put("interpret", new Interpret());
builtins.put("is_boolean", new MiscellaneousBuiltins.is_booleanBI());
builtins.put("is_collection", new MiscellaneousBuiltins.is_collectionBI());
builtins.put("is_date", new MiscellaneousBuiltins.is_dateBI());
builtins.put("is_directive", new MiscellaneousBuiltins.is_directiveBI());
builtins.put("is_enumerable", new MiscellaneousBuiltins.is_enumerableBI());
builtins.put("is_hash_ex", new MiscellaneousBuiltins.is_hash_exBI());
builtins.put("is_hash", new MiscellaneousBuiltins.is_hashBI());
builtins.put("is_infinite", new is_infiniteBI());
builtins.put("is_indexable", new MiscellaneousBuiltins.is_indexableBI());
builtins.put("is_macro", new MiscellaneousBuiltins.is_macroBI());
builtins.put("is_method", new MiscellaneousBuiltins.is_methodBI());
builtins.put("is_nan", new is_nanBI());
builtins.put("is_node", new MiscellaneousBuiltins.is_nodeBI());
builtins.put("is_number", new MiscellaneousBuiltins.is_numberBI());
builtins.put("is_sequence", new MiscellaneousBuiltins.is_sequenceBI());
builtins.put("is_string", new MiscellaneousBuiltins.is_stringBI());
builtins.put("is_transform", new MiscellaneousBuiltins.is_transformBI());
builtins.put("iso_utc", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
builtins.put("iso_utc_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
builtins.put("iso_utc_ms", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true));
builtins.put("iso_utc_ms_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ true));
builtins.put("iso_utc_m", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true));
builtins.put("iso_utc_m_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MINUTES, /* useUTC = */ true));
builtins.put("iso_utc_h", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_HOURS, /* useUTC = */ true));
builtins.put("iso_utc_h_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_HOURS, /* useUTC = */ true));
builtins.put("iso_local", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false));
builtins.put("iso_local_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_SECONDS, /* useUTC = */ false));
builtins.put("iso_local_ms", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false));
builtins.put("iso_local_ms_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MILLISECONDS, /* useUTC = */ false));
builtins.put("iso_local_m", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false));
builtins.put("iso_local_m_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MINUTES, /* useUTC = */ false));
builtins.put("iso_local_h", new iso_tz_BI(
/* showOffset = */ true, DateUtil.ACCURACY_HOURS, /* useUTC = */ false));
builtins.put("iso_local_h_nz", new iso_tz_BI(
/* showOffset = */ false, DateUtil.ACCURACY_HOURS, /* useUTC = */ false));
builtins.put("iso", new iso_BI(
/* showOffset = */ true, DateUtil.ACCURACY_SECONDS));
builtins.put("iso_nz", new iso_BI(
/* showOffset = */ false, DateUtil.ACCURACY_SECONDS));
builtins.put("iso_ms", new iso_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MILLISECONDS));
builtins.put("iso_ms_nz", new iso_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MILLISECONDS));
builtins.put("iso_m", new iso_BI(
/* showOffset = */ true, DateUtil.ACCURACY_MINUTES));
builtins.put("iso_m_nz", new iso_BI(
/* showOffset = */ false, DateUtil.ACCURACY_MINUTES));
builtins.put("iso_h", new iso_BI(
/* showOffset = */ true, DateUtil.ACCURACY_HOURS));
builtins.put("iso_h_nz", new iso_BI(
/* showOffset = */ false, DateUtil.ACCURACY_HOURS));
builtins.put("j_string", new j_stringBI());
builtins.put("join", new SequenceBuiltins.joinBI());
builtins.put("js_string", new js_stringBI());
builtins.put("json_string", new json_stringBI());
builtins.put("keys", new HashBuiltins.keysBI());
builtins.put("last_index_of", new StringBuiltins.index_ofBI(true));
builtins.put("last", new lastBI());
builtins.put("left_pad", new StringBuiltins.padBI(true));
builtins.put("length", new StringBuiltins.lengthBI());
builtins.put("long", new longBI());
builtins.put("lower_case", new lower_caseBI());
builtins.put("namespace", new MiscellaneousBuiltins.namespaceBI());
builtins.put("new", new NewBI());
builtins.put("node_name", new node_nameBI());
builtins.put("node_namespace", new node_namespaceBI());
builtins.put("node_type", new node_typeBI());
builtins.put("number", new numberBI());
builtins.put("number_to_date", new number_to_dateBI(TemplateDateModel.DATE));
builtins.put("number_to_time", new number_to_dateBI(TemplateDateModel.TIME));
builtins.put("number_to_datetime", new number_to_dateBI(TemplateDateModel.DATETIME));
builtins.put("parent", new parentBI());
builtins.put("replace", new StringBuiltins.replaceBI());
builtins.put("reverse", new reverseBI());
builtins.put("right_pad", new StringBuiltins.padBI(false));
builtins.put("root", new rootBI());
builtins.put("round", new roundBI());
builtins.put("rtf", new StringBuiltins.rtfBI());
builtins.put("seq_contains", new seq_containsBI());
builtins.put("seq_index_of", new seq_index_ofBI(1));
builtins.put("seq_last_index_of", new seq_index_ofBI(-1));
builtins.put("short", new shortBI());
builtins.put("size", new MiscellaneousBuiltins.sizeBI());
builtins.put("sort_by", new sort_byBI());
builtins.put("sort", new sortBI());
builtins.put("split", new StringBuiltins.splitBI());
builtins.put("starts_with", new StringBuiltins.starts_withBI());
builtins.put("string", new MiscellaneousBuiltins.stringBI());
builtins.put("substring", new substringBI());
builtins.put("time", new MiscellaneousBuiltins.dateBI(TemplateDateModel.TIME));
builtins.put("trim", new StringBuiltins.trimBI());
builtins.put("uncap_first", new uncap_firstBI());
builtins.put("upper_case", new upper_caseBI());
builtins.put("url", new StringBuiltins.urlBI());
builtins.put("values", new HashBuiltins.valuesBI());
builtins.put("web_safe", builtins.get("html")); // deprecated; use ?html instead
builtins.put("word_list", new word_listBI());
builtins.put("xhtml", new StringBuiltins.xhtmlBI());
builtins.put("xml", new StringBuiltins.xmlBI());
try {
Class.forName("java.util.regex.Pattern");
builtins.put("matches", instantiateBI("freemarker.core._RegexBuiltins$matchesBI"));
builtins.put("groups", instantiateBI("freemarker.core._RegexBuiltins$groupsBI"));
builtins.put("replace", instantiateBI("freemarker.core._RegexBuiltins$replace_reBI"));
builtins.put("split", instantiateBI("freemarker.core._RegexBuiltins$split_reBI"));
} catch (Exception e) {
logger.debug("Regular expression built-ins won't be avilable", e);
}
}
private static Object instantiateBI(String className) throws Exception
{
return Class.forName(className).newInstance();
}
static BuiltIn newBuiltIn(int incompatibleImprovements, Expression target, String key) throws ParseException {
BuiltIn bi = (BuiltIn) builtins.get(key);
if (bi == null) {
StringBuffer buf = new StringBuffer(
"Unknown built-in: " + StringUtil.jQuote(key) + ". "
+ "Help (latest version): http://freemarker.org/docs/ref_builtins.html; "
+ "you're using FreeMarker " + Configuration.getVersion() + ".\n"
+ "The alphabetical list of built-ins:");
List names = new ArrayList(builtins.keySet().size());
names.addAll(builtins.keySet());
Collections.sort(names);
char lastLetter = 0;
for (Iterator it = names.iterator(); it.hasNext();) {
String name = (String) it.next();
char firstChar = name.charAt(0);
if (firstChar != lastLetter) {
lastLetter = firstChar;
buf.append('\n');
}
buf.append(name);
if (it.hasNext()) {
buf.append(", ");
}
}
throw new ParseException(buf.toString(), target);
}
while (bi instanceof ICIChainMember
&& incompatibleImprovements < ((ICIChainMember) bi).getMinimumICIVersion()) {
bi = (BuiltIn) ((ICIChainMember) bi).getPreviousICIChainMember();
}
try {
bi = (BuiltIn) bi.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalError();
}
bi.target = target;
bi.key = key;
return bi;
}
public String getCanonicalForm() {
return target.getCanonicalForm() + getNodeTypeSymbol();
}
String getNodeTypeSymbol() {
return "?" + key;
}
boolean isLiteral() {
return false; // be on the safe side.
}
protected final void checkMethodArgCount(List args, int expectedCnt) throws TemplateModelException {
checkMethodArgCount(args.size(), expectedCnt);
}
protected final void checkMethodArgCount(int argCnt, int expectedCnt) throws TemplateModelException {
if (argCnt != expectedCnt) {
throw MessageUtil.newArgCntError("?" + key, argCnt, expectedCnt);
}
}
protected final void checkMethodArgCount(List args, int minCnt, int maxCnt) throws TemplateModelException {
checkMethodArgCount(args.size(), minCnt, maxCnt);
}
protected final void checkMethodArgCount(int argCnt, int minCnt, int maxCnt) throws TemplateModelException {
if (argCnt < minCnt || argCnt > maxCnt) {
throw MessageUtil.newArgCntError("?" + key, argCnt, minCnt, maxCnt);
}
}
/**
* Same as {@link #getStringMethodArg}, but checks if {@code args} is big enough, and returns {@code null} if it
* isn't.
*/
protected final String getOptStringMethodArg(List args, int argIdx)
throws TemplateModelException {
return args.size() > argIdx ? getStringMethodArg(args, argIdx) : null;
}
/**
* Gets a method argument and checks if it's a string; it does NOT check if {@code args} is big enough.
*/
protected final String getStringMethodArg(List args, int argIdx)
throws TemplateModelException {
TemplateModel arg = (TemplateModel) args.get(argIdx);
if (!(arg instanceof TemplateScalarModel)) {
throw MessageUtil.newMethodArgMustBeStringException("?" + key, argIdx, arg);
} else {
return EvalUtil.modelToString((TemplateScalarModel) arg, null, null);
}
}
/**
* Gets a method argument and checks if it's a number; it does NOT check if {@code args} is big enough.
*/
protected final Number getNumberMethodArg(List args, int argIdx)
throws TemplateModelException {
TemplateModel arg = (TemplateModel) args.get(argIdx);
if (!(arg instanceof TemplateNumberModel)) {
throw MessageUtil.newMethodArgMustBeNumberException("?" + key, argIdx, arg);
} else {
return EvalUtil.modelToNumber((TemplateNumberModel) arg, null);
}
}
protected final Expression deepCloneWithIdentifierReplaced_inner(
String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
try {
BuiltIn clone = (BuiltIn)clone();
clone.target = target.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState);
return clone;
}
catch (CloneNotSupportedException e) {
throw new RuntimeException("Internal error: " + e);
}
}
int getParameterCount() {
return 2;
}
Object getParameterValue(int idx) {
switch (idx) {
case 0: return target;
case 1: return key;
default: throw new IndexOutOfBoundsException();
}
}
ParameterRole getParameterRole(int idx) {
switch (idx) {
case 0: return ParameterRole.LEFT_HAND_OPERAND;
case 1: return ParameterRole.RIGHT_HAND_OPERAND;
default: throw new IndexOutOfBoundsException();
}
}
}