001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.core;
054:
055: import java.util.*;
056: import java.util.regex.*;
057: import freemarker.template.*;
058: import freemarker.template.utility.StringUtil;
059:
060: /**
061: * This is a first-pass implementation of regular expression support.
062: * It is subject to change based on community feedback.
063: * In that sense, use it at your own risk.
064: * @version $Id: RegexBuiltins.java,v 1.14 2005/06/01 22:39:07 ddekany Exp $
065: * @author Jonathan Revusky
066: */
067: abstract class RegexBuiltins {
068:
069: static HashMap patternLookup = new HashMap();
070: static LinkedList patterns = new LinkedList();
071: static final int PATTERN_CACHE_SIZE = 100;
072:
073: static Pattern getPattern(String patternString, String flagString)
074: throws TemplateModelException {
075: int flags = 0;
076: String patternKey = patternString + (char) 0 + flagString;
077: Pattern result = (Pattern) patternLookup.get(patternKey);
078: if (result != null) {
079: return result;
080: }
081: if (flagString == null || flagString.length() == 0) {
082: try {
083: result = Pattern.compile(patternString);
084: } catch (PatternSyntaxException e) {
085: throw new TemplateModelException(e);
086: }
087: } else {
088: if (flagString.indexOf('i') >= 0) {
089: flags = flags | Pattern.CASE_INSENSITIVE;
090: }
091: if (flagString.indexOf('m') >= 0) {
092: flags = flags | Pattern.MULTILINE;
093: }
094: if (flagString.indexOf('c') >= 0) {
095: flags = flags | Pattern.COMMENTS;
096: }
097: if (flagString.indexOf('s') >= 0) {
098: flags = flags | Pattern.DOTALL;
099: }
100: try {
101: result = Pattern.compile(patternString, flags);
102: } catch (PatternSyntaxException e) {
103: throw new TemplateModelException(e);
104: }
105: }
106: patterns.add(patternKey);
107: patternLookup.put(patternKey, result);
108: if (patterns.size() > PATTERN_CACHE_SIZE) {
109: Object first = patterns.removeFirst();
110: patterns.remove(first);
111: }
112: return result;
113: }
114:
115: static class matchesBI extends BuiltIn {
116: TemplateModel _getAsTemplateModel(Environment env)
117: throws TemplateException {
118: TemplateModel targetModel = target.getAsTemplateModel(env);
119: assertNonNull(targetModel, this , env);
120: if (!(targetModel instanceof TemplateScalarModel)) {
121: throw invalidTypeException(targetModel, target, env,
122: "string");
123: }
124: return new MatcherBuilder((TemplateScalarModel) targetModel);
125: }
126: }
127:
128: static class groupsBI extends BuiltIn {
129: TemplateModel _getAsTemplateModel(Environment env)
130: throws TemplateException {
131: TemplateModel targetModel = target.getAsTemplateModel(env);
132: assertNonNull(targetModel, this , env);
133: if (targetModel instanceof RegexMatchModel) {
134: return ((RegexMatchModel) targetModel).getGroups();
135: }
136: if (targetModel instanceof RegexMatchModel.Match) {
137: return ((RegexMatchModel.Match) targetModel).subs;
138: }
139: throw invalidTypeException(targetModel, target, env,
140: "a regular expression matcher");
141: }
142: }
143:
144: static class replace_reBI extends BuiltIn {
145: TemplateModel _getAsTemplateModel(Environment env)
146: throws TemplateException {
147: TemplateModel model = target.getAsTemplateModel(env);
148: if (model instanceof TemplateScalarModel) {
149: return new ReplaceMethod(((TemplateScalarModel) model)
150: .getAsString());
151: }
152: throw invalidTypeException(model, target, env, "string");
153: }
154: }
155:
156: static class split_reBI extends BuiltIn {
157: TemplateModel _getAsTemplateModel(Environment env)
158: throws TemplateException {
159: TemplateModel model = target.getAsTemplateModel(env);
160: if (model instanceof TemplateScalarModel) {
161: return new SplitMethod(((TemplateScalarModel) model)
162: .getAsString());
163: }
164: throw invalidTypeException(model, target, env, "string");
165: }
166: }
167:
168: // Represents the match
169:
170: static class RegexMatchModel implements TemplateBooleanModel,
171: TemplateCollectionModel, TemplateSequenceModel {
172: Matcher matcher;
173: String input, matchedString;
174: final boolean matches;
175: TemplateSequenceModel groups;
176: private ArrayList data;
177:
178: RegexMatchModel(Matcher matcher, String input) {
179: this .matcher = matcher;
180: this .input = input;
181: this .matches = matcher.matches();
182: if (matches) {
183: matchedString = input.substring(matcher.start(),
184: matcher.end());
185: }
186: }
187:
188: public boolean getAsBoolean() {
189: return matches;
190: }
191:
192: public TemplateModel get(int i) throws TemplateModelException {
193: if (data == null)
194: initSequence();
195: return (TemplateModel) data.get(i);
196: }
197:
198: public int size() throws TemplateModelException {
199: if (data == null)
200: initSequence();
201: return data.size();
202: }
203:
204: private void initSequence() throws TemplateModelException {
205: data = new ArrayList();
206: TemplateModelIterator it = iterator();
207: while (it.hasNext()) {
208: data.add(it.next());
209: }
210: }
211:
212: public TemplateModel getGroups() {
213: if (groups == null) {
214: groups = new TemplateSequenceModel() {
215: public int size() throws TemplateModelException {
216: try {
217: return matcher.groupCount() + 1;
218: } catch (Exception e) {
219: throw new TemplateModelException(e);
220: }
221: }
222:
223: public TemplateModel get(int i)
224: throws TemplateModelException {
225: try {
226: return new SimpleScalar(matcher.group(i));
227: } catch (Exception e) {
228: throw new TemplateModelException(e);
229: }
230: }
231: };
232: }
233: return groups;
234: }
235:
236: public TemplateModelIterator iterator() {
237: matcher.reset();
238: return new TemplateModelIterator() {
239: boolean hasFindInfo = matcher.find();
240:
241: public boolean hasNext() {
242: return hasFindInfo;
243: }
244:
245: public TemplateModel next()
246: throws TemplateModelException {
247: if (!hasNext())
248: throw new TemplateModelException(
249: "No more matches");
250: Match result = new Match();
251: hasFindInfo = matcher.find();
252: return result;
253: }
254: };
255: }
256:
257: class Match implements TemplateScalarModel {
258: String match;
259: SimpleSequence subs = new SimpleSequence();
260:
261: Match() {
262: match = input.substring(matcher.start(), matcher.end());
263: for (int i = 0; i < matcher.groupCount() + 1; i++) {
264: subs.add(matcher.group(i));
265: }
266: }
267:
268: public String getAsString() {
269: return match;
270: }
271: }
272: }
273:
274: static class MatcherBuilder implements TemplateMethodModel {
275:
276: String matchString;
277:
278: MatcherBuilder(TemplateScalarModel match)
279: throws TemplateModelException {
280: this .matchString = match.getAsString();
281: }
282:
283: public Object exec(List args) throws TemplateModelException {
284: int numArgs = args.size();
285: if (numArgs == 0) {
286: throw new TemplateModelException(
287: "Expecting at least one argument");
288: }
289: if (numArgs > 2) {
290: throw new TemplateModelException(
291: "Expecting at most two argumnets");
292: }
293: String patternString = (String) args.get(0);
294: String flagString = (numArgs > 1) ? (String) args.get(1)
295: : "";
296: Pattern pattern = getPattern(patternString, flagString);
297: Matcher matcher = pattern.matcher(matchString);
298: return new RegexMatchModel(matcher, matchString);
299: }
300: }
301:
302: static class ReplaceMethod implements TemplateMethodModel {
303: private String s;
304:
305: ReplaceMethod(String s) {
306: this .s = s;
307: }
308:
309: public Object exec(List args) throws TemplateModelException {
310: int numArgs = args.size();
311: if (numArgs < 2 || numArgs > 3) {
312: throw new TemplateModelException(
313: "?replace(...) needs 2 or 3 arguments.");
314: }
315: String first = (String) args.get(0);
316: String second = (String) args.get(1);
317: String flags = numArgs > 2 ? (String) args.get(2) : "";
318: boolean caseInsensitive = flags.indexOf('i') >= 0;
319: boolean useRegexp = flags.indexOf('r') >= 0;
320: boolean firstOnly = flags.indexOf('f') >= 0;
321: String result = null;
322: if (!useRegexp) {
323: result = StringUtil.replace(s, first, second,
324: caseInsensitive, firstOnly);
325: } else {
326: Pattern pattern = getPattern(first, flags);
327: Matcher matcher = pattern.matcher(s);
328: result = firstOnly ? matcher.replaceFirst(second)
329: : matcher.replaceAll(second);
330: }
331: return new SimpleScalar(result);
332: }
333: }
334:
335: static class SplitMethod implements TemplateMethodModel {
336: private String s;
337:
338: SplitMethod(String s) {
339: this .s = s;
340: }
341:
342: public Object exec(List args) throws TemplateModelException {
343: int numArgs = args.size();
344: if (numArgs < 1 || numArgs > 2) {
345: throw new TemplateModelException(
346: "?replace(...) needs 1 or 2 arguments.");
347: }
348: String splitString = (String) args.get(0);
349: String flags = numArgs > 1 ? (String) args.get(1) : "";
350: boolean caseInsensitive = flags.indexOf('i') >= 0;
351: boolean useRegexp = flags.indexOf('r') >= 0;
352: String[] result = null;
353: if (!useRegexp) {
354: result = StringUtil.split(s, splitString,
355: caseInsensitive);
356: } else {
357: Pattern pattern = getPattern(splitString, flags);
358: result = pattern.split(s);
359: }
360: return ObjectWrapper.DEFAULT_WRAPPER.wrap(result);
361: }
362: }
363: }
|