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 */ 077public class FTPListParseEngine { 078 private List<String> entries = new LinkedList<>(); 079 private ListIterator<String> internalIterator = entries.listIterator(); 080 081 private final FTPFileEntryParser parser; 082 // Should invalid files (parse failures) be allowed? 083 private final boolean saveUnparseableEntries; 084 085 /** 086 * An empty immutable {@code FTPFile} array. 087 */ 088 private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = new FTPFile[0]; 089 090 public FTPListParseEngine(final FTPFileEntryParser parser) { 091 this(parser, null); 092 } 093 094 /** 095 * Intended for use by FTPClient only 096 * @since 3.4 097 */ 098 FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) { 099 this.parser = parser; 100 if (configuration != null) { 101 this.saveUnparseableEntries = configuration.getUnparseableEntries(); 102 } else { 103 this.saveUnparseableEntries = false; 104 } 105 } 106 107 /** 108 * Reads (and closes) the initial reading and preparsing of the list returned by the server. After this method has 109 * completed, this object will contain a list of unparsed entries (Strings) each referring to a unique file on the 110 * server. 111 * 112 * @param inputStream input stream provided by the server socket. 113 * @param charsetName the encoding to be used for reading the stream 114 * 115 * @throws IOException thrown on any failure to read from the sever. 116 */ 117 public void readServerList(final InputStream inputStream, final String charsetName) throws IOException { 118 this.entries = new LinkedList<>(); 119 read(inputStream, charsetName); 120 this.parser.preParse(this.entries); 121 resetIterator(); 122 } 123 124 /** 125 * Internal method for reading (and closing) the input into the <code>entries</code> list. After this method has 126 * completed, <code>entries</code> will contain a collection of entries (as defined by 127 * <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain various non-entry preliminary lines from 128 * the server output, duplicates, and other data that will not be part of the final listing. 129 * 130 * @param inputStream The socket stream on which the input will be read. 131 * @param charsetName The encoding to use. 132 * 133 * @throws IOException thrown on any failure to read the stream 134 */ 135 private void read(final InputStream inputStream, final String charsetName) throws IOException { 136 try (final BufferedReader reader = new BufferedReader( 137 new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) { 138 139 String line = this.parser.readNextEntry(reader); 140 141 while (line != null) { 142 this.entries.add(line); 143 line = this.parser.readNextEntry(reader); 144 } 145 } 146 } 147 148 /** 149 * Returns an array of at most <code>quantityRequested</code> FTPFile 150 * objects starting at this object's internal iterator's current position. 151 * If fewer than <code>quantityRequested</code> such 152 * elements are available, the returned array will have a length equal 153 * to the number of entries at and after after the current position. 154 * 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 157 * by a number of positions equal to the size of the array returned. 158 * 159 * @param quantityRequested 160 * the maximum number of entries we want to get. 161 * 162 * @return an array of at most <code>quantityRequested</code> FTPFile 163 * objects starting at the current position of this iterator within its 164 * list and at least the number of elements which exist in the list at 165 * and after its current position. 166 * <p><b> 167 * NOTE:</b> This array may contain null members if any of the 168 * individual file listings failed to parse. The caller should 169 * check each entry for null before referencing it. 170 */ 171 public FTPFile[] getNext(final int quantityRequested) { 172 final List<FTPFile> tmpResults = new LinkedList<>(); 173 int count = quantityRequested; 174 while (count > 0 && this.internalIterator.hasNext()) { 175 final String entry = this.internalIterator.next(); 176 FTPFile temp = this.parser.parseFTPEntry(entry); 177 if (temp == null && saveUnparseableEntries) { 178 temp = new FTPFile(entry); 179 } 180 tmpResults.add(temp); 181 count--; 182 } 183 return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); 184 185 } 186 187 /** 188 * Returns an array of at most <code>quantityRequested</code> FTPFile 189 * objects starting at this object's internal iterator's current position, 190 * and working back toward the beginning. 191 * 192 * If fewer than <code>quantityRequested</code> such 193 * elements are available, the returned array will have a length equal 194 * to the number of entries at and after after the current position. 195 * If no such entries are found, this array will have a length of 0. 196 * 197 * After this method is called this object's internal iterator is moved 198 * back by a number of positions equal to the size of the array returned. 199 * 200 * @param quantityRequested 201 * the maximum number of entries we want to get. 202 * 203 * @return an array of at most <code>quantityRequested</code> FTPFile 204 * objects starting at the current position of this iterator within its 205 * list and at least the number of elements which exist in the list at 206 * and after its current position. This array will be in the same order 207 * as the underlying list (not reversed). 208 * <p><b> 209 * NOTE:</b> This array may contain null members if any of the 210 * individual file listings failed to parse. The caller should 211 * check each entry for null before referencing it. 212 */ 213 public FTPFile[] getPrevious(final int quantityRequested) { 214 final List<FTPFile> tmpResults = new LinkedList<>(); 215 int count = quantityRequested; 216 while (count > 0 && this.internalIterator.hasPrevious()) { 217 final String entry = this.internalIterator.previous(); 218 FTPFile temp = this.parser.parseFTPEntry(entry); 219 if (temp == null && saveUnparseableEntries) { 220 temp = new FTPFile(entry); 221 } 222 tmpResults.add(0,temp); 223 count--; 224 } 225 return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); 226 } 227 228 /** 229 * Returns an array of FTPFile objects containing the whole list of 230 * files returned by the server as read by this object's parser. 231 * 232 * @return an array of FTPFile objects containing the whole list of 233 * files returned by the server as read by this object's parser. 234 * None of the entries will be null 235 * @throws IOException - not ever thrown, may be removed in a later release 236 */ 237 public FTPFile[] getFiles() 238 throws IOException // TODO remove; not actually thrown 239 { 240 return getFiles(FTPFileFilters.NON_NULL); 241 } 242 243 /** 244 * Returns an array of FTPFile objects containing the whole list of 245 * files returned by the server as read by this object's parser. 246 * The files are filtered before being added to the array. 247 * 248 * @param filter FTPFileFilter, must not be <code>null</code>. 249 * 250 * @return an array of FTPFile objects containing the whole list of 251 * files returned by the server as read by this object's parser. 252 * <p><b> 253 * NOTE:</b> This array may contain null members if any of the 254 * individual file listings failed to parse. The caller should 255 * check each entry for null before referencing it, or use the 256 * a filter such as {@link FTPFileFilters#NON_NULL} which does not 257 * allow null entries. 258 * @since 2.2 259 * @throws IOException - not ever thrown, may be removed in a later release 260 */ 261 public FTPFile[] getFiles(final FTPFileFilter filter) 262 throws IOException // TODO remove; not actually thrown 263 { 264 final List<FTPFile> tmpResults = new ArrayList<>(); 265 final Iterator<String> iter = this.entries.iterator(); 266 while (iter.hasNext()) { 267 final String entry = iter.next(); 268 FTPFile temp = this.parser.parseFTPEntry(entry); 269 if (temp == null && saveUnparseableEntries) { 270 temp = new FTPFile(entry); 271 } 272 if (filter.accept(temp)) { 273 tmpResults.add(temp); 274 } 275 } 276 return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); 277 278 } 279 280 /** 281 * convenience method to allow clients to know whether this object's 282 * internal iterator's current position is at the end of the list. 283 * 284 * @return true if internal iterator is not at end of list, false 285 * otherwise. 286 */ 287 public boolean hasNext() { 288 return internalIterator.hasNext(); 289 } 290 291 /** 292 * convenience method to allow clients to know whether this object's 293 * internal iterator's current position is at the beginning of the list. 294 * 295 * @return true if internal iterator is not at beginning of list, false 296 * otherwise. 297 */ 298 public boolean hasPrevious() { 299 return internalIterator.hasPrevious(); 300 } 301 302 /** 303 * resets this object's internal iterator to the beginning of the list. 304 */ 305 public void resetIterator() { 306 this.internalIterator = this.entries.listIterator(); 307 } 308 309 // DEPRECATED METHODS - for API compatibility only - DO NOT USE 310 311 /** 312 * Do not use. 313 * @param inputStream the stream from which to read 314 * @throws IOException on error 315 * @deprecated use {@link #readServerList(InputStream, String)} instead 316 */ 317 @Deprecated 318 public void readServerList(final InputStream inputStream) throws IOException { 319 readServerList(inputStream, null); 320 } 321 322}