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/02/23 21:36:10 UTC
[43/54] [partial] incubator-freemarker git commit: Unifying the
o.a.f.core and o.a.f.core.ast
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
new file mode 100644
index 0000000..0d56137
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
@@ -0,0 +1,317 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+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.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.model.impl.SimpleSequence;
+import org.apache.freemarker.core.util._StringUtil;
+
+
+/**
+ * Contains the string built-ins that correspond to basic regular expressions operations.
+ */
+class BuiltInsForStringsRegexp {
+
+ static class groupsBI extends ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ TemplateModel targetModel = target.eval(env);
+ assertNonNull(targetModel, env);
+ if (targetModel instanceof RegexMatchModel) {
+ return ((RegexMatchModel) targetModel).getGroups();
+ } else if (targetModel instanceof RegexMatchModel.MatchWithGroups) {
+ return ((RegexMatchModel.MatchWithGroups) targetModel).groupsSeq;
+ } else {
+ throw new UnexpectedTypeException(target, targetModel,
+ "regular expression matcher",
+ new Class[] { RegexMatchModel.class, RegexMatchModel.MatchWithGroups.class },
+ env);
+ }
+ }
+ }
+
+ static class matchesBI extends BuiltInForString {
+ class MatcherBuilder implements TemplateMethodModel {
+
+ String matchString;
+
+ MatcherBuilder(String matchString) throws TemplateModelException {
+ this.matchString = matchString;
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ int argCnt = args.size();
+ checkMethodArgCount(argCnt, 1, 2);
+
+ String patternString = (String) args.get(0);
+ long flags = argCnt > 1 ? RegexpHelper.parseFlagString((String) args.get(1)) : 0;
+ if ((flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0) {
+ RegexpHelper.logFlagWarning("?" + key + " doesn't support the \"f\" flag.");
+ }
+ Pattern pattern = RegexpHelper.getPattern(patternString, (int) flags);
+ return new RegexMatchModel(pattern, matchString);
+ }
+ }
+
+ @Override
+ TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+ return new MatcherBuilder(s);
+ }
+
+ }
+
+ static class replace_reBI extends BuiltInForString {
+
+ class ReplaceMethod implements TemplateMethodModel {
+ private String s;
+
+ ReplaceMethod(String s) {
+ this.s = s;
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ int argCnt = args.size();
+ checkMethodArgCount(argCnt, 2, 3);
+ String arg1 = (String) args.get(0);
+ String arg2 = (String) args.get(1);
+ long flags = argCnt > 2 ? RegexpHelper.parseFlagString((String) args.get(2)) : 0;
+ String result;
+ if ((flags & RegexpHelper.RE_FLAG_REGEXP) == 0) {
+ RegexpHelper.checkNonRegexpFlags("replace", flags);
+ result = _StringUtil.replace(s, arg1, arg2,
+ (flags & RegexpHelper.RE_FLAG_CASE_INSENSITIVE) != 0,
+ (flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0);
+ } else {
+ Pattern pattern = RegexpHelper.getPattern(arg1, (int) flags);
+ Matcher matcher = pattern.matcher(s);
+ result = (flags & RegexpHelper.RE_FLAG_FIRST_ONLY) != 0
+ ? matcher.replaceFirst(arg2)
+ : matcher.replaceAll(arg2);
+ }
+ return new SimpleScalar(result);
+ }
+
+ }
+
+ @Override
+ TemplateModel calculateResult(String s, Environment env) throws TemplateModelException {
+ return new ReplaceMethod(s);
+ }
+
+ }
+
+ // Represents the match
+
+ static class RegexMatchModel
+ implements TemplateBooleanModel, TemplateCollectionModel, TemplateSequenceModel {
+ static class MatchWithGroups implements TemplateScalarModel {
+ final String matchedInputPart;
+ final SimpleSequence groupsSeq;
+
+ MatchWithGroups(String input, Matcher matcher) {
+ matchedInputPart = input.substring(matcher.start(), matcher.end());
+ final int grpCount = matcher.groupCount() + 1;
+ groupsSeq = new SimpleSequence(grpCount);
+ for (int i = 0; i < grpCount; i++) {
+ groupsSeq.add(matcher.group(i));
+ }
+ }
+
+ @Override
+ public String getAsString() {
+ return matchedInputPart;
+ }
+ }
+ final Pattern pattern;
+
+ final String input;
+ private Matcher firedEntireInputMatcher;
+ private Boolean entireInputMatched;
+
+ private TemplateSequenceModel entireInputMatchGroups;
+
+ private ArrayList matchingInputParts;
+
+ RegexMatchModel(Pattern pattern, String input) {
+ this.pattern = pattern;
+ this.input = input;
+ }
+
+ @Override
+ public TemplateModel get(int i) throws TemplateModelException {
+ ArrayList matchingInputParts = this.matchingInputParts;
+ if (matchingInputParts == null) {
+ matchingInputParts = getMatchingInputPartsAndStoreResults();
+ }
+ return (TemplateModel) matchingInputParts.get(i);
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ Boolean result = entireInputMatched;
+ return result != null ? result.booleanValue() : isEntrieInputMatchesAndStoreResults();
+ }
+
+ TemplateModel getGroups() {
+ TemplateSequenceModel entireInputMatchGroups = this.entireInputMatchGroups;
+ if (entireInputMatchGroups == null) {
+ Matcher t = firedEntireInputMatcher;
+ if (t == null) {
+ isEntrieInputMatchesAndStoreResults();
+ t = firedEntireInputMatcher;
+ }
+ final Matcher firedEntireInputMatcher = t;
+
+ entireInputMatchGroups = new TemplateSequenceModel() {
+
+ @Override
+ public TemplateModel get(int i) throws TemplateModelException {
+ try {
+ return new SimpleScalar(firedEntireInputMatcher.group(i));
+ } catch (Exception e) {
+ throw new _TemplateModelException(e, "Failed to read match group");
+ }
+ }
+
+ @Override
+ public int size() throws TemplateModelException {
+ try {
+ return firedEntireInputMatcher.groupCount() + 1;
+ } catch (Exception e) {
+ throw new _TemplateModelException(e, "Failed to get match group count");
+ }
+ }
+
+ };
+ this.entireInputMatchGroups = entireInputMatchGroups;
+ }
+ return entireInputMatchGroups;
+ }
+
+ private ArrayList getMatchingInputPartsAndStoreResults() throws TemplateModelException {
+ ArrayList matchingInputParts = new ArrayList();
+
+ Matcher matcher = pattern.matcher(input);
+ while (matcher.find()) {
+ matchingInputParts.add(new MatchWithGroups(input, matcher));
+ }
+
+ this.matchingInputParts = matchingInputParts;
+ return matchingInputParts;
+ }
+
+ private boolean isEntrieInputMatchesAndStoreResults() {
+ Matcher matcher = pattern.matcher(input);
+ boolean matches = matcher.matches();
+ firedEntireInputMatcher = matcher;
+ entireInputMatched = Boolean.valueOf(matches);
+ return matches;
+ }
+
+ @Override
+ public TemplateModelIterator iterator() {
+ final ArrayList matchingInputParts = this.matchingInputParts;
+ if (matchingInputParts == null) {
+ final Matcher matcher = pattern.matcher(input);
+ return new TemplateModelIterator() {
+
+ private int nextIdx = 0;
+ boolean hasFindInfo = matcher.find();
+
+ @Override
+ public boolean hasNext() {
+ final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts;
+ if (matchingInputParts == null) {
+ return hasFindInfo;
+ } else {
+ return nextIdx < matchingInputParts.size();
+ }
+ }
+
+ @Override
+ public TemplateModel next() throws TemplateModelException {
+ final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts;
+ if (matchingInputParts == null) {
+ if (!hasFindInfo) throw new _TemplateModelException("There were no more matches");
+ MatchWithGroups result = new MatchWithGroups(input, matcher);
+ nextIdx++;
+ hasFindInfo = matcher.find();
+ return result;
+ } else {
+ try {
+ return (TemplateModel) matchingInputParts.get(nextIdx++);
+ } catch (IndexOutOfBoundsException e) {
+ throw new _TemplateModelException(e, "There were no more matches");
+ }
+ }
+ }
+
+ };
+ } else {
+ return new TemplateModelIterator() {
+
+ private int nextIdx = 0;
+
+ @Override
+ public boolean hasNext() {
+ return nextIdx < matchingInputParts.size();
+ }
+
+ @Override
+ public TemplateModel next() throws TemplateModelException {
+ try {
+ return (TemplateModel) matchingInputParts.get(nextIdx++);
+ } catch (IndexOutOfBoundsException e) {
+ throw new _TemplateModelException(e, "There were no more matches");
+ }
+ }
+ };
+ }
+ }
+
+ @Override
+ public int size() throws TemplateModelException {
+ ArrayList matchingInputParts = this.matchingInputParts;
+ if (matchingInputParts == null) {
+ matchingInputParts = getMatchingInputPartsAndStoreResults();
+ }
+ return matchingInputParts.size();
+ }
+ }
+
+ // Can't be instantiated
+ private BuiltInsForStringsRegexp() { }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java
new file mode 100644
index 0000000..a3b5689
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java
@@ -0,0 +1,158 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.apache.freemarker.core.Token;
+import org.apache.freemarker.core.model.TemplateModel;
+
+
+final class BuiltInsWithParseTimeParameters {
+
+ /**
+ * Behaves similarly to the ternary operator of Java.
+ */
+ static class then_BI extends BuiltInWithParseTimeParameters {
+
+ private ASTExpression whenTrueExp;
+ private ASTExpression whenFalseExp;
+
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ boolean lho = target.evalToBoolean(env);
+ return (lho ? whenTrueExp : whenFalseExp).evalToNonMissing(env);
+ }
+
+ @Override
+ void bindToParameters(List parameters, Token openParen, Token closeParen) throws ParseException {
+ if (parameters.size() != 2) {
+ throw newArgumentCountException("requires exactly 2", openParen, closeParen);
+ }
+ whenTrueExp = (ASTExpression) parameters.get(0);
+ whenFalseExp = (ASTExpression) parameters.get(1);
+ }
+
+ @Override
+ protected ASTExpression getArgumentParameterValue(final int argIdx) {
+ switch (argIdx) {
+ case 0: return whenTrueExp;
+ case 1: return whenFalseExp;
+ default: throw new IndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ protected int getArgumentsCount() {
+ return 2;
+ }
+
+ @Override
+ protected List getArgumentsAsList() {
+ ArrayList args = new ArrayList(2);
+ args.add(whenTrueExp);
+ args.add(whenFalseExp);
+ return args;
+ }
+
+ @Override
+ protected void cloneArguments(ASTExpression cloneExp, String replacedIdentifier,
+ ASTExpression replacement, ReplacemenetState replacementState) {
+ then_BI clone = (then_BI) cloneExp;
+ clone.whenTrueExp = whenTrueExp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState);
+ clone.whenFalseExp = whenFalseExp.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState);
+ }
+
+ }
+
+ private BuiltInsWithParseTimeParameters() {
+ // Not to be instantiated
+ }
+
+ static class switch_BI extends BuiltInWithParseTimeParameters {
+
+ private List/*<ASTExpression>*/ parameters;
+
+ @Override
+ void bindToParameters(List parameters, Token openParen, Token closeParen) throws ParseException {
+ if (parameters.size() < 2) {
+ throw newArgumentCountException("must have at least 2", openParen, closeParen);
+ }
+ this.parameters = parameters;
+ }
+
+ @Override
+ protected List getArgumentsAsList() {
+ return parameters;
+ }
+
+ @Override
+ protected int getArgumentsCount() {
+ return parameters.size();
+ }
+
+ @Override
+ protected ASTExpression getArgumentParameterValue(int argIdx) {
+ return (ASTExpression) parameters.get(argIdx);
+ }
+
+ @Override
+ protected void cloneArguments(ASTExpression clone, String replacedIdentifier, ASTExpression replacement,
+ ReplacemenetState replacementState) {
+ ArrayList parametersClone = new ArrayList(parameters.size());
+ for (int i = 0; i < parameters.size(); i++) {
+ parametersClone.add(((ASTExpression) parameters.get(i))
+ .deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));
+ }
+ ((switch_BI) clone).parameters = parametersClone;
+ }
+
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ TemplateModel targetValue = target.evalToNonMissing(env);
+
+ List parameters = this.parameters;
+ int paramCnt = parameters.size();
+ for (int i = 0; i + 1 < paramCnt; i += 2) {
+ ASTExpression caseExp = (ASTExpression) parameters.get(i);
+ TemplateModel caseValue = caseExp.evalToNonMissing(env);
+ if (EvalUtil.compare(
+ targetValue, target,
+ EvalUtil.CMP_OP_EQUALS, "==",
+ caseValue, caseExp,
+ this, true,
+ false, false, false,
+ env)) {
+ return ((ASTExpression) parameters.get(i + 1)).evalToNonMissing(env);
+ }
+ }
+
+ if (paramCnt % 2 == 0) {
+ throw new _MiscTemplateException(target,
+ "The value before ?", key, "(case1, value1, case2, value2, ...) didn't match any of the "
+ + "case parameters, and there was no default value parameter (an additional last parameter) "
+ + "eithter. ");
+ }
+ return ((ASTExpression) parameters.get(paramCnt - 1)).evalToNonMissing(env);
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java
new file mode 100644
index 0000000..69a2465
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CSSOutputFormat.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Represents the CSS output format (MIME type "text/css", name "CSS"). This format doesn't support escaping.
+ *
+ * @since 2.3.24
+ */
+public class CSSOutputFormat extends OutputFormat {
+
+ /**
+ * The only instance (singleton) of this {@link OutputFormat}.
+ */
+ public static final CSSOutputFormat INSTANCE = new CSSOutputFormat();
+
+ private CSSOutputFormat() {
+ // Only to decrease visibility
+ }
+
+ @Override
+ public String getName() {
+ return "CSS";
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/css";
+ }
+
+ @Override
+ public boolean isOutputFormatMixingAllowed() {
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
new file mode 100644
index 0000000..0aebeab
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Thrown by {@link DirectiveCallPlace#getOrCreateCustomData(Object, org.apache.freemarker.core.util.ObjectFactory)}
+ *
+ * @since 2.3.22
+ */
+public class CallPlaceCustomDataInitializationException extends Exception {
+
+ public CallPlaceCustomDataInitializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java
new file mode 100644
index 0000000..d629547
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CombinedMarkupOutputFormat.java
@@ -0,0 +1,106 @@
+/*
+ * 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.TemplateModelException;
+
+/**
+ * Represents two markup formats nested into each other. For example, markdown nested into HTML.
+ *
+ * @since 2.3.24
+ */
+public final class CombinedMarkupOutputFormat extends CommonMarkupOutputFormat<TemplateCombinedMarkupOutputModel> {
+
+ private final String name;
+
+ private final MarkupOutputFormat outer;
+ private final MarkupOutputFormat inner;
+
+ /**
+ * Same as {@link #CombinedMarkupOutputFormat(String, MarkupOutputFormat, MarkupOutputFormat)} with {@code null} as
+ * the {@code name} parameter.
+ */
+ public CombinedMarkupOutputFormat(MarkupOutputFormat outer, MarkupOutputFormat inner) {
+ this(null, outer, inner);
+ }
+
+ /**
+ * @param name
+ * Maybe {@code null}, in which case it defaults to
+ * <code>outer.getName() + "{" + inner.getName() + "}"</code>.
+ */
+ public CombinedMarkupOutputFormat(String name, MarkupOutputFormat outer, MarkupOutputFormat inner) {
+ this.name = name != null ? null : outer.getName() + "{" + inner.getName() + "}";
+ this.outer = outer;
+ this.inner = inner;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getMimeType() {
+ return outer.getMimeType();
+ }
+
+ @Override
+ public void output(String textToEsc, Writer out) throws IOException, TemplateModelException {
+ outer.output(inner.escapePlainText(textToEsc), out);
+ }
+
+ @Override
+ public String escapePlainText(String plainTextContent) throws TemplateModelException {
+ return outer.escapePlainText(inner.escapePlainText(plainTextContent));
+ }
+
+ @Override
+ public boolean isLegacyBuiltInBypassed(String builtInName) throws TemplateModelException {
+ return outer.isLegacyBuiltInBypassed(builtInName);
+ }
+
+ @Override
+ public boolean isAutoEscapedByDefault() {
+ return outer.isAutoEscapedByDefault();
+ }
+
+ @Override
+ public boolean isOutputFormatMixingAllowed() {
+ return outer.isOutputFormatMixingAllowed();
+ }
+
+ public MarkupOutputFormat getOuterOutputFormat() {
+ return outer;
+ }
+
+ public MarkupOutputFormat getInnerOutputFormat() {
+ return inner;
+ }
+
+ @Override
+ protected TemplateCombinedMarkupOutputModel newTemplateMarkupOutputModel(
+ String plainTextContent, String markupContent) {
+ return new TemplateCombinedMarkupOutputModel(plainTextContent, markupContent, this);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java
new file mode 100644
index 0000000..cde0163
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CommonMarkupOutputFormat.java
@@ -0,0 +1,124 @@
+/*
+ * 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.TemplateModelException;
+
+/**
+ * Common superclass for implementing {@link MarkupOutputFormat}-s that use a {@link CommonTemplateMarkupOutputModel}
+ * subclass.
+ *
+ * @since 2.3.24
+ */
+public abstract class CommonMarkupOutputFormat<MO extends CommonTemplateMarkupOutputModel>
+ extends MarkupOutputFormat<MO> {
+
+ protected CommonMarkupOutputFormat() {
+ // Only to decrease visibility
+ }
+
+ @Override
+ public final MO fromPlainTextByEscaping(String textToEsc) throws TemplateModelException {
+ return newTemplateMarkupOutputModel(textToEsc, null);
+ }
+
+ @Override
+ public final MO fromMarkup(String markupText) throws TemplateModelException {
+ return newTemplateMarkupOutputModel(null, markupText);
+ }
+
+ @Override
+ public final void output(MO mo, Writer out) throws IOException, TemplateModelException {
+ String mc = mo.getMarkupContent();
+ if (mc != null) {
+ out.write(mc);
+ } else {
+ output(mo.getPlainTextContent(), out);
+ }
+ }
+
+ @Override
+ public abstract void output(String textToEsc, Writer out) throws IOException, TemplateModelException;
+
+ @Override
+ public final String getSourcePlainText(MO mo) throws TemplateModelException {
+ return mo.getPlainTextContent();
+ }
+
+ @Override
+ public final String getMarkupString(MO mo) throws TemplateModelException {
+ String mc = mo.getMarkupContent();
+ if (mc != null) {
+ return mc;
+ }
+
+ mc = escapePlainText(mo.getPlainTextContent());
+ mo.setMarkupContent(mc);
+ return mc;
+ }
+
+ @Override
+ public final MO concat(MO mo1, MO mo2) throws TemplateModelException {
+ String pc1 = mo1.getPlainTextContent();
+ String mc1 = mo1.getMarkupContent();
+ String pc2 = mo2.getPlainTextContent();
+ String mc2 = mo2.getMarkupContent();
+
+ String pc3 = pc1 != null && pc2 != null ? pc1 + pc2 : null;
+ String mc3 = mc1 != null && mc2 != null ? mc1 + mc2 : null;
+ if (pc3 != null || mc3 != null) {
+ return newTemplateMarkupOutputModel(pc3, mc3);
+ }
+
+ if (pc1 != null) {
+ return newTemplateMarkupOutputModel(null, getMarkupString(mo1) + mc2);
+ } else {
+ return newTemplateMarkupOutputModel(null, mc1 + getMarkupString(mo2));
+ }
+ }
+
+ @Override
+ public boolean isEmpty(MO mo) throws TemplateModelException {
+ String s = mo.getPlainTextContent();
+ if (s != null) {
+ return s.length() == 0;
+ }
+ return mo.getMarkupContent().length() == 0;
+ }
+
+ @Override
+ public boolean isOutputFormatMixingAllowed() {
+ return false;
+ }
+
+ @Override
+ public boolean isAutoEscapedByDefault() {
+ return true;
+ }
+
+ /**
+ * Creates a new {@link CommonTemplateMarkupOutputModel} that's bound to this {@link OutputFormat} instance.
+ */
+ protected abstract MO newTemplateMarkupOutputModel(String plainTextContent, String markupContent)
+ throws TemplateModelException;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java
new file mode 100644
index 0000000..809c248
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CommonTemplateMarkupOutputModel.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+/**
+ * Common superclass for implementing {@link TemplateMarkupOutputModel}-s that belong to a
+ * {@link CommonMarkupOutputFormat} subclass format.
+ *
+ * <p>
+ * Thread-safe after proper publishing. Calculated fields (typically, the markup calculated from plain text) might will
+ * be re-calculated for multiple times if accessed from multiple threads (this only affects performance, not
+ * functionality).
+ *
+ * @since 2.3.24
+ */
+public abstract class CommonTemplateMarkupOutputModel<MO extends CommonTemplateMarkupOutputModel<MO>>
+ implements TemplateMarkupOutputModel<MO> {
+
+ private final String plainTextContent;
+ private String markupContent;
+
+ /**
+ * A least one of the parameters must be non-{@code null}!
+ */
+ protected CommonTemplateMarkupOutputModel(String plainTextContent, String markupContent) {
+ this.plainTextContent = plainTextContent;
+ this.markupContent = markupContent;
+ }
+
+ @Override
+ public abstract CommonMarkupOutputFormat<MO> getOutputFormat();
+
+ /** Maybe {@code null}, but then {@link #getMarkupContent()} isn't {@code null}. */
+ final String getPlainTextContent() {
+ return plainTextContent;
+ }
+
+ /** Maybe {@code null}, but then {@link #getPlainTextContent()} isn't {@code null}. */
+ final String getMarkupContent() {
+ return markupContent;
+ }
+
+ /**
+ * Use only to set the value calculated from {@link #getPlainTextContent()}, when {@link #getMarkupContent()} was
+ * still {@code null}!
+ */
+ final void setMarkupContent(String markupContent) {
+ this.markupContent = markupContent;
+ }
+
+}