001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net.ftp;
019
020import java.io.BufferedReader;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InputStreamReader;
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.ListIterator;
029
030import org.apache.commons.net.util.Charsets;
031
032
033/**
034 * This class handles the entire process of parsing a listing of
035 * file entries from the server.
036 * <p>
037 * This object defines a two-part parsing mechanism.
038 * <p>
039 * The first part is comprised of reading the raw input into an internal
040 * list of strings.  Every item in this list corresponds to an actual
041 * file.  All extraneous matter emitted by the server will have been
042 * removed by the end of this phase.  This is accomplished in conjunction
043 * with the FTPFileEntryParser associated with this engine, by calling
044 * its methods <code>readNextEntry()</code> - which handles the issue of
045 * what delimits one entry from another, usually but not always a line
046 * feed and <code>preParse()</code> - which handles removal of
047 * extraneous matter such as the preliminary lines of a listing, removal
048 * of duplicates on versioning systems, etc.
049 * <p>
050 * The second part is composed of the actual parsing, again in conjunction
051 * with the particular parser used by this engine.  This is controlled
052 * by an iterator over the internal list of strings.  This may be done
053 * either in block mode, by calling the <code>getNext()</code> and
054 * <code>getPrevious()</code> methods to provide "paged" output of less
055 * than the whole list at one time, or by calling the
056 * <code>getFiles()</code> method to return the entire list.
057 * <p>
058 * Examples:
059 * <p>
060 * Paged access:
061 * <pre>
062 *    FTPClient f=FTPClient();
063 *    f.connect(server);
064 *    f.login(username, password);
065 *    FTPListParseEngine engine = f.initiateListParsing(directory);
066 *
067 *    while (engine.hasNext()) {
068 *       FTPFile[] files = engine.getNext(25);  // "page size" you want
069 *       //do whatever you want with these files, display them, etc.
070 *       //expensive FTPFile objects not created until needed.
071 *    }
072 * </pre>
073 * <p>
074 * For unpaged access, simply use FTPClient.listFiles().  That method
075 * uses this class transparently.
076 * @version $Id: FTPListParseEngine.java 1414510 2012-11-28 02:40:39Z ggregory $
077 */
078public class FTPListParseEngine {
079    private List<String> entries = new LinkedList<String>();
080    private ListIterator<String> _internalIterator = entries.listIterator();
081
082    private final FTPFileEntryParser parser;
083
084    public FTPListParseEngine(FTPFileEntryParser parser) {
085        this.parser = parser;
086    }
087
088    /**
089     * handle the initial reading and preparsing of the list returned by
090     * the server.  After this method has completed, this object will contain
091     * a list of unparsed entries (Strings) each referring to a unique file
092     * on the server.
093     *
094     * @param stream input stream provided by the server socket.
095     * @param encoding the encoding to be used for reading the stream
096     *
097     * @exception IOException
098     *                   thrown on any failure to read from the sever.
099     */
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}