001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.filters;
019:
020: import java.io.IOException;
021: import java.io.Reader;
022: import java.util.Vector;
023: import java.util.Enumeration;
024: import org.apache.tools.ant.BuildException;
025: import org.apache.tools.ant.ProjectComponent;
026: import org.apache.tools.ant.types.RegularExpression;
027: import org.apache.tools.ant.types.Substitution;
028: import org.apache.tools.ant.util.Tokenizer;
029: import org.apache.tools.ant.util.LineTokenizer;
030: import org.apache.tools.ant.util.StringUtils;
031: import org.apache.tools.ant.util.regexp.Regexp;
032:
033: /**
034: * This splits up input into tokens and passes
035: * the tokens to a sequence of filters.
036: *
037: * @since Ant 1.6
038: * @see BaseFilterReader
039: * @see ChainableReader
040: * @see org.apache.tools.ant.DynamicConfigurator
041: */
042: public class TokenFilter extends BaseFilterReader implements
043: ChainableReader {
044: /**
045: * string filters implement this interface
046: */
047: public interface Filter {
048: /**
049: * filter and/of modify a string
050: *
051: * @param string the string to filter
052: * @return the modified string or null if the
053: * string did not pass the filter
054: */
055: String filter(String string);
056: }
057:
058: /** string filters */
059: private Vector filters = new Vector();
060: /** the tokenizer to use on the input stream */
061: private Tokenizer tokenizer = null;
062: /** the output token termination */
063: private String delimOutput = null;
064: /** the current string token from the input stream */
065: private String line = null;
066: /** the position in the current string token */
067: private int linePos = 0;
068:
069: /**
070: * Constructor for "dummy" instances.
071: *
072: * @see BaseFilterReader#BaseFilterReader()
073: */
074: public TokenFilter() {
075: super ();
076: }
077:
078: /**
079: * Creates a new filtered reader.
080: *
081: * @param in A Reader object providing the underlying stream.
082: * Must not be <code>null</code>.
083: */
084: public TokenFilter(final Reader in) {
085: super (in);
086: }
087:
088: /**
089: * Returns the next character in the filtered stream, only including
090: * lines from the original stream which match all of the specified
091: * regular expressions.
092: *
093: * @return the next character in the resulting stream, or -1
094: * if the end of the resulting stream has been reached
095: *
096: * @exception IOException if the underlying stream throws an IOException
097: * during reading
098: */
099:
100: public int read() throws IOException {
101: if (tokenizer == null) {
102: tokenizer = new LineTokenizer();
103: }
104: while (line == null || line.length() == 0) {
105: line = tokenizer.getToken(in);
106: if (line == null) {
107: return -1;
108: }
109: for (Enumeration e = filters.elements(); e
110: .hasMoreElements();) {
111: Filter filter = (Filter) e.nextElement();
112: line = filter.filter(line);
113: if (line == null) {
114: break;
115: }
116: }
117: linePos = 0;
118: if (line != null) {
119: if (tokenizer.getPostToken().length() != 0) {
120: if (delimOutput != null) {
121: line = line + delimOutput;
122: } else {
123: line = line + tokenizer.getPostToken();
124: }
125: }
126: }
127: }
128: int ch = line.charAt(linePos);
129: linePos++;
130: if (linePos == line.length()) {
131: line = null;
132: }
133: return ch;
134: }
135:
136: /**
137: * Creates a new TokenFilter using the passed in
138: * Reader for instantiation.
139: *
140: * @param reader A Reader object providing the underlying stream.
141: *
142: * @return a new filter based on this configuration
143: */
144:
145: public final Reader chain(final Reader reader) {
146: TokenFilter newFilter = new TokenFilter(reader);
147: newFilter.filters = filters;
148: newFilter.tokenizer = tokenizer;
149: newFilter.delimOutput = delimOutput;
150: newFilter.setProject(getProject());
151: return newFilter;
152: }
153:
154: /**
155: * set the output delimiter.
156: * @param delimOutput replaces the delim string returned by the
157: * tokenizer, if present.
158: */
159:
160: public void setDelimOutput(String delimOutput) {
161: this .delimOutput = resolveBackSlash(delimOutput);
162: }
163:
164: // -----------------------------------------
165: // Predefined tokenizers
166: // -----------------------------------------
167:
168: /**
169: * add a line tokenizer - this is the default.
170: * @param tokenizer the line tokenizer
171: */
172:
173: public void addLineTokenizer(LineTokenizer tokenizer) {
174: add(tokenizer);
175: }
176:
177: /**
178: * add a string tokenizer
179: * @param tokenizer the string tokenizer
180: */
181:
182: public void addStringTokenizer(StringTokenizer tokenizer) {
183: add(tokenizer);
184: }
185:
186: /**
187: * add a file tokenizer
188: * @param tokenizer the file tokenizer
189: */
190: public void addFileTokenizer(FileTokenizer tokenizer) {
191: add(tokenizer);
192: }
193:
194: /**
195: * add an arbitrary tokenizer
196: * @param tokenizer the tokenizer to all, only one allowed
197: */
198:
199: public void add(Tokenizer tokenizer) {
200: if (this .tokenizer != null) {
201: throw new BuildException("Only one tokenizer allowed");
202: }
203: this .tokenizer = tokenizer;
204: }
205:
206: // -----------------------------------------
207: // Predefined filters
208: // -----------------------------------------
209:
210: /**
211: * replace string filter
212: * @param filter the replace string filter
213: */
214: public void addReplaceString(ReplaceString filter) {
215: filters.addElement(filter);
216: }
217:
218: /**
219: * contains string filter
220: * @param filter the contains string filter
221: */
222: public void addContainsString(ContainsString filter) {
223: filters.addElement(filter);
224: }
225:
226: /**
227: * replace regex filter
228: * @param filter the replace regex filter
229: */
230: public void addReplaceRegex(ReplaceRegex filter) {
231: filters.addElement(filter);
232: }
233:
234: /**
235: * contains regex filter
236: * @param filter the contains regex filter
237: */
238: public void addContainsRegex(ContainsRegex filter) {
239: filters.addElement(filter);
240: }
241:
242: /**
243: * trim filter
244: * @param filter the trim filter
245: */
246: public void addTrim(Trim filter) {
247: filters.addElement(filter);
248: }
249:
250: /**
251: * ignore blank filter
252: * @param filter the ignore blank filter
253: */
254: public void addIgnoreBlank(IgnoreBlank filter) {
255: filters.addElement(filter);
256: }
257:
258: /**
259: * delete chars
260: * @param filter the delete characters filter
261: */
262: public void addDeleteCharacters(DeleteCharacters filter) {
263: filters.addElement(filter);
264: }
265:
266: /**
267: * Add an arbitrary filter
268: * @param filter the filter to add
269: */
270: public void add(Filter filter) {
271: filters.addElement(filter);
272: }
273:
274: // --------------------------------------------
275: //
276: // Tokenizer Classes (impls moved to oata.util)
277: //
278: // --------------------------------------------
279:
280: /**
281: * class to read the complete input into a string
282: */
283: public static class FileTokenizer extends
284: org.apache.tools.ant.util.FileTokenizer {
285: }
286:
287: /**
288: * class to tokenize the input as areas separated
289: * by white space, or by a specified list of
290: * delim characters. Behaves like java.util.StringTokenizer.
291: * if the stream starts with delim characters, the first
292: * token will be an empty string (unless the treat delims
293: * as tokens flag is set).
294: */
295: public static class StringTokenizer extends
296: org.apache.tools.ant.util.StringTokenizer {
297: }
298:
299: // --------------------------------------------
300: //
301: // Filter classes
302: //
303: // --------------------------------------------
304:
305: /**
306: * Abstract class that converts derived filter classes into
307: * ChainableReaderFilter's
308: */
309: public abstract static class ChainableReaderFilter extends
310: ProjectComponent implements ChainableReader, Filter {
311: private boolean byLine = true;
312:
313: /**
314: * set whether to use filetokenizer or line tokenizer
315: * @param byLine if true use a linetokenizer (default) otherwise
316: * use a filetokenizer
317: */
318: public void setByLine(boolean byLine) {
319: this .byLine = byLine;
320: }
321:
322: /**
323: * Chain a tokenfilter reader to a reader,
324: *
325: * @param reader the input reader object
326: * @return the chained reader object
327: */
328: public Reader chain(Reader reader) {
329: TokenFilter tokenFilter = new TokenFilter(reader);
330: if (!byLine) {
331: tokenFilter.add(new FileTokenizer());
332: }
333: tokenFilter.add(this );
334: return tokenFilter;
335: }
336: }
337:
338: /**
339: * Simple replace string filter.
340: */
341: public static class ReplaceString extends ChainableReaderFilter {
342: private String from;
343: private String to;
344:
345: /**
346: * the from attribute
347: *
348: * @param from the string to replace
349: */
350: public void setFrom(String from) {
351: this .from = from;
352: }
353:
354: /**
355: * the to attribute
356: *
357: * @param to the string to replace 'from' with
358: */
359: public void setTo(String to) {
360: this .to = to;
361: }
362:
363: /**
364: * Filter a string 'line' replacing from with to
365: * (C&P from the Replace task)
366: * @param line the string to be filtered
367: * @return the filtered line
368: */
369: public String filter(String line) {
370: if (from == null) {
371: throw new BuildException(
372: "Missing from in stringreplace");
373: }
374: StringBuffer ret = new StringBuffer();
375: int start = 0;
376: int found = line.indexOf(from);
377: while (found >= 0) {
378: // write everything up to the from
379: if (found > start) {
380: ret.append(line.substring(start, found));
381: }
382:
383: // write the replacement to
384: if (to != null) {
385: ret.append(to);
386: }
387:
388: // search again
389: start = found + from.length();
390: found = line.indexOf(from, start);
391: }
392:
393: // write the remaining characters
394: if (line.length() > start) {
395: ret.append(line.substring(start, line.length()));
396: }
397:
398: return ret.toString();
399: }
400: }
401:
402: /**
403: * Simple filter to filter lines contains strings
404: */
405: public static class ContainsString extends ProjectComponent
406: implements Filter {
407: private String contains;
408:
409: /**
410: * the contains attribute
411: * @param contains the string that the token should contain
412: */
413: public void setContains(String contains) {
414: this .contains = contains;
415: }
416:
417: /**
418: * Filter strings that contain the contains attribute
419: *
420: * @param string the string to be filtered
421: * @return null if the string does not contain "contains",
422: * string otherwise
423: */
424: public String filter(String string) {
425: if (contains == null) {
426: throw new BuildException(
427: "Missing contains in containsstring");
428: }
429: if (string.indexOf(contains) > -1) {
430: return string;
431: }
432: return null;
433: }
434: }
435:
436: /**
437: * filter to replace regex.
438: */
439: public static class ReplaceRegex extends ChainableReaderFilter {
440: private String from;
441: private String to;
442: private RegularExpression regularExpression;
443: private Substitution substitution;
444: private boolean initialized = false;
445: private String flags = "";
446: private int options;
447: private Regexp regexp;
448:
449: /**
450: * the from attribute
451: * @param from the regex string
452: */
453: public void setPattern(String from) {
454: this .from = from;
455: }
456:
457: /**
458: * the to attribute
459: * @param to the replacement string
460: */
461: public void setReplace(String to) {
462: this .to = to;
463: }
464:
465: /**
466: * @param flags the regex flags
467: */
468: public void setFlags(String flags) {
469: this .flags = flags;
470: }
471:
472: private void initialize() {
473: if (initialized) {
474: return;
475: }
476: options = convertRegexOptions(flags);
477: if (from == null) {
478: throw new BuildException(
479: "Missing pattern in replaceregex");
480: }
481: regularExpression = new RegularExpression();
482: regularExpression.setPattern(from);
483: regexp = regularExpression.getRegexp(getProject());
484: if (to == null) {
485: to = "";
486: }
487: substitution = new Substitution();
488: substitution.setExpression(to);
489: }
490:
491: /**
492: * @param line the string to modify
493: * @return the modified string
494: */
495: public String filter(String line) {
496: initialize();
497:
498: if (!regexp.matches(line, options)) {
499: return line;
500: }
501: return regexp.substitute(line, substitution
502: .getExpression(getProject()), options);
503: }
504: }
505:
506: /**
507: * filter to filter tokens matching regular expressions.
508: */
509: public static class ContainsRegex extends ChainableReaderFilter {
510: private String from;
511: private String to;
512: private RegularExpression regularExpression;
513: private Substitution substitution;
514: private boolean initialized = false;
515: private String flags = "";
516: private int options;
517: private Regexp regexp;
518:
519: /**
520: * @param from the regex pattern
521: */
522: public void setPattern(String from) {
523: this .from = from;
524: }
525:
526: /**
527: * @param to the replacement string
528: */
529: public void setReplace(String to) {
530: this .to = to;
531: }
532:
533: /**
534: * @param flags the regex flags
535: */
536: public void setFlags(String flags) {
537: this .flags = flags;
538: }
539:
540: private void initialize() {
541: if (initialized) {
542: return;
543: }
544: options = convertRegexOptions(flags);
545: if (from == null) {
546: throw new BuildException(
547: "Missing from in containsregex");
548: }
549: regularExpression = new RegularExpression();
550: regularExpression.setPattern(from);
551: regexp = regularExpression.getRegexp(getProject());
552: if (to == null) {
553: return;
554: }
555: substitution = new Substitution();
556: substitution.setExpression(to);
557: }
558:
559: /**
560: * apply regex and substitution on a string
561: * @param string the string to apply filter on
562: * @return the filtered string
563: */
564: public String filter(String string) {
565: initialize();
566: if (!regexp.matches(string, options)) {
567: return null;
568: }
569: if (substitution == null) {
570: return string;
571: }
572: return regexp.substitute(string, substitution
573: .getExpression(getProject()), options);
574: }
575: }
576:
577: /** Filter to trim white space */
578: public static class Trim extends ChainableReaderFilter {
579: /**
580: * @param line the string to be trimmed
581: * @return the trimmed string
582: */
583: public String filter(String line) {
584: return line.trim();
585: }
586: }
587:
588: /** Filter remove empty tokens */
589: public static class IgnoreBlank extends ChainableReaderFilter {
590: /**
591: * @param line the line to modify
592: * @return the trimmed line
593: */
594: public String filter(String line) {
595: if (line.trim().length() == 0) {
596: return null;
597: }
598: return line;
599: }
600: }
601:
602: /**
603: * Filter to delete characters
604: */
605: public static class DeleteCharacters extends ProjectComponent
606: implements Filter, ChainableReader {
607: // Attributes
608: /** the list of characters to remove from the input */
609: private String deleteChars = "";
610:
611: /**
612: * Set the list of characters to delete
613: * @param deleteChars the list of characters
614: */
615: public void setChars(String deleteChars) {
616: this .deleteChars = resolveBackSlash(deleteChars);
617: }
618:
619: /**
620: * remove characters from a string
621: * @param string the string to remove the characters from
622: * @return the converted string
623: */
624: public String filter(String string) {
625: StringBuffer output = new StringBuffer(string.length());
626: for (int i = 0; i < string.length(); ++i) {
627: char ch = string.charAt(i);
628: if (!(isDeleteCharacter(ch))) {
629: output.append(ch);
630: }
631: }
632: return output.toString();
633: }
634:
635: /**
636: * factory method to provide a reader that removes
637: * the characters from a reader as part of a filter
638: * chain
639: * @param reader the reader object
640: * @return the chained reader object
641: */
642: public Reader chain(Reader reader) {
643: return new BaseFilterReader(reader) {
644: /**
645: * @return the next non delete character
646: */
647: public int read() throws IOException {
648: while (true) {
649: int c = in.read();
650: if (c == -1) {
651: return c;
652: }
653: if (!(isDeleteCharacter((char) c))) {
654: return c;
655: }
656: }
657: }
658: };
659: }
660:
661: /** check if the character c is to be deleted */
662: private boolean isDeleteCharacter(char c) {
663: for (int d = 0; d < deleteChars.length(); ++d) {
664: if (deleteChars.charAt(d) == c) {
665: return true;
666: }
667: }
668: return false;
669: }
670: }
671:
672: // --------------------------------------------------------
673: // static utility methods - could be placed somewhere else
674: // --------------------------------------------------------
675:
676: /**
677: * xml does not do "c" like interpretation of strings.
678: * i.e. \n\r\t etc.
679: * this method processes \n, \r, \t, \f, \\
680: * also subs \s -> " \n\r\t\f"
681: * a trailing '\' will be ignored
682: *
683: * @param input raw string with possible embedded '\'s
684: * @return converted string
685: */
686: public static String resolveBackSlash(String input) {
687: return StringUtils.resolveBackSlash(input);
688: }
689:
690: /**
691: * convert regex option flag characters to regex options
692: * <dl>
693: * <li>g - Regexp.REPLACE_ALL</li>
694: * <li>i - Regexp.MATCH_CASE_INSENSITIVE</li>
695: * <li>m - Regexp.MATCH_MULTILINE</li>
696: * <li>s - Regexp.MATCH_SINGLELINE</li>
697: * </dl>
698: * @param flags the string containing the flags
699: * @return the Regexp option bits
700: */
701: public static int convertRegexOptions(String flags) {
702: if (flags == null) {
703: return 0;
704: }
705: int options = 0;
706: if (flags.indexOf('g') != -1) {
707: options |= Regexp.REPLACE_ALL;
708: }
709: if (flags.indexOf('i') != -1) {
710: options |= Regexp.MATCH_CASE_INSENSITIVE;
711: }
712: if (flags.indexOf('m') != -1) {
713: options |= Regexp.MATCH_MULTILINE;
714: }
715: if (flags.indexOf('s') != -1) {
716: options |= Regexp.MATCH_SINGLELINE;
717: }
718: return options;
719: }
720: }
|