http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java new file mode 100644 index 0000000..0420d64 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java @@ -0,0 +1,322 @@ +/* + * 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.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 new NativeStringArraySequence(((RegexMatchModel.MatchWithGroups) targetModel).groups); + + } 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 String[] groups; + + MatchWithGroups(String input, Matcher matcher) { + matchedInputPart = input.substring(matcher.start(), matcher.end()); + final int grpCount = matcher.groupCount() + 1; + groups = new String[grpCount]; + for (int i = 0; i < grpCount; i++) { + groups[i] = 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 { + // Avoid IndexOutOfBoundsException: + if (i > firedEntireInputMatcher.groupCount()) { + return null; + } + + 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java new file mode 100644 index 0000000..1126410 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsWithParseTimeParameters.java @@ -0,0 +1,157 @@ +/* + * 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.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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java new file mode 100644 index 0000000..ffaa2b0 --- /dev/null +++ b/freemarker-core/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); + } + +}
