FTPListParseEngine.java

  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. package org.apache.commons.net.ftp;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.ListIterator;
  25. import java.util.stream.Collectors;

  26. import org.apache.commons.net.util.Charsets;

  27. /**
  28.  * This class handles the entire process of parsing a listing of file entries from the server.
  29.  * <p>
  30.  * This object defines a two-part parsing mechanism.
  31.  * <p>
  32.  * 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
  33.  * 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
  34.  * this engine, by calling its methods <code>readNextEntry()</code> - which handles the issue of what delimits one entry from another, usually but not always a
  35.  * line feed and <code>preParse()</code> - which handles removal of extraneous matter such as the preliminary lines of a listing, removal of duplicates on
  36.  * versioning systems, etc.
  37.  * <p>
  38.  * 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
  39.  * over the internal list of strings. This may be done either in block mode, by calling the <code>getNext()</code> and <code>getPrevious()</code> methods to
  40.  * provide "paged" output of less than the whole list at one time, or by calling the <code>getFiles()</code> method to return the entire list.
  41.  * <p>
  42.  * Examples:
  43.  * <p>
  44.  * Paged access:
  45.  *
  46.  * <pre>
  47.  * FTPClient f = FTPClient();
  48.  * f.connect(server);
  49.  * f.login(user, password);
  50.  * FTPListParseEngine engine = f.initiateListParsing(directory);
  51.  *
  52.  * while (engine.hasNext()) {
  53.  *     FTPFile[] files = engine.getNext(25); // "page size" you want
  54.  *     // do whatever you want with these files, display them, etc.
  55.  *     // expensive FTPFile objects not created until needed.
  56.  * }
  57.  * </pre>
  58.  * <p>
  59.  * For unpaged access, simply use FTPClient.listFiles(). That method uses this class transparently.
  60.  */
  61. public class FTPListParseEngine {
  62.     /**
  63.      * An empty immutable {@code FTPFile} array.
  64.      */
  65.     private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {};
  66.     private List<String> entries = new LinkedList<>();

  67.     private ListIterator<String> internalIterator = entries.listIterator();
  68.     private final FTPFileEntryParser parser;

  69.     // Should invalid files (parse failures) be allowed?
  70.     private final boolean saveUnparseableEntries;

  71.     public FTPListParseEngine(final FTPFileEntryParser parser) {
  72.         this(parser, null);
  73.     }

  74.     /**
  75.      * Intended for use by FTPClient only
  76.      *
  77.      * @since 3.4
  78.      */
  79.     FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) {
  80.         this.parser = parser;
  81.         if (configuration != null) {
  82.             this.saveUnparseableEntries = configuration.getUnparseableEntries();
  83.         } else {
  84.             this.saveUnparseableEntries = false;
  85.         }
  86.     }

  87.     /**
  88.      * Returns 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
  89.      * before being added to the array.
  90.      *
  91.      * @param filter FTPFileFilter, must not be {@code null}.
  92.      *
  93.      * @return a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
  94.      *         <p>
  95.      *         <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for
  96.      *         null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
  97.      * @since 3.9.0
  98.      */
  99.     public List<FTPFile> getFileList(final FTPFileFilter filter) {
  100.         return entries.stream().map(e -> {
  101.             final FTPFile file = parser.parseFTPEntry(e);
  102.             return file == null && saveUnparseableEntries ? new FTPFile(e) : file;
  103.         }).filter(filter::accept).collect(Collectors.toList());
  104.     }

  105.     /**
  106.      * Returns an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
  107.      *
  108.      * @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
  109.      *         be null
  110.      * @throws IOException - not ever thrown, may be removed in a later release
  111.      */
  112.     public FTPFile[] getFiles() throws IOException // TODO remove; not actually thrown
  113.     {
  114.         return getFiles(FTPFileFilters.NON_NULL);
  115.     }

  116.     /**
  117.      * Returns 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
  118.      * before being added to the array.
  119.      *
  120.      * @param filter FTPFileFilter, must not be {@code null}.
  121.      *
  122.      * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser.
  123.      *         <p>
  124.      *         <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for
  125.      *         null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries.
  126.      * @since 2.2
  127.      * @throws IOException - not ever thrown, may be removed in a later release
  128.      */
  129.     public FTPFile[] getFiles(final FTPFileFilter filter) throws IOException // TODO remove; not actually thrown
  130.     {
  131.         return getFileList(filter).toArray(EMPTY_FTP_FILE_ARRAY);
  132.     }

  133.     /**
  134.      * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position. If fewer than
  135.      * <code>quantityRequested</code> such elements are available, the returned array will have a length equal to the number of entries at and after the
  136.      * current position. If no such entries are found, this array will have a length of 0.
  137.      *
  138.      * 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.
  139.      *
  140.      * @param quantityRequested the maximum number of entries we want to get.
  141.      *
  142.      * @return an array of at most <code>quantityRequested</code> FTPFile objects starting at the current position of this iterator within its list and at least
  143.      *         the number of elements which exist in the list at and after its current position.
  144.      *         <p>
  145.      *         <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for
  146.      *         null before referencing it.
  147.      */
  148.     public FTPFile[] getNext(final int quantityRequested) {
  149.         final List<FTPFile> tmpResults = new LinkedList<>();
  150.         int count = quantityRequested;
  151.         while (count > 0 && this.internalIterator.hasNext()) {
  152.             final String entry = this.internalIterator.next();
  153.             FTPFile temp = this.parser.parseFTPEntry(entry);
  154.             if (temp == null && saveUnparseableEntries) {
  155.                 temp = new FTPFile(entry);
  156.             }
  157.             tmpResults.add(temp);
  158.             count--;
  159.         }
  160.         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);

  161.     }

  162.     /**
  163.      * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position, and working
  164.      * back toward the beginning.
  165.      *
  166.      * If fewer than <code>quantityRequested</code> such elements are available, the returned array will have a length equal to the number of entries at and
  167.      * after the current position. If no such entries are found, this array will have a length of 0.
  168.      *
  169.      * 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.
  170.      *
  171.      * @param quantityRequested the maximum number of entries we want to get.
  172.      *
  173.      * @return an array of at most <code>quantityRequested</code> FTPFile objects starting at the current position of this iterator within its list and at least
  174.      *         the 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
  175.      *         (not reversed).
  176.      *         <p>
  177.      *         <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for
  178.      *         null before referencing it.
  179.      */
  180.     public FTPFile[] getPrevious(final int quantityRequested) {
  181.         final List<FTPFile> tmpResults = new LinkedList<>();
  182.         int count = quantityRequested;
  183.         while (count > 0 && this.internalIterator.hasPrevious()) {
  184.             final String entry = this.internalIterator.previous();
  185.             FTPFile temp = this.parser.parseFTPEntry(entry);
  186.             if (temp == null && saveUnparseableEntries) {
  187.                 temp = new FTPFile(entry);
  188.             }
  189.             tmpResults.add(0, temp);
  190.             count--;
  191.         }
  192.         return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY);
  193.     }

  194.     /**
  195.      * convenience method to allow clients to know whether this object's internal iterator's current position is at the end of the list.
  196.      *
  197.      * @return true if internal iterator is not at end of list, false otherwise.
  198.      */
  199.     public boolean hasNext() {
  200.         return internalIterator.hasNext();
  201.     }

  202.     /**
  203.      * convenience method to allow clients to know whether this object's internal iterator's current position is at the beginning of the list.
  204.      *
  205.      * @return true if internal iterator is not at beginning of list, false otherwise.
  206.      */
  207.     public boolean hasPrevious() {
  208.         return internalIterator.hasPrevious();
  209.     }

  210.     /**
  211.      * Internal method for reading (and closing) the input into the <code>entries</code> list. After this method has completed, <code>entries</code> will
  212.      * contain a collection of entries (as defined by <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain various non-entry preliminary lines
  213.      * from the server output, duplicates, and other data that will not be part of the final listing.
  214.      *
  215.      * @param inputStream The socket stream on which the input will be read.
  216.      * @param charsetName The encoding to use.
  217.      *
  218.      * @throws IOException thrown on any failure to read the stream
  219.      */
  220.     private void read(final InputStream inputStream, final String charsetName) throws IOException {
  221.         try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) {

  222.             String line = this.parser.readNextEntry(reader);

  223.             while (line != null) {
  224.                 this.entries.add(line);
  225.                 line = this.parser.readNextEntry(reader);
  226.             }
  227.         }
  228.     }

  229.     /**
  230.      * Do not use.
  231.      *
  232.      * @param inputStream the stream from which to read
  233.      * @throws IOException on error
  234.      * @deprecated use {@link #readServerList(InputStream, String)} instead
  235.      */
  236.     @Deprecated
  237.     public void readServerList(final InputStream inputStream) throws IOException {
  238.         readServerList(inputStream, null);
  239.     }

  240.     /**
  241.      * 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
  242.      * list of unparsed entries (Strings) each referring to a unique file on the server.
  243.      *
  244.      * @param inputStream input stream provided by the server socket.
  245.      * @param charsetName the encoding to be used for reading the stream
  246.      *
  247.      * @throws IOException thrown on any failure to read from the sever.
  248.      */
  249.     public void readServerList(final InputStream inputStream, final String charsetName) throws IOException {
  250.         this.entries = new LinkedList<>();
  251.         read(inputStream, charsetName);
  252.         this.parser.preParse(this.entries);
  253.         resetIterator();
  254.     }

  255.     /**
  256.      * resets this object's internal iterator to the beginning of the list.
  257.      */
  258.     public void resetIterator() {
  259.         this.internalIterator = this.entries.listIterator();
  260.     }

  261. }