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.LinkedList; 025import java.util.List; 026import java.util.ListIterator; 027import java.util.stream.Collectors; 028 029import org.apache.commons.net.util.Charsets; 030 031/** 032 * This class handles the entire process of parsing a listing of file entries from the server. 033 * <p> 034 * This object defines a two-part parsing mechanism. 035 * <p> 036 * 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 037 * 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 038 * 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 039 * line feed and <code>preParse()</code> - which handles removal of extraneous matter such as the preliminary lines of a listing, removal of duplicates on 040 * versioning systems, etc. 041 * <p> 042 * 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 043 * 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 044 * 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. 045 * <p> 046 * Examples: 047 * <p> 048 * Paged access: 049 * 050 * <pre> 051 * FTPClient f = FTPClient(); 052 * f.connect(server); 053 * f.login(user, password); 054 * FTPListParseEngine engine = f.initiateListParsing(directory); 055 * 056 * while (engine.hasNext()) { 057 * FTPFile[] files = engine.getNext(25); // "page size" you want 058 * // do whatever you want with these files, display them, etc. 059 * // expensive FTPFile objects not created until needed. 060 * } 061 * </pre> 062 * <p> 063 * For unpaged access, simply use FTPClient.listFiles(). That method uses this class transparently. 064 */ 065public class FTPListParseEngine { 066 /** 067 * An empty immutable {@code FTPFile} array. 068 */ 069 private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {}; 070 private List<String> entries = new LinkedList<>(); 071 072 private ListIterator<String> internalIterator = entries.listIterator(); 073 private final FTPFileEntryParser parser; 074 075 // Should invalid files (parse failures) be allowed? 076 private final boolean saveUnparseableEntries; 077 078 public FTPListParseEngine(final FTPFileEntryParser parser) { 079 this(parser, null); 080 } 081 082 /** 083 * Intended for use by FTPClient only 084 * 085 * @since 3.4 086 */ 087 FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) { 088 this.parser = parser; 089 if (configuration != null) { 090 this.saveUnparseableEntries = configuration.getUnparseableEntries(); 091 } else { 092 this.saveUnparseableEntries = false; 093 } 094 } 095 096 /** 097 * 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 098 * before being added to the array. 099 * 100 * @param filter FTPFileFilter, must not be {@code null}. 101 * 102 * @return a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. 103 * <p> 104 * <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 105 * null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries. 106 * @since 3.9.0 107 */ 108 public List<FTPFile> getFileList(final FTPFileFilter filter) { 109 return entries.stream().map(e -> { 110 final FTPFile file = parser.parseFTPEntry(e); 111 return file == null && saveUnparseableEntries ? new FTPFile(e) : file; 112 }).filter(filter::accept).collect(Collectors.toList()); 113 } 114 115 /** 116 * Returns an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. 117 * 118 * @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 119 * be null 120 * @throws IOException - not ever thrown, may be removed in a later release 121 */ 122 public FTPFile[] getFiles() throws IOException // TODO remove; not actually thrown 123 { 124 return getFiles(FTPFileFilters.NON_NULL); 125 } 126 127 /** 128 * 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 129 * before being added to the array. 130 * 131 * @param filter FTPFileFilter, must not be {@code null}. 132 * 133 * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. 134 * <p> 135 * <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 136 * null before referencing it, or use a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries. 137 * @since 2.2 138 * @throws IOException - not ever thrown, may be removed in a later release 139 */ 140 public FTPFile[] getFiles(final FTPFileFilter filter) throws IOException // TODO remove; not actually thrown 141 { 142 return getFileList(filter).toArray(EMPTY_FTP_FILE_ARRAY); 143 } 144 145 /** 146 * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position. If fewer than 147 * <code>quantityRequested</code> such elements are available, the returned array will have a length equal to the number of entries at and after the 148 * current position. If no such entries are found, this array will have a length of 0. 149 * 150 * 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. 151 * 152 * @param quantityRequested the maximum number of entries we want to get. 153 * 154 * @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 155 * the number of elements which exist in the list at and after its current position. 156 * <p> 157 * <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 158 * null before referencing it. 159 */ 160 public FTPFile[] getNext(final int quantityRequested) { 161 final List<FTPFile> tmpResults = new LinkedList<>(); 162 int count = quantityRequested; 163 while (count > 0 && this.internalIterator.hasNext()) { 164 final String entry = this.internalIterator.next(); 165 FTPFile temp = this.parser.parseFTPEntry(entry); 166 if (temp == null && saveUnparseableEntries) { 167 temp = new FTPFile(entry); 168 } 169 tmpResults.add(temp); 170 count--; 171 } 172 return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); 173 174 } 175 176 /** 177 * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position, and working 178 * back toward the beginning. 179 * 180 * 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 181 * after the current position. If no such entries are found, this array will have a length of 0. 182 * 183 * 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. 184 * 185 * @param quantityRequested the maximum number of entries we want to get. 186 * 187 * @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 188 * 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 189 * (not reversed). 190 * <p> 191 * <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 192 * null before referencing it. 193 */ 194 public FTPFile[] getPrevious(final int quantityRequested) { 195 final List<FTPFile> tmpResults = new LinkedList<>(); 196 int count = quantityRequested; 197 while (count > 0 && this.internalIterator.hasPrevious()) { 198 final String entry = this.internalIterator.previous(); 199 FTPFile temp = this.parser.parseFTPEntry(entry); 200 if (temp == null && saveUnparseableEntries) { 201 temp = new FTPFile(entry); 202 } 203 tmpResults.add(0, temp); 204 count--; 205 } 206 return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); 207 } 208 209 /** 210 * convenience method to allow clients to know whether this object's internal iterator's current position is at the end of the list. 211 * 212 * @return true if internal iterator is not at end of list, false otherwise. 213 */ 214 public boolean hasNext() { 215 return internalIterator.hasNext(); 216 } 217 218 /** 219 * convenience method to allow clients to know whether this object's internal iterator's current position is at the beginning of the list. 220 * 221 * @return true if internal iterator is not at beginning of list, false otherwise. 222 */ 223 public boolean hasPrevious() { 224 return internalIterator.hasPrevious(); 225 } 226 227 /** 228 * Internal method for reading (and closing) the input into the <code>entries</code> list. After this method has completed, <code>entries</code> will 229 * contain a collection of entries (as defined by <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain various non-entry preliminary lines 230 * from the server output, duplicates, and other data that will not be part of the final listing. 231 * 232 * @param inputStream The socket stream on which the input will be read. 233 * @param charsetName The encoding to use. 234 * 235 * @throws IOException thrown on any failure to read the stream 236 */ 237 private void read(final InputStream inputStream, final String charsetName) throws IOException { 238 try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) { 239 240 String line = this.parser.readNextEntry(reader); 241 242 while (line != null) { 243 this.entries.add(line); 244 line = this.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 * 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 this.entries = new LinkedList<>(); 272 read(inputStream, charsetName); 273 this.parser.preParse(this.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 this.internalIterator = this.entries.listIterator(); 282 } 283 284}