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.LinkedList; 25 import java.util.List; 26 import java.util.ListIterator; 27 import java.util.stream.Collectors; 28 29 import org.apache.commons.net.util.Charsets; 30 31 /** 32 * This class handles the entire process of parsing a listing of file entries from the server. 33 * <p> 34 * This object defines a two-part parsing mechanism. 35 * <p> 36 * 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 37 * 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 38 * 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 39 * line feed and <code>preParse()</code> - which handles removal of extraneous matter such as the preliminary lines of a listing, removal of duplicates on 40 * versioning systems, etc. 41 * <p> 42 * 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 43 * 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 44 * 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. 45 * <p> 46 * Examples: 47 * <p> 48 * Paged access: 49 * 50 * <pre> 51 * FTPClient f = FTPClient(); 52 * f.connect(server); 53 * f.login(user, password); 54 * FTPListParseEngine engine = f.initiateListParsing(directory); 55 * 56 * while (engine.hasNext()) { 57 * FTPFile[] files = engine.getNext(25); // "page size" you want 58 * // do whatever you want with these files, display them, etc. 59 * // expensive FTPFile objects not created until needed. 60 * } 61 * </pre> 62 * <p> 63 * For unpaged access, simply use FTPClient.listFiles(). That method uses this class transparently. 64 */ 65 public class FTPListParseEngine { 66 /** 67 * An empty immutable {@code FTPFile} array. 68 */ 69 private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {}; 70 private List<String> entries = new LinkedList<>(); 71 72 private ListIterator<String> internalIterator = entries.listIterator(); 73 private final FTPFileEntryParser parser; 74 75 // Should invalid files (parse failures) be allowed? 76 private final boolean saveUnparseableEntries; 77 78 public FTPListParseEngine(final FTPFileEntryParser parser) { 79 this(parser, null); 80 } 81 82 /** 83 * Intended for use by FTPClient only 84 * 85 * @since 3.4 86 */ 87 FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) { 88 this.parser = parser; 89 if (configuration != null) { 90 this.saveUnparseableEntries = configuration.getUnparseableEntries(); 91 } else { 92 this.saveUnparseableEntries = false; 93 } 94 } 95 96 /** 97 * 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 98 * before being added to the array. 99 * 100 * @param filter FTPFileFilter, must not be <code>null</code>. 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</code>. 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 // DEPRECATED METHODS - for API compatibility only - DO NOT USE 278 279 /** 280 * resets this object's internal iterator to the beginning of the list. 281 */ 282 public void resetIterator() { 283 this.internalIterator = this.entries.listIterator(); 284 } 285 286 }