1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.net.ftp;
19
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.ListIterator;
29
30 import org.apache.commons.net.util.Charsets;
31
32
33 /**
34 * This class handles the entire process of parsing a listing of
35 * file entries from the server.
36 * <p>
37 * This object defines a two-part parsing mechanism.
38 * <p>
39 * The first part is comprised of reading the raw input into an internal
40 * list of strings. Every item in this list corresponds to an actual
41 * file. All extraneous matter emitted by the server will have been
42 * removed by the end of this phase. This is accomplished in conjunction
43 * with the FTPFileEntryParser associated with this engine, by calling
44 * its methods <code>readNextEntry()</code> - which handles the issue of
45 * what delimits one entry from another, usually but not always a line
46 * feed and <code>preParse()</code> - which handles removal of
47 * extraneous matter such as the preliminary lines of a listing, removal
48 * of duplicates on versioning systems, etc.
49 * <p>
50 * The second part is composed of the actual parsing, again in conjunction
51 * with the particular parser used by this engine. This is controlled
52 * by an iterator over the internal list of strings. This may be done
53 * either in block mode, by calling the <code>getNext()</code> and
54 * <code>getPrevious()</code> methods to provide "paged" output of less
55 * than the whole list at one time, or by calling the
56 * <code>getFiles()</code> method to return the entire list.
57 * <p>
58 * Examples:
59 * <p>
60 * Paged access:
61 * <pre>
62 * FTPClient f=FTPClient();
63 * f.connect(server);
64 * f.login(username, password);
65 * FTPListParseEngine engine = f.initiateListParsing(directory);
66 *
67 * while (engine.hasNext()) {
68 * FTPFile[] files = engine.getNext(25); // "page size" you want
69 * //do whatever you want with these files, display them, etc.
70 * //expensive FTPFile objects not created until needed.
71 * }
72 * </pre>
73 * <p>
74 * For unpaged access, simply use FTPClient.listFiles(). That method
75 * uses this class transparently.
76 * @version $Id: FTPListParseEngine.java 1414510 2012-11-28 02:40:39Z ggregory $
77 */
78 public class FTPListParseEngine {
79 private List<String> entries = new LinkedList<String>();
80 private ListIterator<String> _internalIterator = entries.listIterator();
81
82 private final FTPFileEntryParser parser;
83
84 public FTPListParseEngine(FTPFileEntryParser parser) {
85 this.parser = parser;
86 }
87
88 /**
89 * handle the initial reading and preparsing of the list returned by
90 * the server. After this method has completed, this object will contain
91 * a list of unparsed entries (Strings) each referring to a unique file
92 * on the server.
93 *
94 * @param stream input stream provided by the server socket.
95 * @param encoding the encoding to be used for reading the stream
96 *
97 * @exception IOException
98 * thrown on any failure to read from the sever.
99 */
100 public void readServerList(InputStream stream, String encoding)
101 throws IOException
102 {
103 this.entries = new LinkedList<String>();
104 readStream(stream, encoding);
105 this.parser.preParse(this.entries);
106 resetIterator();
107 }
108
109 /**
110 * Internal method for reading the input into the <code>entries</code> list.
111 * After this method has completed, <code>entries</code> will contain a
112 * collection of entries (as defined by
113 * <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain
114 * various non-entry preliminary lines from the server output, duplicates,
115 * and other data that will not be part of the final listing.
116 *
117 * @param stream The socket stream on which the input will be read.
118 * @param encoding The encoding to use.
119 *
120 * @exception IOException
121 * thrown on any failure to read the stream
122 */
123 private void readStream(InputStream stream, String encoding) throws IOException
124 {
125 BufferedReader reader = new BufferedReader(
126 new InputStreamReader(stream, Charsets.toCharset(encoding)));
127
128 String line = this.parser.readNextEntry(reader);
129
130 while (line != null)
131 {
132 this.entries.add(line);
133 line = this.parser.readNextEntry(reader);
134 }
135 reader.close();
136 }
137
138 /**
139 * Returns an array of at most <code>quantityRequested</code> FTPFile
140 * objects starting at this object's internal iterator's current position.
141 * If fewer than <code>quantityRequested</code> such
142 * elements are available, the returned array will have a length equal
143 * to the number of entries at and after after the current position.
144 * If no such entries are found, this array will have a length of 0.
145 *
146 * After this method is called this object's internal iterator is advanced
147 * by a number of positions equal to the size of the array returned.
148 *
149 * @param quantityRequested
150 * the maximum number of entries we want to get.
151 *
152 * @return an array of at most <code>quantityRequested</code> FTPFile
153 * objects starting at the current position of this iterator within its
154 * list and at least the number of elements which exist in the list at
155 * and after its current position.
156 * <p><b>
157 * NOTE:</b> This array may contain null members if any of the
158 * individual file listings failed to parse. The caller should
159 * check each entry for null before referencing it.
160 */
161 public FTPFile[] getNext(int quantityRequested) {
162 List<FTPFile> tmpResults = new LinkedList<FTPFile>();
163 int count = quantityRequested;
164 while (count > 0 && this._internalIterator.hasNext()) {
165 String entry = this._internalIterator.next();
166 FTPFile temp = this.parser.parseFTPEntry(entry);
167 tmpResults.add(temp);
168 count--;
169 }
170 return tmpResults.toArray(new FTPFile[tmpResults.size()]);
171
172 }
173
174 /**
175 * Returns an array of at most <code>quantityRequested</code> FTPFile
176 * objects starting at this object's internal iterator's current position,
177 * and working back toward the beginning.
178 *
179 * If fewer than <code>quantityRequested</code> such
180 * elements are available, the returned array will have a length equal
181 * to the number of entries at and after after the current position.
182 * If no such entries are found, this array will have a length of 0.
183 *
184 * After this method is called this object's internal iterator is moved
185 * back by a number of positions equal to the size of the array returned.
186 *
187 * @param quantityRequested
188 * the maximum number of entries we want to get.
189 *
190 * @return an array of at most <code>quantityRequested</code> FTPFile
191 * objects starting at the current position of this iterator within its
192 * list and at least the number of elements which exist in the list at
193 * and after its current position. This array will be in the same order
194 * as the underlying list (not reversed).
195 * <p><b>
196 * NOTE:</b> This array may contain null members if any of the
197 * individual file listings failed to parse. The caller should
198 * check each entry for null before referencing it.
199 */
200 public FTPFile[] getPrevious(int quantityRequested) {
201 List<FTPFile> tmpResults = new LinkedList<FTPFile>();
202 int count = quantityRequested;
203 while (count > 0 && this._internalIterator.hasPrevious()) {
204 String entry = this._internalIterator.previous();
205 FTPFile temp = this.parser.parseFTPEntry(entry);
206 tmpResults.add(0,temp);
207 count--;
208 }
209 return tmpResults.toArray(new FTPFile[tmpResults.size()]);
210 }
211
212 /**
213 * Returns an array of FTPFile objects containing the whole list of
214 * files returned by the server as read by this object's parser.
215 *
216 * @return an array of FTPFile objects containing the whole list of
217 * files returned by the server as read by this object's parser.
218 * None of the entries will be null
219 * @exception IOException - not ever thrown, may be removed in a later release
220 */
221 public FTPFile[] getFiles()
222 throws IOException // TODO remove; not actually thrown
223 {
224 return getFiles(FTPFileFilters.NON_NULL);
225 }
226
227 /**
228 * Returns an array of FTPFile objects containing the whole list of
229 * files returned by the server as read by this object's parser.
230 * The files are filtered before being added to the array.
231 *
232 * @param filter FTPFileFilter, must not be <code>null</code>.
233 *
234 * @return an array of FTPFile objects containing the whole list of
235 * files returned by the server as read by this object's parser.
236 * <p><b>
237 * NOTE:</b> This array may contain null members if any of the
238 * individual file listings failed to parse. The caller should
239 * check each entry for null before referencing it, or use the
240 * a filter such as {@link FTPFileFilters#NON_NULL} which does not
241 * allow null entries.
242 * @since 2.2
243 * @exception IOException - not ever thrown, may be removed in a later release
244 */
245 public FTPFile[] getFiles(FTPFileFilter filter)
246 throws IOException // TODO remove; not actually thrown
247 {
248 List<FTPFile> tmpResults = new ArrayList<FTPFile>();
249 Iterator<String> iter = this.entries.iterator();
250 while (iter.hasNext()) {
251 String entry = iter.next();
252 FTPFile temp = this.parser.parseFTPEntry(entry);
253 if (filter.accept(temp)){
254 tmpResults.add(temp);
255 }
256 }
257 return tmpResults.toArray(new FTPFile[tmpResults.size()]);
258
259 }
260
261 /**
262 * convenience method to allow clients to know whether this object's
263 * internal iterator's current position is at the end of the list.
264 *
265 * @return true if internal iterator is not at end of list, false
266 * otherwise.
267 */
268 public boolean hasNext() {
269 return _internalIterator.hasNext();
270 }
271
272 /**
273 * convenience method to allow clients to know whether this object's
274 * internal iterator's current position is at the beginning of the list.
275 *
276 * @return true if internal iterator is not at beginning of list, false
277 * otherwise.
278 */
279 public boolean hasPrevious() {
280 return _internalIterator.hasPrevious();
281 }
282
283 /**
284 * resets this object's internal iterator to the beginning of the list.
285 */
286 public void resetIterator() {
287 this._internalIterator = this.entries.listIterator();
288 }
289
290 // DEPRECATED METHODS - for API compatibility only - DO NOT USE
291
292 /**
293 * Do not use.
294 * @deprecated use {@link #readServerList(InputStream, String)} instead
295 */
296 @Deprecated
297 public void readServerList(InputStream stream)
298 throws IOException
299 {
300 readServerList(stream, null);
301 }
302
303 }