You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/14 10:53:29 UTC
[46/51] [partial] incubator-freemarker git commit: Migrated from Ant
to Gradle, and modularized the project. This is an incomplete migration;
there are some TODO-s in the build scripts, and release related tasks are
still missing. What works: Building th
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
new file mode 100644
index 0000000..b95b3fc
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirRecurse.java
@@ -0,0 +1,130 @@
+/*
+ * 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;
+
+
+/**
+ * AST directive node: {@code #recurse}.
+ */
+final class ASTDirRecurse extends ASTDirective {
+
+ 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) {
+ NativeSequence ss = new NativeSequence(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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirReturn.java
new file mode 100644
index 0000000..0e82a74
--- /dev/null
+++ b/freemarker-core/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}.
+ */
+final class ASTDirReturn extends ASTDirective {
+
+ 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 && getParent() 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSep.java
new file mode 100644
index 0000000..9e83e83
--- /dev/null
+++ b/freemarker-core/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 ASTDirective {
+
+ 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSetting.java
new file mode 100644
index 0000000..68a0672
--- /dev/null
+++ b/freemarker-core/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 ASTDirective {
+
+ private final String key;
+ private final ASTExpression value;
+
+ static final String[] SETTING_NAMES = new String[] {
+ // Must be sorted alphabetically!
+ MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.BOOLEAN_FORMAT_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.DATE_FORMAT_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.DATE_FORMAT_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.DATETIME_FORMAT_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.DATETIME_FORMAT_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.LOCALE_KEY,
+ MutableProcessingConfiguration.NUMBER_FORMAT_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.NUMBER_FORMAT_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.OUTPUT_ENCODING_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.OUTPUT_ENCODING_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY,
+ MutableProcessingConfiguration.TIME_FORMAT_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.TIME_ZONE_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.TIME_FORMAT_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.TIME_ZONE_KEY_SNAKE_CASE,
+ MutableProcessingConfiguration.URL_ESCAPING_CHARSET_KEY_CAMEL_CASE,
+ MutableProcessingConfiguration.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 (Configuration.ExtendableBuilder.getSettingNames(true).contains(key)
+ || Configuration.ExtendableBuilder.getSettingNames(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 != ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION
+ ? namingConvention : ParsingConfiguration.LEGACY_NAMING_CONVENTION /* [2.4] CAMEL_CASE */;
+ }
+
+ boolean first = true;
+ for (String correctName : SETTING_NAMES) {
+ int correctNameNamingConvention = _StringUtil.getIdentifierNamingConvention(correctName);
+ if (shownNamingConvention == ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION
+ ? correctNameNamingConvention != ParsingConfiguration.LEGACY_NAMING_CONVENTION
+ : correctNameNamingConvention != ParsingConfiguration.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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirStop.java
new file mode 100644
index 0000000..f453734
--- /dev/null
+++ b/freemarker-core/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 ASTDirective {
+
+ 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
new file mode 100644
index 0000000..e66c419
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirSwitch.java
@@ -0,0 +1,129 @@
+/*
+ * 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 ASTDirective {
+
+ 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) {
+ // #break was called
+ }
+ 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirTOrTrOrTl.java
new file mode 100644
index 0000000..937bc18
--- /dev/null
+++ b/freemarker-core/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 ASTDirective {
+
+ private static final int TYPE_T = 0;
+ private static final int TYPE_LT = 1;
+ private static final int TYPE_RT = 2;
+ private static 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
new file mode 100644
index 0000000..6042bd8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
@@ -0,0 +1,343 @@
+/*
+ * 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 ASTDirective implements DirectiveCallPlace {
+
+ private ASTExpression nameExp;
+ private Map namedArgs;
+ private List positionalArgs, bodyParameterNames;
+ 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()) {
+ 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
new file mode 100644
index 0000000..4a4023b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirVisit.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+
+/**
+ * AST directive node: {@code #visit}.
+ */
+final class ASTDirVisit extends ASTDirective {
+
+ 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) {
+ NativeSequence ss = new NativeSequence(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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
new file mode 100644
index 0000000..778fed1
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirective.java
@@ -0,0 +1,98 @@
+/*
+ * 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.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * AST directive node superclass.
+ */
+abstract class ASTDirective extends ASTElement {
+
+ private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+ String commonName) {
+ allNames.add(commonName);
+ lcNames.add(commonName);
+ ccNames.add(commonName);
+ }
+
+ private static void addName(Set<String> allNames, Set<String> lcNames, Set<String> ccNames,
+ String lcName, String ccName) {
+ allNames.add(lcName);
+ allNames.add(ccName);
+ lcNames.add(lcName);
+ ccNames.add(ccName);
+ }
+
+ static final Set<String> ALL_BUILT_IN_DIRECTIVE_NAMES;
+ static final Set<String> LEGACY_BUILT_IN_DIRECTIVE_NAMES;
+ static final Set<String> CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES;
+ static {
+ Set<String> allNames = new TreeSet();
+ Set<String> lcNames = new TreeSet();
+ Set<String> ccNames = new TreeSet();
+
+ addName(allNames, lcNames, ccNames, "assign");
+ addName(allNames, lcNames, ccNames, "attempt");
+ addName(allNames, lcNames, ccNames, "autoesc", "autoEsc");
+ addName(allNames, lcNames, ccNames, "break");
+ addName(allNames, lcNames, ccNames, "case");
+ addName(allNames, lcNames, ccNames, "compress");
+ addName(allNames, lcNames, ccNames, "default");
+ addName(allNames, lcNames, ccNames, "else");
+ addName(allNames, lcNames, ccNames, "elseif", "elseIf");
+ addName(allNames, lcNames, ccNames, "escape");
+ addName(allNames, lcNames, ccNames, "fallback");
+ addName(allNames, lcNames, ccNames, "flush");
+ addName(allNames, lcNames, ccNames, "ftl");
+ addName(allNames, lcNames, ccNames, "function");
+ addName(allNames, lcNames, ccNames, "global");
+ addName(allNames, lcNames, ccNames, "if");
+ addName(allNames, lcNames, ccNames, "import");
+ addName(allNames, lcNames, ccNames, "include");
+ addName(allNames, lcNames, ccNames, "items");
+ addName(allNames, lcNames, ccNames, "list");
+ addName(allNames, lcNames, ccNames, "local");
+ addName(allNames, lcNames, ccNames, "lt");
+ addName(allNames, lcNames, ccNames, "macro");
+ addName(allNames, lcNames, ccNames, "nested");
+ addName(allNames, lcNames, ccNames, "noautoesc", "noAutoEsc");
+ addName(allNames, lcNames, ccNames, "noescape", "noEscape");
+ addName(allNames, lcNames, ccNames, "noparse", "noParse");
+ addName(allNames, lcNames, ccNames, "nt");
+ addName(allNames, lcNames, ccNames, "outputformat", "outputFormat");
+ addName(allNames, lcNames, ccNames, "recover");
+ addName(allNames, lcNames, ccNames, "recurse");
+ addName(allNames, lcNames, ccNames, "return");
+ addName(allNames, lcNames, ccNames, "rt");
+ addName(allNames, lcNames, ccNames, "sep");
+ addName(allNames, lcNames, ccNames, "setting");
+ addName(allNames, lcNames, ccNames, "stop");
+ addName(allNames, lcNames, ccNames, "switch");
+ addName(allNames, lcNames, ccNames, "t");
+ addName(allNames, lcNames, ccNames, "visit");
+
+ ALL_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(allNames);
+ LEGACY_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(lcNames);
+ CAMEL_CASE_BUILT_IN_DIRECTIVE_NAMES = Collections.unmodifiableSet(ccNames);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java
new file mode 100644
index 0000000..1e5a7b4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDollarInterpolation.java
@@ -0,0 +1,151 @@
+/*
+ * 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.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.outputformat.OutputFormat;
+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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
new file mode 100644
index 0000000..a9cbfc0
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
@@ -0,0 +1,445 @@
+/*
+ * 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.util.Collections;
+import java.util.Enumeration;
+
+import org.apache.freemarker.core.util._ArrayEnumeration;
+
+/**
+ * AST non-expression node superclass: Superclass of directive calls, interpolations, static text, top-level comments,
+ * or other such non-expression node in the parsed template. Some information that can be found here can be accessed
+ * through the {@link Environment#getCurrentDirectiveCallPlace()}, which is a published API, and thus promises backward
+ * compatibility.
+ */
+// TODO [FM3] Get rid of "public" and thus the "_" prefix
+abstract class ASTElement extends ASTNode {
+
+ private static final int INITIAL_CHILD_BUFFER_CAPACITY = 6;
+
+ private ASTElement parent;
+
+ /**
+ * Contains 1 or more nested elements with optional trailing {@code null}-s, or is {@code null} exactly if there are
+ * no nested elements.
+ */
+ private ASTElement[] childBuffer;
+
+ /**
+ * Contains the number of elements in the {@link #childBuffer}, not counting the trailing {@code null}-s. If this is
+ * 0, then and only then {@link #childBuffer} must be {@code null}.
+ */
+ private int childCount;
+
+ /**
+ * The index of the element in the parent's {@link #childBuffer} array.
+ *
+ * @since 2.3.23
+ */
+ private int index;
+
+ /**
+ * Executes this {@link ASTElement}. Usually should not be called directly, but through
+ * {@link Environment#visit(ASTElement)} or a similar {@link Environment} method.
+ *
+ * @param env
+ * The runtime environment
+ *
+ * @return The template elements to execute (meant to be used for nested elements), or {@code null}. Can have
+ * <em>trailing</em> {@code null}-s (unused buffer capacity). Returning the nested elements instead of
+ * executing them inside this method is a trick used for decreasing stack usage when there's nothing to do
+ * after the children was processed anyway.
+ */
+ abstract ASTElement[] accept(Environment env) throws TemplateException, IOException;
+
+ /**
+ * One-line description of the element, that contain all the information that is used in {@link #getCanonicalForm()}
+ * , except the nested content (elements) of the element. The expressions inside the element (the parameters) has to
+ * be shown. Meant to be used for stack traces, also for tree views that don't go down to the expression-level.
+ * There are no backward-compatibility guarantees regarding the format used ATM, but it must be regular enough to be
+ * machine-parseable, and it must contain all information necessary for restoring an AST equivalent to the original.
+ *
+ * This final implementation calls {@link #dump(boolean) dump(false)}.
+ *
+ * @see #getCanonicalForm()
+ * @see #getNodeTypeSymbol()
+ */
+ public final String getDescription() {
+ return dump(false);
+ }
+
+ /**
+ * This final implementation calls {@link #dump(boolean) dump(false)}.
+ */
+ @Override
+ public final String getCanonicalForm() {
+ return dump(true);
+ }
+
+ final String getChildrenCanonicalForm() {
+ return getChildrenCanonicalForm(childBuffer);
+ }
+
+ static String getChildrenCanonicalForm(ASTElement[] children) {
+ if (children == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (ASTElement child : children) {
+ if (child == null) {
+ break;
+ }
+ sb.append(child.getCanonicalForm());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Tells if the element should show up in error stack traces. Note that this will be ignored for the top (current)
+ * element of a stack trace, as that's always shown.
+ */
+ boolean isShownInStackTrace() {
+ return false;
+ }
+
+ /**
+ * Tells if this element possibly executes its nested content for many times. This flag is useful when a template
+ * AST is modified for running time limiting (see {@link ThreadInterruptionSupportTemplatePostProcessor}). Elements
+ * that use {@link #childBuffer} should not need this, as the insertion of the timeout checks is impossible there,
+ * given their rigid nested element schema.
+ */
+ abstract boolean isNestedBlockRepeater();
+
+ /**
+ * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} to a single place. Don't
+ * call those methods in method on {@code this}, because that will result in infinite recursion!
+ *
+ * @param canonical
+ * if {@code true}, it calculates the return value of {@link #getCanonicalForm()}, otherwise of
+ * {@link #getDescription()}.
+ */
+ abstract protected String dump(boolean canonical);
+
+ // Methods to implement TemplateNodeModel
+
+ public String getNodeName() {
+ String className = getClass().getName();
+ int shortNameOffset = className.lastIndexOf('.') + 1;
+ return className.substring(shortNameOffset);
+ }
+
+ // Methods so that we can implement the Swing TreeNode API.
+
+ public boolean isLeaf() {
+ return childCount == 0;
+ }
+
+ public int getIndex(ASTElement node) {
+ for (int i = 0; i < childCount; i++) {
+ if (childBuffer[i].equals(node)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int getChildCount() {
+ return childCount;
+ }
+
+ /**
+ * Note: For element with {@code #nestedBlock}, this will hide the {@code #nestedBlock} when that's a
+ * {@link ASTImplicitParent}.
+ */
+ public Enumeration children() {
+ return childBuffer != null
+ ? new _ArrayEnumeration(childBuffer, childCount)
+ : Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ public void setChildAt(int index, ASTElement element) {
+ if (index < childCount && index >= 0) {
+ childBuffer[index] = element;
+ element.index = index;
+ element.parent = this;
+ } else {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
+ }
+ }
+
+ /**
+ * The element whose child this element is, or {@code null} if this is the root node.
+ */
+ final ASTElement getParent() {
+ return parent;
+ }
+
+ final void setChildBufferCapacity(int capacity) {
+ int ln = childCount;
+ ASTElement[] newChildBuffer = new ASTElement[capacity];
+ for (int i = 0; i < ln; i++) {
+ newChildBuffer[i] = childBuffer[i];
+ }
+ childBuffer = newChildBuffer;
+ }
+
+ /**
+ * Inserts a new nested element after the last nested element.
+ */
+ final void addChild(ASTElement nestedElement) {
+ addChild(childCount, nestedElement);
+ }
+
+ /**
+ * Inserts a new nested element at the given index, which can also be one higher than the current highest index.
+ */
+ final void addChild(int index, ASTElement nestedElement) {
+ final int lChildCount = childCount;
+
+ ASTElement[] lChildBuffer = childBuffer;
+ if (lChildBuffer == null) {
+ lChildBuffer = new ASTElement[INITIAL_CHILD_BUFFER_CAPACITY];
+ childBuffer = lChildBuffer;
+ } else if (lChildCount == lChildBuffer.length) {
+ setChildBufferCapacity(lChildCount != 0 ? lChildCount * 2 : 1);
+ lChildBuffer = childBuffer;
+ }
+ // At this point: nestedElements == this.nestedElements, and has sufficient capacity.
+
+ for (int i = lChildCount; i > index; i--) {
+ ASTElement movedElement = lChildBuffer[i - 1];
+ movedElement.index = i;
+ lChildBuffer[i] = movedElement;
+ }
+ nestedElement.index = index;
+ nestedElement.parent = this;
+ lChildBuffer[index] = nestedElement;
+ childCount = lChildCount + 1;
+ }
+
+ final ASTElement getChild(int index) {
+ return childBuffer[index];
+ }
+
+ /**
+ * @return Array containing 1 or more nested elements with optional trailing {@code null}-s, or is {@code null}
+ * exactly if there are no nested elements.
+ */
+ final ASTElement[] getChildBuffer() {
+ return childBuffer;
+ }
+
+ /**
+ * @param buffWithCnt Maybe {@code null}
+ *
+ * @since 2.3.24
+ */
+ final void setChildren(TemplateElements buffWithCnt) {
+ ASTElement[] childBuffer = buffWithCnt.getBuffer();
+ int childCount = buffWithCnt.getCount();
+ for (int i = 0; i < childCount; i++) {
+ ASTElement child = childBuffer[i];
+ child.index = i;
+ child.parent = this;
+ }
+ this.childBuffer = childBuffer;
+ this.childCount = childCount;
+ }
+
+ final int getIndex() {
+ return index;
+ }
+
+ /**
+ * This is a special case, because a root element is not contained in another element, so we couldn't set the
+ * private fields.
+ */
+ final void setFieldsForRootElement() {
+ index = 0;
+ parent = null;
+ }
+
+ /**
+ * Walk the AST subtree rooted by this element, and do simplifications where possible, also removes superfluous
+ * whitespace.
+ *
+ * @param stripWhitespace
+ * whether to remove superfluous whitespace
+ *
+ * @return The element this element should be replaced with in the parent. If it's the same as this element, no
+ * actual replacement will happen. Note that adjusting the {@link #parent} and {@link #index} of the result
+ * is the duty of the caller, not of this method.
+ */
+ ASTElement postParseCleanup(boolean stripWhitespace) throws ParseException {
+ int childCount = this.childCount;
+ if (childCount != 0) {
+ for (int i = 0; i < childCount; i++) {
+ ASTElement te = childBuffer[i];
+
+ /*
+ // Assertion:
+ if (te.getIndex() != i) {
+ throw new BugException("Invalid index " + te.getIndex() + " (expected: "
+ + i + ") for: " + te.dump(false));
+ }
+ if (te.getParent() != this) {
+ throw new BugException("Invalid parent " + te.getParent() + " (expected: "
+ + this.dump(false) + ") for: " + te.dump(false));
+ }
+ */
+
+ te = te.postParseCleanup(stripWhitespace);
+ childBuffer[i] = te;
+ te.parent = this;
+ te.index = i;
+ }
+ for (int i = 0; i < childCount; i++) {
+ ASTElement te = childBuffer[i];
+ if (te.isIgnorable(stripWhitespace)) {
+ childCount--;
+ // As later isIgnorable calls might investigates the siblings, we have to move all the items now.
+ for (int j = i; j < childCount; j++) {
+ final ASTElement te2 = childBuffer[j + 1];
+ childBuffer[j] = te2;
+ te2.index = j;
+ }
+ childBuffer[childCount] = null;
+ this.childCount = childCount;
+ i--;
+ }
+ }
+ if (childCount == 0) {
+ childBuffer = null;
+ } else if (childCount < childBuffer.length
+ && childCount <= childBuffer.length * 3 / 4) {
+ ASTElement[] trimmedChildBuffer = new ASTElement[childCount];
+ for (int i = 0; i < childCount; i++) {
+ trimmedChildBuffer[i] = childBuffer[i];
+ }
+ childBuffer = trimmedChildBuffer;
+ }
+ }
+ return this;
+ }
+
+ boolean isIgnorable(boolean stripWhitespace) {
+ return false;
+ }
+
+ // The following methods exist to support some fancier tree-walking
+ // and were introduced to support the whitespace cleanup feature in 2.2
+
+ ASTElement prevTerminalNode() {
+ ASTElement prev = previousSibling();
+ if (prev != null) {
+ return prev.getLastLeaf();
+ } else if (parent != null) {
+ return parent.prevTerminalNode();
+ }
+ return null;
+ }
+
+ ASTElement nextTerminalNode() {
+ ASTElement next = nextSibling();
+ if (next != null) {
+ return next.getFirstLeaf();
+ } else if (parent != null) {
+ return parent.nextTerminalNode();
+ }
+ return null;
+ }
+
+ ASTElement previousSibling() {
+ if (parent == null) {
+ return null;
+ }
+ return index > 0 ? parent.childBuffer[index - 1] : null;
+ }
+
+ ASTElement nextSibling() {
+ if (parent == null) {
+ return null;
+ }
+ return index + 1 < parent.childCount ? parent.childBuffer[index + 1] : null;
+ }
+
+ private ASTElement getFirstChild() {
+ return childCount == 0 ? null : childBuffer[0];
+ }
+
+ private ASTElement getLastChild() {
+ final int childCount = this.childCount;
+ return childCount == 0 ? null : childBuffer[childCount - 1];
+ }
+
+ private ASTElement getFirstLeaf() {
+ ASTElement te = this;
+ while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+ // A macro or macro invocation is treated as a leaf here for special reasons
+ te = te.getFirstChild();
+ }
+ return te;
+ }
+
+ private ASTElement getLastLeaf() {
+ ASTElement te = this;
+ while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+ // A macro or macro invocation is treated as a leaf here for special reasons
+ te = te.getLastChild();
+ }
+ return te;
+ }
+
+ /**
+ * Tells if executing this element has output that only depends on the template content and that has no side
+ * effects.
+ */
+ boolean isOutputCacheable() {
+ return false;
+ }
+
+ boolean isChildrenOutputCacheable() {
+ int ln = childCount;
+ for (int i = 0; i < ln; i++) {
+ if (!childBuffer[i].isOutputCacheable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * determines whether this element's presence on a line indicates that we should not strip opening whitespace in the
+ * post-parse whitespace gobbling step.
+ */
+ boolean heedsOpeningWhitespace() {
+ return false;
+ }
+
+ /**
+ * determines whether this element's presence on a line indicates that we should not strip trailing whitespace in
+ * the post-parse whitespace gobbling step.
+ */
+ boolean heedsTrailingWhitespace() {
+ return false;
+ }
+}