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       * An empty immutable {@code FTPFile} array.
74       */
75      private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {};
76      private List<String> entries = new LinkedList<>();
77  
78      private ListIterator<String> internalIterator = entries.listIterator();
79      private final FTPFileEntryParser parser;
80  
81      // Should invalid files (parse failures) be allowed?
82      private final boolean saveUnparseableEntries;
83  
84      /**
85       * Constructs a new instance.
86       *
87       * @param parser How to parse file entries.
88       */
89      public FTPListParseEngine(final FTPFileEntryParser parser) {
90          this(parser, null);
91      }
92  
93      /**
94       * Intended for use by FTPClient only
95       *
96       * @since 3.4
97       */
98      FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) {
99          this.parser = parser;
100         this.saveUnparseableEntries = configuration != null && configuration.getUnparseableEntries();
101     }
102 
103     /**
104      * 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
105      * before being added to the array.
106      *
107      * @param filter FTPFileFilter, must not be {@code null}.
108      * @return a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
109      *         <p>
110      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
111      *         entry for null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
112      * @since 3.9.0
113      */
114     public List<FTPFile> getFileList(final FTPFileFilter filter) {
115         return entries.stream().map(e -> {
116             final FTPFile file = parser.parseFTPEntry(e);
117             return file == null && saveUnparseableEntries ? new FTPFile(e) : file;
118         }).filter(filter::accept).collect(Collectors.toList());
119     }
120 
121     /**
122      * Gets an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
123      *
124      * @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
125      *         be null
126      * @throws IOException - not ever thrown, may be removed in a later release
127      */
128     // TODO remove; not actually thrown
129     public FTPFile[] getFiles() throws IOException {
130         return getFiles(FTPFileFilters.NON_NULL);
131     }
132 
133     /**
134      * 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
135      * before being added to the array.
136      *
137      * @param filter FTPFileFilter, must not be {@code null}.
138      * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
139      *         <p>
140      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
141      *         entry for null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
142      * @throws IOException - not ever thrown, may be removed in a later release
143      * @since 2.2
144      */
145     // TODO remove; not actually thrown
146     public FTPFile[] getFiles(final FTPFileFilter filter) throws IOException {
147         return getFileList(filter).toArray(EMPTY_FTP_FILE_ARRAY);
148     }
149 
150     /**
151      * Gets an array of at most {@code quantityRequested} FTPFile objects starting at this object's internal iterator's current position. If fewer than
152      * {@code quantityRequested} such elements are available, the returned array will have a length equal to the number of entries at and after the current
153      * position. If no such entries are found, this array will have a length of 0.
154      *
155      * 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.
156      *
157      * @param quantityRequested the maximum number of entries we want to get.
158      * @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
159      *         number of elements which exist in the list at and after its current position.
160      *         <p>
161      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
162      *         entry for null before referencing it.
163      */
164     public FTPFile[] getNext(final int quantityRequested) {
165         final List<FTPFile> tmpResults = new LinkedList<>();
166         int count = quantityRequested;
167         while (count > 0 && internalIterator.hasNext()) {
168             final String entry = internalIterator.next();
169             FTPFile temp = parser.parseFTPEntry(entry);
170             if (temp == null && saveUnparseableEntries) {
171                 temp = new FTPFile(entry);
172             }
173             tmpResults.add(temp);
174             count--;
175         }
176         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);
177 
178     }
179 
180     /**
181      * Gets an array of at most {@code quantityRequested} FTPFile objects starting at this object's internal iterator's current position, and working back
182      * toward the beginning.
183      *
184      * 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
185      * the current position. If no such entries are found, this array will have a length of 0.
186      *
187      * 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.
188      *
189      * @param quantityRequested the maximum number of entries we want to get.
190      * @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
191      *         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
192      *         reversed).
193      *         <p>
194      *         <strong> NOTE:</strong> This array may contain null members if any of the individual file listings failed to parse. The caller should check each
195      *         entry for null before referencing it.
196      */
197     public FTPFile[] getPrevious(final int quantityRequested) {
198         final List<FTPFile> tmpResults = new LinkedList<>();
199         int count = quantityRequested;
200         while (count > 0 && internalIterator.hasPrevious()) {
201             final String entry = internalIterator.previous();
202             FTPFile temp = parser.parseFTPEntry(entry);
203             if (temp == null && saveUnparseableEntries) {
204                 temp = new FTPFile(entry);
205             }
206             tmpResults.add(0, temp);
207             count--;
208         }
209         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);
210     }
211 
212     /**
213      * convenience method to allow clients to know whether this object's internal iterator's current position is at the end of the list.
214      *
215      * @return true if internal iterator is not at end of list, false otherwise.
216      */
217     public boolean hasNext() {
218         return internalIterator.hasNext();
219     }
220 
221     /**
222      * convenience method to allow clients to know whether this object's internal iterator's current position is at the beginning of the list.
223      *
224      * @return true if internal iterator is not at beginning of list, false otherwise.
225      */
226     public boolean hasPrevious() {
227         return internalIterator.hasPrevious();
228     }
229 
230     /**
231      * Internal method for reading (and closing) the input into the {@code entries} list. After this method has completed, {@code entries} will contain a
232      * collection of entries (as defined by {@code FTPFileEntryParser.readNextEntry()}), but this may contain various non-entry preliminary lines from the
233      * server output, duplicates, and other data that will not be part of the final listing.
234      *
235      * @param inputStream The socket stream on which the input will be read.
236      * @param charsetName The encoding to use.
237      * @throws IOException thrown on any failure to read the stream
238      */
239     private void read(final InputStream inputStream, final String charsetName) throws IOException {
240         try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) {
241             String line = parser.readNextEntry(reader);
242             while (line != null) {
243                 entries.add(line);
244                 line = parser.readNextEntry(reader);
245             }
246         }
247     }
248 
249     /**
250      * Do not use.
251      *
252      * @param inputStream the stream from which to read
253      * @throws IOException on error
254      * @deprecated use {@link #readServerList(InputStream, String)} instead
255      */
256     @Deprecated
257     public void readServerList(final InputStream inputStream) throws IOException {
258         readServerList(inputStream, null);
259     }
260 
261     /**
262      * 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
263      * list of unparsed entries (Strings) each referring to a unique file on the server.
264      *
265      * @param inputStream input stream provided by the server socket.
266      * @param charsetName the encoding to be used for reading the stream
267      * @throws IOException thrown on any failure to read from the sever.
268      */
269     public void readServerList(final InputStream inputStream, final String charsetName) throws IOException {
270         entries = new LinkedList<>();
271         read(inputStream, charsetName);
272         parser.preParse(entries);
273         resetIterator();
274     }
275 
276     /**
277      * resets this object's internal iterator to the beginning of the list.
278      */
279     public void resetIterator() {
280         internalIterator = entries.listIterator();
281     }
282 
283 }