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