1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.filters;
  18. import java.io.IOException;
  19. import java.io.Reader;
  20. import java.util.LinkedList;
  21. import org.apache.tools.ant.types.Parameter;
  22. import org.apache.tools.ant.util.LineTokenizer;
  23. /**
  24. * Reads the last <code>n</code> lines of a stream. (Default is last10 lines.)
  25. *
  26. * Example:
  27. *
  28. * <pre><tailfilter lines="3"/></pre>
  29. *
  30. * Or:
  31. *
  32. * <pre><filterreader classname="org.apache.tools.ant.filters.TailFilter">
  33. * <param name="lines" value="3"/>
  34. * </filterreader></pre>
  35. *
  36. */
  37. public final class TailFilter extends BaseParamFilterReader
  38. implements ChainableReader {
  39. /** Parameter name for the number of lines to be returned. */
  40. private static final String LINES_KEY = "lines";
  41. /** Parameter name for the number of lines to be skipped. */
  42. private static final String SKIP_KEY = "skip";
  43. /** Default number of lines to show */
  44. private static final int DEFAULT_NUM_LINES = 10;
  45. /** Number of lines to be returned in the filtered stream. */
  46. private long lines = DEFAULT_NUM_LINES;
  47. /** Number of lines to be skipped. */
  48. private long skip = 0;
  49. /** Whether or not read-ahead been completed. */
  50. private boolean completedReadAhead = false;
  51. /** A line tokenizer */
  52. private LineTokenizer lineTokenizer = null;
  53. /** the current line from the input stream */
  54. private String line = null;
  55. /** the position in the current line */
  56. private int linePos = 0;
  57. private LinkedList lineList = new LinkedList();
  58. /**
  59. * Constructor for "dummy" instances.
  60. *
  61. * @see BaseFilterReader#BaseFilterReader()
  62. */
  63. public TailFilter() {
  64. super();
  65. }
  66. /**
  67. * Creates a new filtered reader.
  68. *
  69. * @param in A Reader object providing the underlying stream.
  70. * Must not be <code>null</code>.
  71. */
  72. public TailFilter(final Reader in) {
  73. super(in);
  74. lineTokenizer = new LineTokenizer();
  75. lineTokenizer.setIncludeDelims(true);
  76. }
  77. /**
  78. * Returns the next character in the filtered stream. If the read-ahead
  79. * has been completed, the next character in the buffer is returned.
  80. * Otherwise, the stream is read to the end and buffered (with the buffer
  81. * growing as necessary), then the appropriate position in the buffer is
  82. * set to read from.
  83. *
  84. * @return the next character in the resulting stream, or -1
  85. * if the end of the resulting stream has been reached
  86. *
  87. * @exception IOException if the underlying stream throws an IOException
  88. * during reading
  89. */
  90. public final int read() throws IOException {
  91. if (!getInitialized()) {
  92. initialize();
  93. setInitialized(true);
  94. }
  95. while (line == null || line.length() == 0) {
  96. line = lineTokenizer.getToken(in);
  97. line = tailFilter(line);
  98. if (line == null) {
  99. return -1;
  100. }
  101. linePos = 0;
  102. }
  103. int ch = line.charAt(linePos);
  104. linePos++;
  105. if (linePos == line.length()) {
  106. line = null;
  107. }
  108. return ch;
  109. }
  110. /**
  111. * Sets the number of lines to be returned in the filtered stream.
  112. *
  113. * @param lines the number of lines to be returned in the filtered stream
  114. */
  115. public final void setLines(final long lines) {
  116. this.lines = lines;
  117. }
  118. /**
  119. * Returns the number of lines to be returned in the filtered stream.
  120. *
  121. * @return the number of lines to be returned in the filtered stream
  122. */
  123. private final long getLines() {
  124. return lines;
  125. }
  126. /**
  127. * Sets the number of lines to be skipped in the filtered stream.
  128. *
  129. * @param skip the number of lines to be skipped in the filtered stream
  130. */
  131. public final void setSkip(final long skip) {
  132. this.skip = skip;
  133. }
  134. /**
  135. * Returns the number of lines to be skipped in the filtered stream.
  136. *
  137. * @return the number of lines to be skipped in the filtered stream
  138. */
  139. private final long getSkip() {
  140. return skip;
  141. }
  142. /**
  143. * Creates a new TailFilter using the passed in
  144. * Reader for instantiation.
  145. *
  146. * @param rdr A Reader object providing the underlying stream.
  147. * Must not be <code>null</code>.
  148. *
  149. * @return a new filter based on this configuration, but filtering
  150. * the specified reader
  151. */
  152. public final Reader chain(final Reader rdr) {
  153. TailFilter newFilter = new TailFilter(rdr);
  154. newFilter.setLines(getLines());
  155. newFilter.setSkip(getSkip());
  156. newFilter.setInitialized(true);
  157. return newFilter;
  158. }
  159. /**
  160. * Scans the parameters list for the "lines" parameter and uses
  161. * it to set the number of lines to be returned in the filtered stream.
  162. * also scan for "skip" parameter.
  163. */
  164. private final void initialize() {
  165. Parameter[] params = getParameters();
  166. if (params != null) {
  167. for (int i = 0; i < params.length; i++) {
  168. if (LINES_KEY.equals(params[i].getName())) {
  169. setLines(new Long(params[i].getValue()).longValue());
  170. continue;
  171. }
  172. if (SKIP_KEY.equals(params[i].getName())) {
  173. skip = new Long(params[i].getValue()).longValue();
  174. continue;
  175. }
  176. }
  177. }
  178. }
  179. /**
  180. * implement a tail filter on a stream of lines.
  181. * line = null is the end of the stream.
  182. * @return "" while reading in the lines,
  183. * line while outputting the lines
  184. * null at the end of outputting the lines
  185. */
  186. private String tailFilter(String line) {
  187. if (!completedReadAhead) {
  188. if (line != null) {
  189. lineList.add(line);
  190. if (lines == -1) {
  191. if (lineList.size() > skip) {
  192. return (String) lineList.removeFirst();
  193. }
  194. } else {
  195. long linesToKeep = lines + (skip > 0 ? skip : 0);
  196. if (linesToKeep < lineList.size()) {
  197. lineList.removeFirst();
  198. }
  199. }
  200. return "";
  201. }
  202. completedReadAhead = true;
  203. if (skip > 0) {
  204. for (int i = 0; i < skip; ++i) {
  205. lineList.removeLast();
  206. }
  207. }
  208. if (lines > -1) {
  209. while (lineList.size() > lines) {
  210. lineList.removeFirst();
  211. }
  212. }
  213. }
  214. if (lineList.size() > 0) {
  215. return (String) lineList.removeFirst();
  216. }
  217. return null;
  218. }
  219. }