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 18 package org.apache.commons.net.ftp; 19 20 import java.io.BufferedReader; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.InputStreamReader; 24 import java.util.ArrayList; 25 import java.util.Iterator; 26 import java.util.LinkedList; 27 import java.util.List; 28 import java.util.ListIterator; 29 30 import org.apache.commons.net.util.Charsets; 31 32 33 /** 34 * This class handles the entire process of parsing a listing of 35 * file entries from the server. 36 * <p> 37 * This object defines a two-part parsing mechanism. 38 * <p> 39 * The first part is comprised of reading the raw input into an internal 40 * list of strings. Every item in this list corresponds to an actual 41 * file. All extraneous matter emitted by the server will have been 42 * removed by the end of this phase. This is accomplished in conjunction 43 * with the FTPFileEntryParser associated with this engine, by calling 44 * its methods <code>readNextEntry()</code> - which handles the issue of 45 * what delimits one entry from another, usually but not always a line 46 * feed and <code>preParse()</code> - which handles removal of 47 * extraneous matter such as the preliminary lines of a listing, removal 48 * of duplicates on versioning systems, etc. 49 * <p> 50 * The second part is composed of the actual parsing, again in conjunction 51 * with the particular parser used by this engine. This is controlled 52 * by an iterator over the internal list of strings. This may be done 53 * either in block mode, by calling the <code>getNext()</code> and 54 * <code>getPrevious()</code> methods to provide "paged" output of less 55 * than the whole list at one time, or by calling the 56 * <code>getFiles()</code> method to return the entire list. 57 * <p> 58 * Examples: 59 * <p> 60 * Paged access: 61 * <pre> 62 * FTPClient f=FTPClient(); 63 * f.connect(server); 64 * f.login(username, password); 65 * FTPListParseEngine engine = f.initiateListParsing(directory); 66 * 67 * while (engine.hasNext()) { 68 * FTPFile[] files = engine.getNext(25); // "page size" you want 69 * //do whatever you want with these files, display them, etc. 70 * //expensive FTPFile objects not created until needed. 71 * } 72 * </pre> 73 * <p> 74 * For unpaged access, simply use FTPClient.listFiles(). That method 75 * uses this class transparently. 76 * @version $Id: FTPListParseEngine.java 1414510 2012-11-28 02:40:39Z ggregory $ 77 */ 78 public class FTPListParseEngine { 79 private List<String> entries = new LinkedList<String>(); 80 private ListIterator<String> _internalIterator = entries.listIterator(); 81 82 private final FTPFileEntryParser parser; 83 84 public FTPListParseEngine(FTPFileEntryParser parser) { 85 this.parser = parser; 86 } 87 88 /** 89 * handle the initial reading and preparsing of the list returned by 90 * the server. After this method has completed, this object will contain 91 * a list of unparsed entries (Strings) each referring to a unique file 92 * on the server. 93 * 94 * @param stream input stream provided by the server socket. 95 * @param encoding the encoding to be used for reading the stream 96 * 97 * @exception IOException 98 * thrown on any failure to read from the sever. 99 */ 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 }