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    *      https://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.LinkedList;
25  import java.util.List;
26  import java.util.ListIterator;
27  import java.util.stream.Collectors;
28  
29  import org.apache.commons.io.Charsets;
30  
31  
32  /**
33   * This class handles the entire process of parsing a listing of file entries from the server.
34   * <p>
35   * This object defines a two-part parsing mechanism.
36   * </p>
37   * <p>
38   * The first part consists of reading the raw input into an internal list of strings. Every item in this list corresponds to an actual file. All extraneous
39   * matter emitted by the server will have been removed by the end of this phase. This is accomplished in conjunction with the FTPFileEntryParser associated with
40   * this engine, by calling its methods {@code readNextEntry()} - which handles the issue of what delimits one entry from another, usually but not always a line
41   * feed and {@code preParse()} - which handles removal of extraneous matter such as the preliminary lines of a listing, removal of duplicates on versioning
42   * systems, etc.
43   * </p>
44   * <p>
45   * The second part is composed of the actual parsing, again in conjunction with the particular parser used by this engine. This is controlled by an iterator
46   * over the internal list of strings. This may be done either in block mode, by calling the {@code getNext()} and {@code getPrevious()} methods to provide
47   * "paged" output of less than the whole list at one time, or by calling the {@code getFiles()} method to return the entire list.
48   * </p>
49   * <p>
50   * Examples:
51   * </p>
52   * <p>
53   * Paged access:
54   * </p>
55   * <pre>
56   * FTPClient f = FTPClient();
57   * f.connect(server);
58   * f.login(user, password);
59   * FTPListParseEngine engine = f.initiateListParsing(directory);
60   *
61   * while (engine.hasNext()) {
62   *     FTPFile[] files = engine.getNext(25); // "page size" you want
63   *     // do whatever you want with these files, display them, etc.
64   *     // expensive FTPFile objects not created until needed.
65   * }
66   * </pre>
67   * <p>
68   * For unpaged access, simply use FTPClient.listFiles(). That method uses this class transparently.
69   * </p>
70   */
71  public class FTPListParseEngine {
72  
73      /**
74       * An empty immutable {@code FTPFile} array.
75       */
76      private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {};
77      private List<String> entries = new LinkedList<>();
78  
79      private ListIterator<String> internalIterator = entries.listIterator();
80      private final FTPFileEntryParser parser;
81  
82      // Should invalid files (parse failures) be allowed?
83      private final boolean saveUnparseableEntries;
84  
85      /**
86       * Constructs a new instance.
87       *
88       * @param parser How to parse file entries.
89       */
90      public FTPListParseEngine(final FTPFileEntryParser parser) {
91          this(parser, null);
92      }
93  
94      /**
95       * Intended for use by FTPClient only
96       *
97       * @since 3.4
98       */
99      FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) {
100         this.parser = parser;
101         this.saveUnparseableEntries = configuration != null && configuration.getUnparseableEntries();
102     }
103 
104     /**
105      * Gets a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. The files are filtered
106      * before being added to the array.
107      *
108      * @param filter FTPFileFilter, must not be {@code null}.
109      * @return a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
110      *         <p>
111      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
112      *         entry for null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
113      * @since 3.9.0
114      */
115     public List<FTPFile> getFileList(final FTPFileFilter filter) {
116         return entries.stream().map(e -> {
117             final FTPFile file = parser.parseFTPEntry(e);
118             return file == null && saveUnparseableEntries ? new FTPFile(e) : file;
119         }).filter(filter::accept).collect(Collectors.toList());
120     }
121 
122     /**
123      * Gets an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
124      *
125      * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. None of the entries will
126      *         be null
127      * @throws IOException - not ever thrown, may be removed in a later release
128      */
129     // TODO remove; not actually thrown
130     public FTPFile[] getFiles() throws IOException {
131         return getFiles(FTPFileFilters.NON_NULL);
132     }
133 
134     /**
135      * Gets an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. The files are filtered
136      * before being added to the array.
137      *
138      * @param filter FTPFileFilter, must not be {@code null}.
139      * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
140      *         <p>
141      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
142      *         entry for null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
143      * @throws IOException - not ever thrown, may be removed in a later release
144      * @since 2.2
145      */
146     // TODO remove; not actually thrown
147     public FTPFile[] getFiles(final FTPFileFilter filter) throws IOException {
148         return getFileList(filter).toArray(EMPTY_FTP_FILE_ARRAY);
149     }
150 
151     /**
152      * Gets an array of at most {@code quantityRequested} FTPFile objects starting at this object's internal iterator's current position. If fewer than
153      * {@code quantityRequested} such elements are available, the returned array will have a length equal to the number of entries at and after the current
154      * position. If no such entries are found, this array will have a length of 0.
155      *
156      * After this method is called this object's internal iterator is advanced by a number of positions equal to the size of the array returned.
157      *
158      * @param quantityRequested the maximum number of entries we want to get.
159      * @return an array of at most {@code quantityRequested} FTPFile objects starting at the current position of this iterator within its list and at least the
160      *         number of elements which exist in the list at and after its current position.
161      *         <p>
162      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
163      *         entry for null before referencing it.
164      */
165     public FTPFile[] getNext(final int quantityRequested) {
166         final List<FTPFile> tmpResults = new LinkedList<>();
167         int count = quantityRequested;
168         while (count > 0 && internalIterator.hasNext()) {
169             final String entry = internalIterator.next();
170             FTPFile temp = parser.parseFTPEntry(entry);
171             if (temp == null && saveUnparseableEntries) {
172                 temp = new FTPFile(entry);
173             }
174             tmpResults.add(temp);
175             count--;
176         }
177         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);
178 
179     }
180 
181     /**
182      * Gets an array of at most {@code quantityRequested} FTPFile objects starting at this object's internal iterator's current position, and working back
183      * toward the beginning.
184      *
185      * If fewer than {@code quantityRequested} such elements are available, the returned array will have a length equal to the number of entries at and after
186      * the current position. If no such entries are found, this array will have a length of 0.
187      *
188      * After this method is called this object's internal iterator is moved back by a number of positions equal to the size of the array returned.
189      *
190      * @param quantityRequested the maximum number of entries we want to get.
191      * @return an array of at most {@code quantityRequested} FTPFile objects starting at the current position of this iterator within its list and at least the
192      *         number of elements which exist in the list at and after its current position. This array will be in the same order as the underlying list (not
193      *         reversed).
194      *         <p>
195      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
196      *         entry for null before referencing it.
197      */
198     public FTPFile[] getPrevious(final int quantityRequested) {
199         final List<FTPFile> tmpResults = new LinkedList<>();
200         int count = quantityRequested;
201         while (count > 0 && internalIterator.hasPrevious()) {
202             final String entry = internalIterator.previous();
203             FTPFile temp = parser.parseFTPEntry(entry);
204             if (temp == null && saveUnparseableEntries) {
205                 temp = new FTPFile(entry);
206             }
207             tmpResults.add(0, temp);
208             count--;
209         }
210         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);
211     }
212 
213     /**
214      * convenience method to allow clients to know whether this object's internal iterator's current position is at the end of the list.
215      *
216      * @return true if internal iterator is not at end of list, false otherwise.
217      */
218     public boolean hasNext() {
219         return internalIterator.hasNext();
220     }
221 
222     /**
223      * convenience method to allow clients to know whether this object's internal iterator's current position is at the beginning of the list.
224      *
225      * @return true if internal iterator is not at beginning of list, false otherwise.
226      */
227     public boolean hasPrevious() {
228         return internalIterator.hasPrevious();
229     }
230 
231     /**
232      * Internal method for reading (and closing) the input into the {@code entries} list. After this method has completed, {@code entries} will contain a
233      * collection of entries (as defined by {@code FTPFileEntryParser.readNextEntry()}), but this may contain various non-entry preliminary lines from the
234      * server output, duplicates, and other data that will not be part of the final listing.
235      *
236      * @param inputStream The socket stream on which the input will be read.
237      * @param charsetName The encoding to use.
238      * @throws IOException thrown on any failure to read the stream
239      */
240     private void read(final InputStream inputStream, final String charsetName) throws IOException {
241         try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) {
242             String line = parser.readNextEntry(reader);
243             while (line != null) {
244                 entries.add(line);
245                 line = parser.readNextEntry(reader);
246             }
247         }
248     }
249 
250     /**
251      * Do not use.
252      *
253      * @param inputStream the stream from which to read
254      * @throws IOException on error
255      * @deprecated Use {@link #readServerList(InputStream, String)} instead
256      */
257     @Deprecated
258     public void readServerList(final InputStream inputStream) throws IOException {
259         readServerList(inputStream, null);
260     }
261 
262     /**
263      * Reads (and closes) the initial reading and preparsing of the list returned by the server. After this method has completed, this object will contain a
264      * list of unparsed entries (Strings) each referring to a unique file on the server.
265      *
266      * @param inputStream input stream provided by the server socket.
267      * @param charsetName the encoding to be used for reading the stream
268      * @throws IOException thrown on any failure to read from the sever.
269      */
270     public void readServerList(final InputStream inputStream, final String charsetName) throws IOException {
271         entries = new LinkedList<>();
272         read(inputStream, charsetName);
273         parser.preParse(entries);
274         resetIterator();
275     }
276 
277     /**
278      * resets this object's internal iterator to the beginning of the list.
279      */
280     public void resetIterator() {
281         internalIterator = entries.listIterator();
282     }
283 
284 }