View Javadoc
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 1652855 2015-01-18 21:26:41Z sebb $
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      // Should invalid files (parse failures) be allowed?
84      private final boolean saveUnparseableEntries;
85  
86      public FTPListParseEngine(FTPFileEntryParser parser) {
87          this(parser, null);
88      }
89  
90      /**
91       * Intended for use by FTPClient only
92       * @since 3.4
93       */
94      FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) {
95          this.parser = parser;
96          if (configuration != null) {
97              this.saveUnparseableEntries = configuration.getUnparseableEntries();
98          } else {
99              this.saveUnparseableEntries = false;
100         }
101     }
102 
103     /**
104      * handle the initial reading and preparsing of the list returned by
105      * the server.  After this method has completed, this object will contain
106      * a list of unparsed entries (Strings) each referring to a unique file
107      * on the server.
108      *
109      * @param stream input stream provided by the server socket.
110      * @param encoding the encoding to be used for reading the stream
111      *
112      * @exception IOException
113      *                   thrown on any failure to read from the sever.
114      */
115     public void readServerList(InputStream stream, String encoding)
116     throws IOException
117     {
118         this.entries = new LinkedList<String>();
119         readStream(stream, encoding);
120         this.parser.preParse(this.entries);
121         resetIterator();
122     }
123 
124     /**
125      * Internal method for reading the input into the <code>entries</code> list.
126      * After this method has completed, <code>entries</code> will contain a
127      * collection of entries (as defined by
128      * <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain
129      * various non-entry preliminary lines from the server output, duplicates,
130      * and other data that will not be part of the final listing.
131      *
132      * @param stream The socket stream on which the input will be read.
133      * @param encoding The encoding to use.
134      *
135      * @exception IOException
136      *                   thrown on any failure to read the stream
137      */
138     private void readStream(InputStream stream, String encoding) throws IOException
139     {
140         BufferedReader reader = new BufferedReader(
141                 new InputStreamReader(stream, Charsets.toCharset(encoding)));
142 
143         String line = this.parser.readNextEntry(reader);
144 
145         while (line != null)
146         {
147             this.entries.add(line);
148             line = this.parser.readNextEntry(reader);
149         }
150         reader.close();
151     }
152 
153     /**
154      * Returns an array of at most <code>quantityRequested</code> FTPFile
155      * objects starting at this object's internal iterator's current position.
156      * If fewer than <code>quantityRequested</code> such
157      * elements are available, the returned array will have a length equal
158      * to the number of entries at and after after the current position.
159      * If no such entries are found, this array will have a length of 0.
160      *
161      * After this method is called this object's internal iterator is advanced
162      * by a number of positions equal to the size of the array returned.
163      *
164      * @param quantityRequested
165      * the maximum number of entries we want to get.
166      *
167      * @return an array of at most <code>quantityRequested</code> FTPFile
168      * objects starting at the current position of this iterator within its
169      * list and at least the number of elements which  exist in the list at
170      * and after its current position.
171      * <p><b>
172      * NOTE:</b> This array may contain null members if any of the
173      * individual file listings failed to parse.  The caller should
174      * check each entry for null before referencing it.
175      */
176     public FTPFile[] getNext(int quantityRequested) {
177         List<FTPFile> tmpResults = new LinkedList<FTPFile>();
178         int count = quantityRequested;
179         while (count > 0 && this._internalIterator.hasNext()) {
180             String entry = this._internalIterator.next();
181             FTPFile temp = this.parser.parseFTPEntry(entry);
182             if (temp == null && saveUnparseableEntries) {
183                 temp = new FTPFile(entry);
184             }
185             tmpResults.add(temp);
186             count--;
187         }
188         return tmpResults.toArray(new FTPFile[tmpResults.size()]);
189 
190     }
191 
192     /**
193      * Returns an array of at most <code>quantityRequested</code> FTPFile
194      * objects starting at this object's internal iterator's current position,
195      * and working back toward the beginning.
196      *
197      * If fewer than <code>quantityRequested</code> such
198      * elements are available, the returned array will have a length equal
199      * to the number of entries at and after after the current position.
200      * If no such entries are found, this array will have a length of 0.
201      *
202      * After this method is called this object's internal iterator is moved
203      * back by a number of positions equal to the size of the array returned.
204      *
205      * @param quantityRequested
206      * the maximum number of entries we want to get.
207      *
208      * @return an array of at most <code>quantityRequested</code> FTPFile
209      * objects starting at the current position of this iterator within its
210      * list and at least the number of elements which  exist in the list at
211      * and after its current position.  This array will be in the same order
212      * as the underlying list (not reversed).
213      * <p><b>
214      * NOTE:</b> This array may contain null members if any of the
215      * individual file listings failed to parse.  The caller should
216      * check each entry for null before referencing it.
217      */
218     public FTPFile[] getPrevious(int quantityRequested) {
219         List<FTPFile> tmpResults = new LinkedList<FTPFile>();
220         int count = quantityRequested;
221         while (count > 0 && this._internalIterator.hasPrevious()) {
222             String entry = this._internalIterator.previous();
223             FTPFile temp = this.parser.parseFTPEntry(entry);
224             if (temp == null && saveUnparseableEntries) {
225                 temp = new FTPFile(entry);
226             }
227             tmpResults.add(0,temp);
228             count--;
229         }
230         return tmpResults.toArray(new FTPFile[tmpResults.size()]);
231     }
232 
233     /**
234      * Returns an array of FTPFile objects containing the whole list of
235      * files returned by the server as read by this object's parser.
236      *
237      * @return an array of FTPFile objects containing the whole list of
238      *         files returned by the server as read by this object's parser.
239      * None of the entries will be null
240      * @exception IOException - not ever thrown, may be removed in a later release
241      */
242     public FTPFile[] getFiles()
243     throws IOException // TODO remove; not actually thrown
244     {
245         return getFiles(FTPFileFilters.NON_NULL);
246     }
247 
248     /**
249      * Returns an array of FTPFile objects containing the whole list of
250      * files returned by the server as read by this object's parser.
251      * The files are filtered before being added to the array.
252      *
253      * @param filter FTPFileFilter, must not be <code>null</code>.
254      *
255      * @return an array of FTPFile objects containing the whole list of
256      *         files returned by the server as read by this object's parser.
257      * <p><b>
258      * NOTE:</b> This array may contain null members if any of the
259      * individual file listings failed to parse.  The caller should
260      * check each entry for null before referencing it, or use the
261      * a filter such as {@link FTPFileFilters#NON_NULL} which does not
262      * allow null entries.
263      * @since 2.2
264      * @exception IOException - not ever thrown, may be removed in a later release
265      */
266     public FTPFile[] getFiles(FTPFileFilter filter)
267     throws IOException // TODO remove; not actually thrown
268     {
269         List<FTPFile> tmpResults = new ArrayList<FTPFile>();
270         Iterator<String> iter = this.entries.iterator();
271         while (iter.hasNext()) {
272             String entry = iter.next();
273             FTPFile temp = this.parser.parseFTPEntry(entry);
274             if (temp == null && saveUnparseableEntries) {
275                 temp = new FTPFile(entry);
276             }
277             if (filter.accept(temp)){
278                 tmpResults.add(temp);
279             }
280         }
281         return tmpResults.toArray(new FTPFile[tmpResults.size()]);
282 
283     }
284 
285     /**
286      * convenience method to allow clients to know whether this object's
287      * internal iterator's current position is at the end of the list.
288      *
289      * @return true if internal iterator is not at end of list, false
290      * otherwise.
291      */
292     public boolean hasNext() {
293         return _internalIterator.hasNext();
294     }
295 
296     /**
297      * convenience method to allow clients to know whether this object's
298      * internal iterator's current position is at the beginning of the list.
299      *
300      * @return true if internal iterator is not at beginning of list, false
301      * otherwise.
302      */
303     public boolean hasPrevious() {
304         return _internalIterator.hasPrevious();
305     }
306 
307     /**
308      * resets this object's internal iterator to the beginning of the list.
309      */
310     public void resetIterator() {
311         this._internalIterator = this.entries.listIterator();
312     }
313 
314     // DEPRECATED METHODS - for API compatibility only - DO NOT USE
315 
316     /**
317      * Do not use.
318      * @param stream the stream from which to read
319      * @throws IOException on error
320      * @deprecated use {@link #readServerList(InputStream, String)} instead
321     */
322     @Deprecated
323     public void readServerList(InputStream stream)
324     throws IOException
325     {
326         readServerList(stream, null);
327     }
328 
329 }