http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java b/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java new file mode 100644 index 0000000..162ec1e --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirOutputFormat.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +/** + * AST directive node: {@code #outputformat}. + */ +final class ASTDirOutputFormat extends _ASTElement { + + private final ASTExpression paramExp; + + ASTDirOutputFormat(TemplateElements children, ASTExpression paramExp) { + this.paramExp = paramExp; + setChildren(children); + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException, IOException { + return getChildBuffer(); + } + + @Override + protected String dump(boolean canonical) { + if (canonical) { + return "<" + getNodeTypeSymbol() + " \"" + paramExp.getCanonicalForm() + "\">" + + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">"; + } else { + return getNodeTypeSymbol(); + } + } + + @Override + String getNodeTypeSymbol() { + return "#outputformat"; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx == 0) return paramExp; + else + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx == 0) return ParameterRole.VALUE; + else + throw new IndexOutOfBoundsException(); + } + + @Override + boolean isIgnorable(boolean stripWhitespace) { + return getChildCount() == 0; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirRecover.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirRecover.java b/src/main/java/org/apache/freemarker/core/ASTDirRecover.java new file mode 100644 index 0000000..33ef932 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirRecover.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +/** + * AST directive node: {@code #recover}. + */ +final class ASTDirRecover extends _ASTElement { + + ASTDirRecover(TemplateElements children) { + setChildren(children); + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException, IOException { + return getChildBuffer(); + } + + @Override + protected String dump(boolean canonical) { + if (canonical) { + StringBuilder buf = new StringBuilder(); + buf.append('<').append(getNodeTypeSymbol()).append('>'); + buf.append(getChildrenCanonicalForm()); + return buf.toString(); + } else { + return getNodeTypeSymbol(); + } + } + + @Override + String getNodeTypeSymbol() { + return "#recover"; + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java b/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java new file mode 100644 index 0000000..8597e92 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +import org.apache.freemarker.core.model.TemplateHashModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNodeModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.SimpleSequence; + + +/** + * AST directive node: {@code #recurse}. + */ +final class ASTDirRecurse extends _ASTElement { + + ASTExpression targetNode, namespaces; + + ASTDirRecurse(ASTExpression targetNode, ASTExpression namespaces) { + this.targetNode = targetNode; + this.namespaces = namespaces; + } + + @Override + _ASTElement[] accept(Environment env) throws IOException, TemplateException { + TemplateModel node = targetNode == null ? null : targetNode.eval(env); + if (node != null && !(node instanceof TemplateNodeModel)) { + throw new NonNodeException(targetNode, node, "node", env); + } + + TemplateModel nss = namespaces == null ? null : namespaces.eval(env); + if (namespaces instanceof ASTExpStringLiteral) { + nss = env.importLib(((TemplateScalarModel) nss).getAsString(), null); + } else if (namespaces instanceof ASTExpListLiteral) { + nss = ((ASTExpListLiteral) namespaces).evaluateStringsToNamespaces(env); + } + if (nss != null) { + if (nss instanceof TemplateHashModel) { + SimpleSequence ss = new SimpleSequence(1); + ss.add(nss); + nss = ss; + } else if (!(nss instanceof TemplateSequenceModel)) { + if (namespaces != null) { + throw new NonSequenceException(namespaces, nss, env); + } else { + // Should not occur + throw new _MiscTemplateException(env, "Expecting a sequence of namespaces after \"using\""); + } + } + } + + env.recurse((TemplateNodeModel) node, (TemplateSequenceModel) nss); + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (targetNode != null) { + sb.append(' '); + sb.append(targetNode.getCanonicalForm()); + } + if (namespaces != null) { + sb.append(" using "); + sb.append(namespaces.getCanonicalForm()); + } + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#recurse"; + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return targetNode; + case 1: return namespaces; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.NODE; + case 1: return ParameterRole.NAMESPACE; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + + @Override + boolean isShownInStackTrace() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirReturn.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirReturn.java b/src/main/java/org/apache/freemarker/core/ASTDirReturn.java new file mode 100644 index 0000000..afa7d56 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirReturn.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +/** + * AST directive node: {@code #return}. + */ +public final class ASTDirReturn extends _ASTElement { + + private ASTExpression exp; + + ASTDirReturn(ASTExpression exp) { + this.exp = exp; + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException { + if (exp != null) { + env.setLastReturnValue(exp.eval(env)); + } + if (nextSibling() == null && getParentElement() instanceof ASTDirMacro) { + // Avoid unnecessary exception throwing + return null; + } + throw Return.INSTANCE; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (exp != null) { + sb.append(' '); + sb.append(exp.getCanonicalForm()); + } + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#return"; + } + + public static class Return extends RuntimeException { + static final Return INSTANCE = new Return(); + private Return() { + } + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return exp; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.VALUE; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirSep.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirSep.java b/src/main/java/org/apache/freemarker/core/ASTDirSep.java new file mode 100644 index 0000000..fe181ab --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirSep.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import java.io.IOException; + +import org.apache.freemarker.core.ASTDirList.IterationContext; + +/** + * AST directive node: {@code #sep}. + */ +class ASTDirSep extends _ASTElement { + + public ASTDirSep(TemplateElements children) { + setChildren(children); + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException, IOException { + final IterationContext iterCtx = ASTDirList.findEnclosingIterationContext(env, null); + if (iterCtx == null) { + // The parser should prevent this situation + throw new _MiscTemplateException(env, + getNodeTypeSymbol(), " without iteration in context"); + } + + if (iterCtx.hasNext()) { + return getChildBuffer(); + } + return null; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (canonical) { + sb.append('>'); + sb.append(getChildrenCanonicalForm()); + sb.append("</"); + sb.append(getNodeTypeSymbol()); + sb.append('>'); + } + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#sep"; + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirSetting.java b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java new file mode 100644 index 0000000..5554599 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirSetting.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.util.Arrays; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.util._StringUtil; + +/** + * AST directive node: {@code #setting}. + */ +final class ASTDirSetting extends _ASTElement { + + private final String key; + private final ASTExpression value; + + static final String[] SETTING_NAMES = new String[] { + // Must be sorted alphabetically! + Configurable.BOOLEAN_FORMAT_KEY_CAMEL_CASE, + Configurable.BOOLEAN_FORMAT_KEY_SNAKE_CASE, + Configurable.DATE_FORMAT_KEY_CAMEL_CASE, + Configurable.DATE_FORMAT_KEY_SNAKE_CASE, + Configurable.DATETIME_FORMAT_KEY_CAMEL_CASE, + Configurable.DATETIME_FORMAT_KEY_SNAKE_CASE, + Configurable.LOCALE_KEY, + Configurable.NUMBER_FORMAT_KEY_CAMEL_CASE, + Configurable.NUMBER_FORMAT_KEY_SNAKE_CASE, + Configurable.OUTPUT_ENCODING_KEY_CAMEL_CASE, + Configurable.OUTPUT_ENCODING_KEY_SNAKE_CASE, + Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE, + Configurable.SQL_DATE_AND_TIME_TIME_ZONE_KEY, + Configurable.TIME_FORMAT_KEY_CAMEL_CASE, + Configurable.TIME_ZONE_KEY_CAMEL_CASE, + Configurable.TIME_FORMAT_KEY_SNAKE_CASE, + Configurable.TIME_ZONE_KEY_SNAKE_CASE, + Configurable.URL_ESCAPING_CHARSET_KEY_CAMEL_CASE, + Configurable.URL_ESCAPING_CHARSET_KEY_SNAKE_CASE + }; + + ASTDirSetting(Token keyTk, FMParserTokenManager tokenManager, ASTExpression value, Configuration cfg) + throws ParseException { + String key = keyTk.image; + if (Arrays.binarySearch(SETTING_NAMES, key) < 0) { + StringBuilder sb = new StringBuilder(); + if (_TemplateAPI.getConfigurationSettingNames(cfg, true).contains(key) + || _TemplateAPI.getConfigurationSettingNames(cfg, false).contains(key)) { + sb.append("The setting name is recognized, but changing this setting from inside a template isn't " + + "supported."); + } else { + sb.append("Unknown setting name: "); + sb.append(_StringUtil.jQuote(key)).append("."); + sb.append(" The allowed setting names are: "); + + int shownNamingConvention; + { + int namingConvention = tokenManager.namingConvention; + shownNamingConvention = namingConvention != Configuration.AUTO_DETECT_NAMING_CONVENTION + ? namingConvention : Configuration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */; + } + + boolean first = true; + for (String correctName : SETTING_NAMES) { + int correctNameNamingConvention = _StringUtil.getIdentifierNamingConvention(correctName); + if (shownNamingConvention == Configuration.CAMEL_CASE_NAMING_CONVENTION + ? correctNameNamingConvention != Configuration.LEGACY_NAMING_CONVENTION + : correctNameNamingConvention != Configuration.CAMEL_CASE_NAMING_CONVENTION) { + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(correctName); + } + } + } + throw new ParseException(sb.toString(), null, keyTk); + } + + this.key = key; + this.value = value; + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException { + TemplateModel mval = value.eval(env); + String strval; + if (mval instanceof TemplateScalarModel) { + strval = ((TemplateScalarModel) mval).getAsString(); + } else if (mval instanceof TemplateBooleanModel) { + strval = ((TemplateBooleanModel) mval).getAsBoolean() ? "true" : "false"; + } else if (mval instanceof TemplateNumberModel) { + strval = ((TemplateNumberModel) mval).getAsNumber().toString(); + } else { + strval = value.evalAndCoerceToStringOrUnsupportedMarkup(env); + } + try { + env.setSetting(key, strval); + } catch (ConfigurationException e) { + throw new _MiscTemplateException(env, e.getMessage(), e.getCause()); + } + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + sb.append(' '); + sb.append(_StringUtil.toFTLTopLevelTragetIdentifier(key)); + sb.append('='); + sb.append(value.getCanonicalForm()); + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#setting"; + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return key; + case 1: return value; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.ITEM_KEY; + case 1: return ParameterRole.ITEM_VALUE; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirStop.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirStop.java b/src/main/java/org/apache/freemarker/core/ASTDirStop.java new file mode 100644 index 0000000..54397b4 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirStop.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +/** + * AST directive node: {@code #stop}. + */ +final class ASTDirStop extends _ASTElement { + + private ASTExpression exp; + + ASTDirStop(ASTExpression exp) { + this.exp = exp; + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException { + if (exp == null) { + throw new StopException(env); + } + throw new StopException(env, exp.evalAndCoerceToPlainText(env)); + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (exp != null) { + sb.append(' '); + sb.append(exp.getCanonicalForm()); + } + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#stop"; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return exp; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.MESSAGE; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java b/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java new file mode 100644 index 0000000..de56bf3 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +/** + * AST directive node: {@code #switch}. + */ +final class ASTDirSwitch extends _ASTElement { + + private ASTDirCase defaultCase; + private final ASTExpression searched; + + /** + * @param searched the expression to be tested. + */ + ASTDirSwitch(ASTExpression searched) { + this.searched = searched; + setChildBufferCapacity(4); + } + + void addCase(ASTDirCase cas) { + if (cas.condition == null) { + defaultCase = cas; + } + addChild(cas); + } + + @Override + _ASTElement[] accept(Environment env) + throws TemplateException, IOException { + boolean processedCase = false; + int ln = getChildCount(); + try { + for (int i = 0; i < ln; i++) { + ASTDirCase cas = (ASTDirCase) getChild(i); + boolean processCase = false; + + // Fall through if a previous case tested true. + if (processedCase) { + processCase = true; + } else if (cas.condition != null) { + // Otherwise, if this case isn't the default, test it. + processCase = EvalUtil.compare( + searched, + EvalUtil.CMP_OP_EQUALS, "case==", cas.condition, cas.condition, env); + } + if (processCase) { + env.visit(cas); + processedCase = true; + } + } + + // If we didn't process any nestedElements, and we have a default, + // process it. + if (!processedCase && defaultCase != null) { + env.visit(defaultCase); + } + } catch (ASTDirBreak.Break br) {} + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder buf = new StringBuilder(); + if (canonical) buf.append('<'); + buf.append(getNodeTypeSymbol()); + buf.append(' '); + buf.append(searched.getCanonicalForm()); + if (canonical) { + buf.append('>'); + int ln = getChildCount(); + for (int i = 0; i < ln; i++) { + ASTDirCase cas = (ASTDirCase) getChild(i); + buf.append(cas.getCanonicalForm()); + } + buf.append("</").append(getNodeTypeSymbol()).append('>'); + } + return buf.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#switch"; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return searched; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.VALUE; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java b/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java new file mode 100644 index 0000000..c5f2182 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +/** + * AST directive node: {@code #t}, {@code #tr}, {@code #tl}. + */ +final class ASTDirTOrTrOrTl extends _ASTElement { + + private final int TYPE_T = 0; + private final int TYPE_LT = 1; + private final int TYPE_RT = 2; + private final int TYPE_NT = 3; + + final boolean left, right; + + ASTDirTOrTrOrTl(boolean left, boolean right) { + this.left = left; + this.right = right; + } + + @Override + _ASTElement[] accept(Environment env) { + // This instruction does nothing at render-time, only parse-time. + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + if (left && right) { + return "#t"; + } else if (left) { + return "#lt"; + } else if (right) { + return "#rt"; + } else { + return "#nt"; + } + } + + @Override + boolean isIgnorable(boolean stripWhitespace) { + return true; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + int type; + if (left && right) { + type = TYPE_T; + } else if (left) { + type = TYPE_LT; + } else if (right) { + type = TYPE_RT; + } else { + type = TYPE_NT; + } + return Integer.valueOf(type); + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.AST_NODE_SUBTYPE; + } + + @Override + boolean isOutputCacheable() { + return true; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirTransform.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirTransform.java b/src/main/java/org/apache/freemarker/core/ASTDirTransform.java new file mode 100644 index 0000000..cd22b97 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirTransform.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateTransformModel; + +/** + * AST directive node: {@code #transform}. + */ +final class ASTDirTransform extends _ASTElement { + + private ASTExpression transformExpression; + Map namedArgs; + private transient volatile SoftReference/*List<Map.Entry<String,ASTExpression>>*/ sortedNamedArgsCache; + + ASTDirTransform(ASTExpression transformExpression, + Map namedArgs, + TemplateElements children) { + this.transformExpression = transformExpression; + this.namedArgs = namedArgs; + setChildren(children); + } + + @Override + _ASTElement[] accept(Environment env) + throws TemplateException, IOException { + TemplateTransformModel ttm = env.getTransform(transformExpression); + if (ttm != null) { + Map args; + if (namedArgs != null && !namedArgs.isEmpty()) { + args = new HashMap(); + for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext(); ) { + Map.Entry entry = (Map.Entry) it.next(); + String key = (String) entry.getKey(); + ASTExpression valueExp = (ASTExpression) entry.getValue(); + TemplateModel value = valueExp.eval(env); + args.put(key, value); + } + } else { + args = Collections.emptyMap(); + } + env.visitAndTransform(getChildBuffer(), ttm, args); + } else { + TemplateModel tm = transformExpression.eval(env); + throw new UnexpectedTypeException( + transformExpression, tm, + "transform", new Class[] { TemplateTransformModel.class }, env); + } + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + sb.append(' '); + sb.append(transformExpression); + if (namedArgs != null) { + for (Iterator it = getSortedNamedArgs().iterator(); it.hasNext(); ) { + Map.Entry entry = (Map.Entry) it.next(); + sb.append(' '); + sb.append(entry.getKey()); + sb.append('='); + MessageUtil.appendExpressionAsUntearable(sb, (ASTExpression) entry.getValue()); + } + } + if (canonical) { + sb.append(">"); + sb.append(getChildrenCanonicalForm()); + sb.append("</").append(getNodeTypeSymbol()).append('>'); + } + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#transform"; + } + + @Override + int getParameterCount() { + return 1/*nameExp*/ + (namedArgs != null ? namedArgs.size() * 2 : 0); + } + + @Override + Object getParameterValue(int idx) { + if (idx == 0) { + return transformExpression; + } else if (namedArgs != null && idx - 1 < namedArgs.size() * 2) { + Map.Entry namedArg = (Map.Entry) getSortedNamedArgs().get((idx - 1) / 2); + return (idx - 1) % 2 == 0 ? namedArg.getKey() : namedArg.getValue(); + } else { + throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx == 0) { + return ParameterRole.CALLEE; + } else if (idx - 1 < namedArgs.size() * 2) { + return (idx - 1) % 2 == 0 ? ParameterRole.ARGUMENT_NAME : ParameterRole.ARGUMENT_VALUE; + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Returns the named args by source-code order; it's not meant to be used during template execution, too slow for + * that! + */ + private List/*<Map.Entry<String, ASTExpression>>*/ getSortedNamedArgs() { + Reference ref = sortedNamedArgsCache; + if (ref != null) { + List res = (List) ref.get(); + if (res != null) return res; + } + + List res = MiscUtil.sortMapOfExpressions(namedArgs); + sortedNamedArgsCache = new SoftReference(res); + return res; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + + @Override + boolean isShownInStackTrace() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java b/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java new file mode 100644 index 0000000..3eaa6bb --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateTransformModel; +import org.apache.freemarker.core.util.ObjectFactory; +import org.apache.freemarker.core.util._StringUtil; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * AST directive node: {@code <@exp .../>} or {@code <@exp ...>...</@...>}. Calls an user-defined directive (like a + * macro). + */ +final class ASTDirUserDefined extends _ASTElement implements DirectiveCallPlace { + + private ASTExpression nameExp; + private Map namedArgs; + private List positionalArgs, bodyParameterNames; + boolean legacySyntax; + private transient volatile SoftReference/*List<Map.Entry<String,ASTExpression>>*/ sortedNamedArgsCache; + private CustomDataHolder customDataHolder; + + ASTDirUserDefined(ASTExpression nameExp, + Map namedArgs, + TemplateElements children, + List bodyParameterNames) { + this.nameExp = nameExp; + this.namedArgs = namedArgs; + setChildren(children); + this.bodyParameterNames = bodyParameterNames; + } + + ASTDirUserDefined(ASTExpression nameExp, + List positionalArgs, + TemplateElements children, + List bodyParameterNames) { + this.nameExp = nameExp; + this.positionalArgs = positionalArgs; + setChildren(children); + this.bodyParameterNames = bodyParameterNames; + } + + @Override + _ASTElement[] accept(Environment env) throws TemplateException, IOException { + TemplateModel tm = nameExp.eval(env); + if (tm == ASTDirMacro.DO_NOTHING_MACRO) return null; // shortcut here. + if (tm instanceof ASTDirMacro) { + ASTDirMacro macro = (ASTDirMacro) tm; + if (macro.isFunction() && !legacySyntax) { + throw new _MiscTemplateException(env, + "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. " + + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ", + "<@someDirective someParam=f() />", "."); + } + env.invoke(macro, namedArgs, positionalArgs, bodyParameterNames, getChildBuffer()); + } else { + boolean isDirectiveModel = tm instanceof TemplateDirectiveModel; + if (isDirectiveModel || tm instanceof TemplateTransformModel) { + Map args; + if (namedArgs != null && !namedArgs.isEmpty()) { + args = new HashMap(); + for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext(); ) { + Map.Entry entry = (Map.Entry) it.next(); + String key = (String) entry.getKey(); + ASTExpression valueExp = (ASTExpression) entry.getValue(); + TemplateModel value = valueExp.eval(env); + args.put(key, value); + } + } else { + args = Collections.emptyMap(); + } + if (isDirectiveModel) { + env.visit(getChildBuffer(), (TemplateDirectiveModel) tm, args, bodyParameterNames); + } else { + env.visitAndTransform(getChildBuffer(), (TemplateTransformModel) tm, args); + } + } else if (tm == null) { + throw InvalidReferenceException.getInstance(nameExp, env); + } else { + throw new NonUserDefinedDirectiveLikeException(nameExp, tm, env); + } + } + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append('@'); + MessageUtil.appendExpressionAsUntearable(sb, nameExp); + boolean nameIsInParen = sb.charAt(sb.length() - 1) == ')'; + if (positionalArgs != null) { + for (int i = 0; i < positionalArgs.size(); i++) { + ASTExpression argExp = (ASTExpression) positionalArgs.get(i); + if (i != 0) { + sb.append(','); + } + sb.append(' '); + sb.append(argExp.getCanonicalForm()); + } + } else { + List entries = getSortedNamedArgs(); + for (int i = 0; i < entries.size(); i++) { + Map.Entry entry = (Map.Entry) entries.get(i); + ASTExpression argExp = (ASTExpression) entry.getValue(); + sb.append(' '); + sb.append(_StringUtil.toFTLTopLevelIdentifierReference((String) entry.getKey())); + sb.append('='); + MessageUtil.appendExpressionAsUntearable(sb, argExp); + } + } + if (bodyParameterNames != null && !bodyParameterNames.isEmpty()) { + sb.append("; "); + for (int i = 0; i < bodyParameterNames.size(); i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(_StringUtil.toFTLTopLevelIdentifierReference((String) bodyParameterNames.get(i))); + } + } + if (canonical) { + if (getChildCount() == 0) { + sb.append("/>"); + } else { + sb.append('>'); + sb.append(getChildrenCanonicalForm()); + sb.append("</@"); + if (!nameIsInParen + && (nameExp instanceof ASTExpVariable + || (nameExp instanceof ASTExpDot && ((ASTExpDot) nameExp).onlyHasIdentifiers()))) { + sb.append(nameExp.getCanonicalForm()); + } + sb.append('>'); + } + } + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "@"; + } + + @Override + int getParameterCount() { + return 1/*nameExp*/ + + (positionalArgs != null ? positionalArgs.size() : 0) + + (namedArgs != null ? namedArgs.size() * 2 : 0) + + (bodyParameterNames != null ? bodyParameterNames.size() : 0); + } + + @Override + Object getParameterValue(int idx) { + if (idx == 0) { + return nameExp; + } else { + int base = 1; + final int positionalArgsSize = positionalArgs != null ? positionalArgs.size() : 0; + if (idx - base < positionalArgsSize) { + return positionalArgs.get(idx - base); + } else { + base += positionalArgsSize; + final int namedArgsSize = namedArgs != null ? namedArgs.size() : 0; + if (idx - base < namedArgsSize * 2) { + Map.Entry namedArg = (Map.Entry) getSortedNamedArgs().get((idx - base) / 2); + return (idx - base) % 2 == 0 ? namedArg.getKey() : namedArg.getValue(); + } else { + base += namedArgsSize * 2; + final int bodyParameterNamesSize = bodyParameterNames != null ? bodyParameterNames.size() : 0; + if (idx - base < bodyParameterNamesSize) { + return bodyParameterNames.get(idx - base); + } else { + throw new IndexOutOfBoundsException(); + } + } + } + } + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx == 0) { + return ParameterRole.CALLEE; + } else { + int base = 1; + final int positionalArgsSize = positionalArgs != null ? positionalArgs.size() : 0; + if (idx - base < positionalArgsSize) { + return ParameterRole.ARGUMENT_VALUE; + } else { + base += positionalArgsSize; + final int namedArgsSize = namedArgs != null ? namedArgs.size() : 0; + if (idx - base < namedArgsSize * 2) { + return (idx - base) % 2 == 0 ? ParameterRole.ARGUMENT_NAME : ParameterRole.ARGUMENT_VALUE; + } else { + base += namedArgsSize * 2; + final int bodyParameterNamesSize = bodyParameterNames != null ? bodyParameterNames.size() : 0; + if (idx - base < bodyParameterNamesSize) { + return ParameterRole.TARGET_LOOP_VARIABLE; + } else { + throw new IndexOutOfBoundsException(); + } + } + } + } + } + + /** + * Returns the named args by source-code order; it's not meant to be used during template execution, too slow for + * that! + */ + private List/*<Map.Entry<String, ASTExpression>>*/ getSortedNamedArgs() { + Reference ref = sortedNamedArgsCache; + if (ref != null) { + List res = (List) ref.get(); + if (res != null) return res; + } + + List res = MiscUtil.sortMapOfExpressions(namedArgs); + sortedNamedArgsCache = new SoftReference(res); + return res; + } + + @Override + @SuppressFBWarnings(value={ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }, justification="Performance tricks") + public Object getOrCreateCustomData(Object providerIdentity, ObjectFactory objectFactory) + throws CallPlaceCustomDataInitializationException { + // We are using double-checked locking, utilizing Java memory model "final" trick. + // Note that this.customDataHolder is NOT volatile. + + CustomDataHolder customDataHolder = this.customDataHolder; // Findbugs false alarm + if (customDataHolder == null) { // Findbugs false alarm + synchronized (this) { + customDataHolder = this.customDataHolder; + if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) { + customDataHolder = createNewCustomData(providerIdentity, objectFactory); + this.customDataHolder = customDataHolder; + } + } + } + + if (customDataHolder.providerIdentity != providerIdentity) { + synchronized (this) { + customDataHolder = this.customDataHolder; + if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) { + customDataHolder = createNewCustomData(providerIdentity, objectFactory); + this.customDataHolder = customDataHolder; + } + } + } + + return customDataHolder.customData; + } + + private CustomDataHolder createNewCustomData(Object provierIdentity, ObjectFactory objectFactory) + throws CallPlaceCustomDataInitializationException { + CustomDataHolder customDataHolder; + Object customData; + try { + customData = objectFactory.createObject(); + } catch (Exception e) { + throw new CallPlaceCustomDataInitializationException( + "Failed to initialize custom data for provider identity " + + _StringUtil.tryToString(provierIdentity) + " via factory " + + _StringUtil.tryToString(objectFactory), e); + } + if (customData == null) { + throw new NullPointerException("ObjectFactory.createObject() has returned null"); + } + customDataHolder = new CustomDataHolder(provierIdentity, customData); + return customDataHolder; + } + + @Override + public boolean isNestedOutputCacheable() { + return isChildrenOutputCacheable(); + } + +/* + //REVISIT + boolean heedsOpeningWhitespace() { + return nestedBlock == null; + } + + //REVISIT + boolean heedsTrailingWhitespace() { + return nestedBlock == null; + }*/ + + /** + * Used for implementing double check locking in implementing the + * {@link DirectiveCallPlace#getOrCreateCustomData(Object, ObjectFactory)}. + */ + private static class CustomDataHolder { + + private final Object providerIdentity; + private final Object customData; + public CustomDataHolder(Object providerIdentity, Object customData) { + this.providerIdentity = providerIdentity; + this.customData = customData; + } + + } + + @Override + boolean isNestedBlockRepeater() { + return true; + } + + @Override + boolean isShownInStackTrace() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDirVisit.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDirVisit.java b/src/main/java/org/apache/freemarker/core/ASTDirVisit.java new file mode 100644 index 0000000..e68cf72 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDirVisit.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNodeModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.SimpleSequence; + + +/** + * AST directive node: {@code #visit}. + */ +final class ASTDirVisit extends _ASTElement { + + ASTExpression targetNode, namespaces; + + ASTDirVisit(ASTExpression targetNode, ASTExpression namespaces) { + this.targetNode = targetNode; + this.namespaces = namespaces; + } + + @Override + _ASTElement[] accept(Environment env) throws IOException, TemplateException { + TemplateModel node = targetNode.eval(env); + if (!(node instanceof TemplateNodeModel)) { + throw new NonNodeException(targetNode, node, env); + } + + TemplateModel nss = namespaces == null ? null : namespaces.eval(env); + if (namespaces instanceof ASTExpStringLiteral) { + nss = env.importLib(((TemplateScalarModel) nss).getAsString(), null); + } else if (namespaces instanceof ASTExpListLiteral) { + nss = ((ASTExpListLiteral) namespaces).evaluateStringsToNamespaces(env); + } + if (nss != null) { + if (nss instanceof Environment.Namespace) { + SimpleSequence ss = new SimpleSequence(1); + ss.add(nss); + nss = ss; + } else if (!(nss instanceof TemplateSequenceModel)) { + if (namespaces != null) { + throw new NonSequenceException(namespaces, nss, env); + } else { + // Should not occur + throw new _MiscTemplateException(env, "Expecting a sequence of namespaces after \"using\""); + } + } + } + env.invokeNodeHandlerFor((TemplateNodeModel) node, (TemplateSequenceModel) nss); + return null; + } + + @Override + protected String dump(boolean canonical) { + StringBuilder sb = new StringBuilder(); + if (canonical) sb.append('<'); + sb.append(getNodeTypeSymbol()); + sb.append(' '); + sb.append(targetNode.getCanonicalForm()); + if (namespaces != null) { + sb.append(" using "); + sb.append(namespaces.getCanonicalForm()); + } + if (canonical) sb.append("/>"); + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "#visit"; + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return targetNode; + case 1: return namespaces; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + switch (idx) { + case 0: return ParameterRole.NODE; + case 1: return ParameterRole.NAMESPACE; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + boolean isNestedBlockRepeater() { + return true; + } + + @Override + boolean isShownInStackTrace() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java b/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java new file mode 100644 index 0000000..2396a60 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.freemarker.core.util.FTLUtil; + +/** + * AST interpolation node: <tt>${exp}</tt> + */ +final class ASTDollarInterpolation extends ASTInterpolation { + + private final ASTExpression expression; + + /** For {@code #escape x as ...} (legacy auto-escaping) */ + private final ASTExpression escapedExpression; + + /** For OutputFormat-based auto-escaping */ + private final OutputFormat outputFormat; + private final MarkupOutputFormat markupOutputFormat; + private final boolean autoEscape; + + ASTDollarInterpolation( + ASTExpression expression, ASTExpression escapedExpression, + OutputFormat outputFormat, boolean autoEscape) { + this.expression = expression; + this.escapedExpression = escapedExpression; + this.outputFormat = outputFormat; + markupOutputFormat + = (MarkupOutputFormat) (outputFormat instanceof MarkupOutputFormat ? outputFormat : null); + this.autoEscape = autoEscape; + } + + /** + * Outputs the string value of the enclosed expression. + */ + @Override + _ASTElement[] accept(Environment env) throws TemplateException, IOException { + final Object moOrStr = calculateInterpolatedStringOrMarkup(env); + final Writer out = env.getOut(); + if (moOrStr instanceof String) { + final String s = (String) moOrStr; + if (autoEscape) { + markupOutputFormat.output(s, out); + } else { + out.write(s); + } + } else { + final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr; + final MarkupOutputFormat moOF = mo.getOutputFormat(); + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) { + final String srcPlainText; + // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic! + srcPlainText = moOF.getSourcePlainText(mo); + if (srcPlainText == null) { + throw new _TemplateModelException(escapedExpression, + "The value to print is in ", new _DelayedToString(moOF), + " format, which differs from the current output format, ", + new _DelayedToString(outputFormat), ". Format conversion wasn't possible."); + } + if (outputFormat instanceof MarkupOutputFormat) { + ((MarkupOutputFormat) outputFormat).output(srcPlainText, out); + } else { + out.write(srcPlainText); + } + } else { + moOF.output(mo, out); + } + } + return null; + } + + @Override + protected Object calculateInterpolatedStringOrMarkup(Environment env) throws TemplateException { + return EvalUtil.coerceModelToStringOrMarkup(escapedExpression.eval(env), escapedExpression, null, env); + } + + @Override + protected String dump(boolean canonical, boolean inStringLiteral) { + StringBuilder sb = new StringBuilder(); + sb.append("${"); + final String exprCF = expression.getCanonicalForm(); + sb.append(inStringLiteral ? FTLUtil.escapeStringLiteralPart(exprCF, '"') : exprCF); + sb.append("}"); + if (!canonical && expression != escapedExpression) { + sb.append(" auto-escaped"); + } + return sb.toString(); + } + + @Override + String getNodeTypeSymbol() { + return "${...}"; + } + + @Override + boolean heedsOpeningWhitespace() { + return true; + } + + @Override + boolean heedsTrailingWhitespace() { + return true; + } + + @Override + int getParameterCount() { + return 1; + } + + @Override + Object getParameterValue(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return expression; + } + + @Override + ParameterRole getParameterRole(int idx) { + if (idx != 0) throw new IndexOutOfBoundsException(); + return ParameterRole.CONTENT; + } + + @Override + boolean isNestedBlockRepeater() { + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java b/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java new file mode 100644 index 0000000..7b2369a --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateHashModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.CollectionAndSequence; +import org.apache.freemarker.core.model.impl.SimpleNumber; +import org.apache.freemarker.core.model.impl.SimpleScalar; +import org.apache.freemarker.core.model.impl.SimpleSequence; + +/** + * AST expression node: binary {@code +} operator. Note that this is treated separately from the other 4 arithmetic + * operators, since it's overloaded to mean concatenation of string-s, sequences and hash-es too. + */ +final class ASTExpAddOrConcat extends ASTExpression { + + private final ASTExpression left; + private final ASTExpression right; + + ASTExpAddOrConcat(ASTExpression left, ASTExpression right) { + this.left = left; + this.right = right; + } + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return _eval(env, this, left, left.eval(env), right, right.eval(env)); + } + + /** + * @param leftExp + * Used for error messages only; can be {@code null} + * @param rightExp + * Used for error messages only; can be {@code null} + */ + static TemplateModel _eval(Environment env, + ASTNode parent, + ASTExpression leftExp, TemplateModel leftModel, + ASTExpression rightExp, TemplateModel rightModel) + throws TemplateModelException, TemplateException, NonStringException { + if (leftModel instanceof TemplateNumberModel && rightModel instanceof TemplateNumberModel) { + Number first = EvalUtil.modelToNumber((TemplateNumberModel) leftModel, leftExp); + Number second = EvalUtil.modelToNumber((TemplateNumberModel) rightModel, rightExp); + return _evalOnNumbers(env, parent, first, second); + } else if (leftModel instanceof TemplateSequenceModel && rightModel instanceof TemplateSequenceModel) { + return new ConcatenatedSequence((TemplateSequenceModel) leftModel, (TemplateSequenceModel) rightModel); + } else { + boolean hashConcatPossible + = leftModel instanceof TemplateHashModel && rightModel instanceof TemplateHashModel; + try { + // We try string addition first. If hash addition is possible, then instead of throwing exception + // we return null and do hash addition instead. (We can't simply give hash addition a priority, like + // with sequence addition above, as FTL strings are often also FTL hashes.) + Object leftOMOrStr = EvalUtil.coerceModelToStringOrMarkup( + leftModel, leftExp, /* returnNullOnNonCoercableType = */ hashConcatPossible, null, + env); + if (leftOMOrStr == null) { + return _eval_concatenateHashes(leftModel, rightModel); + } + + // Same trick with null return as above. + Object rightOMOrStr = EvalUtil.coerceModelToStringOrMarkup( + rightModel, rightExp, /* returnNullOnNonCoercableType = */ hashConcatPossible, null, + env); + if (rightOMOrStr == null) { + return _eval_concatenateHashes(leftModel, rightModel); + } + + if (leftOMOrStr instanceof String) { + if (rightOMOrStr instanceof String) { + return new SimpleScalar(((String) leftOMOrStr).concat((String) rightOMOrStr)); + } else { // rightOMOrStr instanceof TemplateMarkupOutputModel + TemplateMarkupOutputModel<?> rightMO = (TemplateMarkupOutputModel<?>) rightOMOrStr; + return EvalUtil.concatMarkupOutputs(parent, + rightMO.getOutputFormat().fromPlainTextByEscaping((String) leftOMOrStr), + rightMO); + } + } else { // leftOMOrStr instanceof TemplateMarkupOutputModel + TemplateMarkupOutputModel<?> leftMO = (TemplateMarkupOutputModel<?>) leftOMOrStr; + if (rightOMOrStr instanceof String) { // markup output + return EvalUtil.concatMarkupOutputs(parent, + leftMO, + leftMO.getOutputFormat().fromPlainTextByEscaping((String) rightOMOrStr)); + } else { // rightOMOrStr instanceof TemplateMarkupOutputModel + return EvalUtil.concatMarkupOutputs(parent, + leftMO, + (TemplateMarkupOutputModel<?>) rightOMOrStr); + } + } + } catch (NonStringOrTemplateOutputException e) { + // 2.4: Remove this catch; it's for BC, after reworking hash addition so it doesn't rely on this. But + // user code might throws this (very unlikely), and then in 2.3.x we did catch that too, incorrectly. + if (hashConcatPossible) { + return _eval_concatenateHashes(leftModel, rightModel); + } else { + throw e; + } + } + } + } + + private static TemplateModel _eval_concatenateHashes(TemplateModel leftModel, TemplateModel rightModel) + throws TemplateModelException { + if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) { + TemplateHashModelEx leftModelEx = (TemplateHashModelEx) leftModel; + TemplateHashModelEx rightModelEx = (TemplateHashModelEx) rightModel; + if (leftModelEx.size() == 0) { + return rightModelEx; + } else if (rightModelEx.size() == 0) { + return leftModelEx; + } else { + return new ConcatenatedHashEx(leftModelEx, rightModelEx); + } + } else { + return new ConcatenatedHash((TemplateHashModel) leftModel, + (TemplateHashModel) rightModel); + } + } + + static TemplateModel _evalOnNumbers(Environment env, ASTNode parent, Number first, Number second) + throws TemplateException { + ArithmeticEngine ae = EvalUtil.getArithmeticEngine(env, parent); + return new SimpleNumber(ae.add(first, second)); + } + + @Override + boolean isLiteral() { + return constantValue != null || (left.isLiteral() && right.isLiteral()); + } + + @Override + protected ASTExpression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { + return new ASTExpAddOrConcat( + left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), + right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState)); + } + + @Override + public String getCanonicalForm() { + return left.getCanonicalForm() + " + " + right.getCanonicalForm(); + } + + @Override + String getNodeTypeSymbol() { + return "+"; + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + return idx == 0 ? left : right; + } + + @Override + ParameterRole getParameterRole(int idx) { + return ParameterRole.forBinaryOperatorOperand(idx); + } + + private static final class ConcatenatedSequence + implements + TemplateSequenceModel { + private final TemplateSequenceModel left; + private final TemplateSequenceModel right; + + ConcatenatedSequence(TemplateSequenceModel left, TemplateSequenceModel right) { + this.left = left; + this.right = right; + } + + @Override + public int size() + throws TemplateModelException { + return left.size() + right.size(); + } + + @Override + public TemplateModel get(int i) + throws TemplateModelException { + int ls = left.size(); + return i < ls ? left.get(i) : right.get(i - ls); + } + } + + private static class ConcatenatedHash + implements TemplateHashModel { + protected final TemplateHashModel left; + protected final TemplateHashModel right; + + ConcatenatedHash(TemplateHashModel left, TemplateHashModel right) { + this.left = left; + this.right = right; + } + + @Override + public TemplateModel get(String key) + throws TemplateModelException { + TemplateModel model = right.get(key); + return (model != null) ? model : left.get(key); + } + + @Override + public boolean isEmpty() + throws TemplateModelException { + return left.isEmpty() && right.isEmpty(); + } + } + + private static final class ConcatenatedHashEx + extends ConcatenatedHash + implements TemplateHashModelEx { + private CollectionAndSequence keys; + private CollectionAndSequence values; + private int size; + + ConcatenatedHashEx(TemplateHashModelEx left, TemplateHashModelEx right) { + super(left, right); + } + + @Override + public int size() throws TemplateModelException { + initKeys(); + return size; + } + + @Override + public TemplateCollectionModel keys() + throws TemplateModelException { + initKeys(); + return keys; + } + + @Override + public TemplateCollectionModel values() + throws TemplateModelException { + initValues(); + return values; + } + + private void initKeys() + throws TemplateModelException { + if (keys == null) { + HashSet keySet = new HashSet(); + SimpleSequence keySeq = new SimpleSequence(32); + addKeys(keySet, keySeq, (TemplateHashModelEx) left); + addKeys(keySet, keySeq, (TemplateHashModelEx) right); + size = keySet.size(); + keys = new CollectionAndSequence(keySeq); + } + } + + private static void addKeys(Set set, SimpleSequence keySeq, TemplateHashModelEx hash) + throws TemplateModelException { + TemplateModelIterator it = hash.keys().iterator(); + while (it.hasNext()) { + TemplateScalarModel tsm = (TemplateScalarModel) it.next(); + if (set.add(tsm.getAsString())) { + // The first occurence of the key decides the index; + // this is consisten with stuff like java.util.LinkedHashSet. + keySeq.add(tsm); + } + } + } + + private void initValues() + throws TemplateModelException { + if (values == null) { + SimpleSequence seq = new SimpleSequence(size()); + // Note: size() invokes initKeys() if needed. + + int ln = keys.size(); + for (int i = 0; i < ln; i++) { + seq.add(get(((TemplateScalarModel) keys.get(i)).getAsString())); + } + values = new CollectionAndSequence(seq); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTExpAnd.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTExpAnd.java b/src/main/java/org/apache/freemarker/core/ASTExpAnd.java new file mode 100644 index 0000000..08a9ffa --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTExpAnd.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +/** + * AST expression node: {@code &&} operator + */ +final class ASTExpAnd extends ASTExpBoolean { + + private final ASTExpression lho; + private final ASTExpression rho; + + ASTExpAnd(ASTExpression lho, ASTExpression rho) { + this.lho = lho; + this.rho = rho; + } + + @Override + boolean evalToBoolean(Environment env) throws TemplateException { + return lho.evalToBoolean(env) && rho.evalToBoolean(env); + } + + @Override + public String getCanonicalForm() { + return lho.getCanonicalForm() + " && " + rho.getCanonicalForm(); + } + + @Override + String getNodeTypeSymbol() { + return "&&"; + } + + @Override + boolean isLiteral() { + return constantValue != null || (lho.isLiteral() && rho.isLiteral()); + } + + @Override + protected ASTExpression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { + return new ASTExpAnd( + lho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState), + rho.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState)); + } + + @Override + int getParameterCount() { + return 2; + } + + @Override + Object getParameterValue(int idx) { + switch (idx) { + case 0: return lho; + case 1: return rho; + default: throw new IndexOutOfBoundsException(); + } + } + + @Override + ParameterRole getParameterRole(int idx) { + return ParameterRole.forBinaryOperatorOperand(idx); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTExpBoolean.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTExpBoolean.java b/src/main/java/org/apache/freemarker/core/ASTExpBoolean.java new file mode 100644 index 0000000..e292389 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTExpBoolean.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModel; + +/** + * AST expression node superclass for expressions returning a boolean. + */ +abstract class ASTExpBoolean extends ASTExpression { + + @Override + TemplateModel _eval(Environment env) throws TemplateException { + return evalToBoolean(env) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java b/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java new file mode 100644 index 0000000..47336cb --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/ASTExpBooleanLiteral.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core; + +import org.apache.freemarker.core.model.TemplateBooleanModel; +import org.apache.freemarker.core.model.TemplateModel; + +/** + * AST expression node: boolean literal + */ +final class ASTExpBooleanLiteral extends ASTExpression { + + private final boolean val; + + public ASTExpBooleanLiteral(boolean val) { + this.val = val; + } + + static TemplateBooleanModel getTemplateModel(boolean b) { + return b? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } + + @Override + boolean evalToBoolean(Environment env) { + return val; + } + + @Override + public String getCanonicalForm() { + return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE; + } + + @Override + String getNodeTypeSymbol() { + return getCanonicalForm(); + } + + @Override + public String toString() { + return val ? MiscUtil.C_TRUE : MiscUtil.C_FALSE; + } + + @Override + TemplateModel _eval(Environment env) { + return val ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + } + + @Override + boolean isLiteral() { + return true; + } + + @Override + protected ASTExpression deepCloneWithIdentifierReplaced_inner( + String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { + return new ASTExpBooleanLiteral(val); + } + + @Override + int getParameterCount() { + return 0; + } + + @Override + Object getParameterValue(int idx) { + throw new IndexOutOfBoundsException(); + } + + @Override + ParameterRole getParameterRole(int idx) { + throw new IndexOutOfBoundsException(); + } + +}
