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.LinkedList;
023: import org.apache.tools.ant.types.Parameter;
024: import org.apache.tools.ant.util.LineTokenizer;
025:
026: /**
027: * Reads the last <code>n</code> lines of a stream. (Default is last10 lines.)
028: *
029: * Example:
030: *
031: * <pre><tailfilter lines="3"/></pre>
032: *
033: * Or:
034: *
035: * <pre><filterreader classname="org.apache.tools.ant.filters.TailFilter">
036: * <param name="lines" value="3"/>
037: * </filterreader></pre>
038: *
039: */
040: public final class TailFilter extends BaseParamFilterReader implements
041: ChainableReader {
042: /** Parameter name for the number of lines to be returned. */
043: private static final String LINES_KEY = "lines";
044:
045: /** Parameter name for the number of lines to be skipped. */
046: private static final String SKIP_KEY = "skip";
047:
048: /** Default number of lines to show */
049: private static final int DEFAULT_NUM_LINES = 10;
050:
051: /** Number of lines to be returned in the filtered stream. */
052: private long lines = DEFAULT_NUM_LINES;
053:
054: /** Number of lines to be skipped. */
055: private long skip = 0;
056:
057: /** Whether or not read-ahead been completed. */
058: private boolean completedReadAhead = false;
059:
060: /** A line tokenizer */
061: private LineTokenizer lineTokenizer = null;
062:
063: /** the current line from the input stream */
064: private String line = null;
065: /** the position in the current line */
066: private int linePos = 0;
067:
068: private LinkedList lineList = new LinkedList();
069:
070: /**
071: * Constructor for "dummy" instances.
072: *
073: * @see BaseFilterReader#BaseFilterReader()
074: */
075: public TailFilter() {
076: super ();
077: }
078:
079: /**
080: * Creates a new filtered reader.
081: *
082: * @param in A Reader object providing the underlying stream.
083: * Must not be <code>null</code>.
084: */
085: public TailFilter(final Reader in) {
086: super (in);
087: lineTokenizer = new LineTokenizer();
088: lineTokenizer.setIncludeDelims(true);
089: }
090:
091: /**
092: * Returns the next character in the filtered stream. If the read-ahead
093: * has been completed, the next character in the buffer is returned.
094: * Otherwise, the stream is read to the end and buffered (with the buffer
095: * growing as necessary), then the appropriate position in the buffer is
096: * set to read from.
097: *
098: * @return the next character in the resulting stream, or -1
099: * if the end of the resulting stream has been reached
100: *
101: * @exception IOException if the underlying stream throws an IOException
102: * during reading
103: */
104: public int read() throws IOException {
105: if (!getInitialized()) {
106: initialize();
107: setInitialized(true);
108: }
109:
110: while (line == null || line.length() == 0) {
111: line = lineTokenizer.getToken(in);
112: line = tailFilter(line);
113: if (line == null) {
114: return -1;
115: }
116: linePos = 0;
117: }
118:
119: int ch = line.charAt(linePos);
120: linePos++;
121: if (linePos == line.length()) {
122: line = null;
123: }
124: return ch;
125: }
126:
127: /**
128: * Sets the number of lines to be returned in the filtered stream.
129: *
130: * @param lines the number of lines to be returned in the filtered stream
131: */
132: public void setLines(final long lines) {
133: this .lines = lines;
134: }
135:
136: /**
137: * Returns the number of lines to be returned in the filtered stream.
138: *
139: * @return the number of lines to be returned in the filtered stream
140: */
141: private long getLines() {
142: return lines;
143: }
144:
145: /**
146: * Sets the number of lines to be skipped in the filtered stream.
147: *
148: * @param skip the number of lines to be skipped in the filtered stream
149: */
150: public void setSkip(final long skip) {
151: this .skip = skip;
152: }
153:
154: /**
155: * Returns the number of lines to be skipped in the filtered stream.
156: *
157: * @return the number of lines to be skipped in the filtered stream
158: */
159: private long getSkip() {
160: return skip;
161: }
162:
163: /**
164: * Creates a new TailFilter using the passed in
165: * Reader for instantiation.
166: *
167: * @param rdr A Reader object providing the underlying stream.
168: * Must not be <code>null</code>.
169: *
170: * @return a new filter based on this configuration, but filtering
171: * the specified reader
172: */
173: public Reader chain(final Reader rdr) {
174: TailFilter newFilter = new TailFilter(rdr);
175: newFilter.setLines(getLines());
176: newFilter.setSkip(getSkip());
177: newFilter.setInitialized(true);
178: return newFilter;
179: }
180:
181: /**
182: * Scans the parameters list for the "lines" parameter and uses
183: * it to set the number of lines to be returned in the filtered stream.
184: * also scan for "skip" parameter.
185: */
186: private void initialize() {
187: Parameter[] params = getParameters();
188: if (params != null) {
189: for (int i = 0; i < params.length; i++) {
190: if (LINES_KEY.equals(params[i].getName())) {
191: setLines(new Long(params[i].getValue()).longValue());
192: continue;
193: }
194: if (SKIP_KEY.equals(params[i].getName())) {
195: skip = new Long(params[i].getValue()).longValue();
196: continue;
197: }
198: }
199: }
200: }
201:
202: /**
203: * implement a tail filter on a stream of lines.
204: * line = null is the end of the stream.
205: * @return "" while reading in the lines,
206: * line while outputting the lines
207: * null at the end of outputting the lines
208: */
209: private String tailFilter(String line) {
210: if (!completedReadAhead) {
211: if (line != null) {
212: lineList.add(line);
213: if (lines == -1) {
214: if (lineList.size() > skip) {
215: return (String) lineList.removeFirst();
216: }
217: } else {
218: long linesToKeep = lines + (skip > 0 ? skip : 0);
219: if (linesToKeep < lineList.size()) {
220: lineList.removeFirst();
221: }
222: }
223: return "";
224: }
225: completedReadAhead = true;
226: if (skip > 0) {
227: for (int i = 0; i < skip; ++i) {
228: lineList.removeLast();
229: }
230: }
231: if (lines > -1) {
232: while (lineList.size() > lines) {
233: lineList.removeFirst();
234: }
235: }
236: }
237: if (lineList.size() > 0) {
238: return (String) lineList.removeFirst();
239: }
240: return null;
241: }
242: }
|