View Javadoc

1   // FtpClient.java - a FTP client protocol implementation in Java
2   // $Id: FtpClient.java,v 1.7 2007/05/07 18:26:54 sjardine Exp $
3   //
4   // Copyright 1999, 2000 Joe Phillips <jaiger@innovationsw.com>
5   // Copyright 2001, 2002 Innovation Software Group, LLC - http://www.innovationsw.com
6   //
7   // This library is free software; you can redistribute it and/or
8   // modify it under the terms of the GNU Library General Public
9   // License as published by the Free Software Foundation; either
10  // version 2 of the License, or (at your option) any later version.
11  //
12  // This library is distributed in the hope that it will be useful,
13  // but WITHOUT ANY WARRANTY; without even the implied warranty of
14  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  // Library General Public License for more details.
16  //
17  // You should have received a copy of the GNU Library General Public
18  // License along with this library; if not, write to the Free
19  // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  //
21  package gnu.inet.ftp;
22  
23  import java.io.BufferedReader;
24  import java.io.ByteArrayInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.FileInputStream;
27  import java.io.FileNotFoundException;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InputStreamReader;
32  import java.io.OutputStream;
33  import java.io.RandomAccessFile;
34  import java.util.Enumeration;
35  import java.util.Vector;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  /***
41   * This class implements convenience methods that wrapper the FtpClientProtocol
42   * methods for common functionality.
43   * <P>
44   * Most developers will want to use this class rather than the lower-level
45   * FtpClientProtocol methods directly.
46   * 
47   * @see FtpClientProtocol
48   */
49  public class FtpClient extends FtpClientProtocol implements
50  	ConnectionEventSource, TransferEventSource {
51  
52      private boolean passive; // indicate whether passive transfers should
53  
54      private final static Log log = LogFactory.getLog(FtpClient.class);
55  
56      // be used
57      private char mode; // the current mode setting
58  
59      private Vector connectionListeners;
60  
61      private Vector transferListeners;
62  
63      /***
64       * This is a cached PassiveConnection instance. After some hair-pulling I
65       * came to realize that in order to avoid an annoying bug, I needed to cache
66       * the last PassiveConnection instance in case an error was encountered in
67       * the last passive transfer. In my test cases the error condition that
68       * triggered the wierd bug could be duplicated by attempting to
69       * getList(some-non-exising-file). This caused an exception during the LIST
70       * transfer. The next PASV command would return the same IP/Port values.
71       * Opening a second socket to the same IP/Port would yield an exception or
72       * other errors. Caching the last PassiveConnection (and by extension the
73       * Socket) I could reuse the last PassiveConnection values avoiding the
74       * bug/error-condition. This is not very pretty but it works. Unfortunately,
75       * I'm worried that it may not be very portable since it relies on the
76       * behavior of the HylaFAX server. I'll have to see if this behavior is in
77       * the FTP RFC (RFC0959.)
78       * <P>
79       * Whenever a successful passive transfer occurrs, this variable should be
80       * set to null, thereby invalidating the cached value.
81       */
82      protected PassiveConnection connection = null;
83  
84      /***
85       * default constructor. initialize class state.
86       */
87      public FtpClient() {
88  	passive = false; // disable passive transfers by default
89  	mode = MODE_STREAM; // default mode is stream mode
90  	transferListeners = new Vector();
91  	connectionListeners = new Vector();
92      }
93  
94      /***
95       * enable or disable passive transfers
96       * 
97       * @param passive
98       *                indicates whether passive transfers should be used
99       */
100     public synchronized void setPassive(boolean passive) {
101 	this.passive = passive;
102     }
103 
104     /***
105      * check whether we're using passive transfers or not.
106      * 
107      * @return true if passive transfers are enabled, false otherwise
108      */
109     public synchronized boolean getPassive() {
110 	return passive;
111     }
112 
113     /***
114      * set the transfer mode. valid mode values are MODE_* listed in the
115      * FtpClientProtocol class.
116      * 
117      * @param mode
118      *                the new mode setting
119      * @exception IOException
120      *                    an io error occurred talking to the server
121      * @exception ServerResponseException
122      *                    the server replied with an error code
123      */
124     public synchronized void mode(char newMode) throws IOException,
125 	    ServerResponseException {
126 	super.mode(newMode);
127 	this.mode = newMode; // cache the mode for later use
128     }
129 
130     /***
131      * Register a connection listener with the event source.
132      * 
133      * @param listener
134      *                the listener to register with the event source
135      */
136     public void addConnectionListener(ConnectionListener listener) {
137 	connectionListeners.addElement(listener);
138     }
139 
140     /***
141      * Register a set of connection listeners with the event source.
142      * 
143      * @param listeners
144      *                the listeners to register with the event source
145      */
146     public void addConnectionListeners(Vector listeners) {
147 	Enumeration e = listeners.elements();
148 	while (e.hasMoreElements()) {
149 	    ConnectionListener listener = (ConnectionListener) e.nextElement();
150 	    connectionListeners.addElement(listener);
151 	}
152     }
153 
154     /***
155      * De-register a connection listener with the event source.
156      * 
157      * @param listener
158      *                the listener to de-register with the event source
159      */
160     public void removeConnectionListener(ConnectionListener listener) {
161 	connectionListeners.removeElement(listener);
162     }
163 
164     /***
165      * Register a transfer listener with the event source.
166      * 
167      * @param listener
168      *                the listener to register with the event source
169      */
170     public void addTransferListener(TransferListener listener) {
171 	transferListeners.addElement(listener);
172     }
173 
174     /***
175      * Register a set of transfer listeners with the event source.
176      * 
177      * @param listeners
178      *                the listeners to register with the event source
179      */
180     public void addTransferListeners(Vector listeners) {
181 	Enumeration e = listeners.elements();
182 	while (e.hasMoreElements()) {
183 	    TransferListener listener = (TransferListener) e.nextElement();
184 	    transferListeners.addElement(listener);
185 	}
186     }
187 
188     /***
189      * De-register a transfer listener with the event source.
190      * 
191      * @param listener
192      *                the listener to de-register with the event source
193      */
194     public void removeTransferListener(TransferListener listener) {
195 	transferListeners.removeElement(listener);
196     }
197 
198     /***
199      * put a temp file, the data is stored in a uniquely named file on the
200      * server. The remote temp file is deleted when the connection is closed.
201      * NOTE: this calls stot() internally.
202      * 
203      * @exception IOException
204      *                    io error occurred talking to the server
205      * @exception ServerResponseException
206      *                    server replied with error code
207      * @return the filename of the temp file
208      */
209     public synchronized String putTemporary(InputStream data)
210 	    throws IOException, ServerResponseException {
211 	String filename;
212 
213 	Putter put;
214 	if (passive == true) {
215 	    // do a passive transfer
216 	    if (connection == null) {
217 		connection = new PassiveConnection(pasv());
218 	    }
219 	    put = new PassivePutter(data, connection);
220 	} else {
221 	    // do a non-passive (active) transfer
222 	    ActivePutter aput = new ActivePutter(data);
223 	    put = aput;
224 	    port(getInetAddress(), aput.getPort());
225 	}
226 	put.setMode(mode);
227 	put.setType(fileType);
228 	put.addConnectionListeners(connectionListeners);
229 	put.addTransferListeners(transferListeners);
230 	put.start();
231 
232 	// start transmission
233 	try {
234 	    filename = stot(data);
235 	} catch (IOException ioe) {
236 	    put.cancel();
237 	    throw ioe;
238 	} catch (ServerResponseException sree) {
239 	    put.cancel();
240 	    throw sree;
241 	} finally {
242 	    // wait for thread to end
243 	    try {
244 		put.join();
245 	    } catch (InterruptedException ie) { /* not really an error */
246 	    }
247 	}
248 
249 	connection = null;
250 
251 	return filename;
252     }
253 
254     /***
255      * put a file with a unique name. NOTE: this calls stou() internally.
256      * 
257      * @exception IOException
258      *                    a socket IO error occurred
259      * @exception ServerResponseException
260      *                    the server responded with an error code
261      * @return the name of the file created
262      */
263     public synchronized String put(InputStream in) throws IOException,
264 	    ServerResponseException {
265 	String filename;
266 
267 	// prepare to put file
268 	Putter put;
269 	if (passive == true) {
270 	    // passive mode transfers
271 	    if (connection == null) {
272 		connection = new PassiveConnection(pasv());
273 	    }
274 	    put = new PassivePutter(in, connection);
275 	} else {
276 	    // non-passive (active) transfers
277 	    ActivePutter aput = new ActivePutter(in);
278 	    put = aput;
279 	    port(getInetAddress(), aput.getPort()); // tell server where to send
280 	    // data
281 	}
282 	put.setMode(mode);
283 	put.setType(fileType);
284 	put.addConnectionListeners(connectionListeners);
285 	put.addTransferListeners(transferListeners);
286 	put.start();
287 
288 	// transfer file
289 	try {
290 	    filename = stou(in);
291 	} catch (IOException ioe) {
292 	    put.cancel();
293 	    throw ioe;
294 	} catch (ServerResponseException sree) {
295 	    put.cancel();
296 	    throw sree;
297 	} finally {
298 	    // shut down data thread
299 	    try {
300 		put.join();
301 	    } catch (InterruptedException ie) { /* not really an error */
302 	    }
303 	}
304 
305 	connection = null;
306 
307 	return filename;
308     }
309 
310     /***
311      * store a file. NOTE: this calls stor() internally.
312      * 
313      * @param pathname
314      *                name of file to store on server (where to put the file on
315      *                the server)
316      * @exception IOException
317      *                    a socket IO error occurred
318      * @exception ServerResponseException
319      *                    the server responded with an error
320      */
321     public synchronized void put(InputStream in, String pathname)
322 	    throws IOException, ServerResponseException {
323 
324 	// prepare for transfer
325 	Putter put;
326 	if (passive == true) {
327 	    // passive mode transfers
328 	    if (connection == null) {
329 		connection = new PassiveConnection(pasv());
330 	    }
331 	    put = new PassivePutter(in, connection);
332 	} else {
333 	    // non-passive (active) transfers
334 	    ActivePutter aput = new ActivePutter(in);
335 	    put = aput;
336 	    port(getInetAddress(), aput.getPort()); // tell server where to send
337 	    // data
338 	}
339 	put.setMode(mode);
340 	put.setType(fileType);
341 	put.addConnectionListeners(connectionListeners);
342 	put.addTransferListeners(transferListeners);
343 	put.start();
344 
345 	// transfer file
346 	try {
347 	    stor(in, pathname);
348 	} catch (IOException ioe) {
349 	    put.cancel();
350 	    throw ioe;
351 	} catch (ServerResponseException sree) {
352 	    put.cancel();
353 	    throw sree;
354 	} finally {
355 	    // stop putter thread
356 	    try {
357 		put.join();
358 	    } catch (InterruptedException ie) { /* not really an error */
359 	    }
360 	}
361 
362 	connection = null;
363 
364 	// done
365     }
366 
367     /***
368      * get a long-style listing of files in the given directory. NOTE: this
369      * calls the list() method internally.
370      * 
371      * @param path
372      *                the path that we're interested in finding the contents of
373      * @exception IOException
374      *                    an IO error occurred
375      * @exception FileNotFoundException
376      *                    the given path doesn't exist
377      * @exception ServerResponseException
378      *                    the server reported an error
379      * @return a Vector of Strings containing the list information
380      */
381     public synchronized Vector getList(String path) throws IOException,
382 	    FileNotFoundException, ServerResponseException {
383 	Vector filenames = new Vector();
384 	ByteArrayOutputStream buffer = new ByteArrayOutputStream();
385 	Getter spot;
386 
387 	if (passive == true) {
388 	    // do passive style transfers
389 	    if (connection == null) {
390 		connection = new PassiveConnection(pasv());
391 	    }
392 	    spot = new PassiveGetter(buffer, connection);
393 	} else {
394 	    ActiveGetter aget = new ActiveGetter(buffer);
395 	    // tell server which port we'll listen on
396 	    port(getInetAddress(), aget.getPort());
397 	    spot = aget;
398 	}
399 
400 	// start transfer
401 	spot.addConnectionListeners(connectionListeners);
402 	spot.addTransferListeners(transferListeners);
403 	spot.start();
404 
405 	// start the listing ...
406 	try {
407 	    list(path);
408 	} catch (FileNotFoundException fnfe) {
409 	    spot.cancel();
410 	    throw fnfe;
411 	} catch (IOException ioe) {
412 	    spot.cancel();
413 	    throw ioe;
414 	} catch (ServerResponseException sree) {
415 	    spot.cancel();
416 	    throw sree;
417 	} finally {
418 	    // wait for thread to complete
419 	    try {
420 		spot.join();
421 	    } catch (InterruptedException ie) { /* no error */
422 	    }
423 	}
424 
425 	connection = null;
426 
427 	// make list of filenames
428 	BufferedReader data = new BufferedReader(new InputStreamReader(
429 		new ByteArrayInputStream(buffer.toByteArray())));
430 	boolean done = false;
431 	while (!done) {
432 	    String line = data.readLine();
433 	    if (line == null) {
434 		done = true;
435 	    } else {
436 		filenames.addElement(line);
437 	    }
438 	}
439 
440 	return filenames;
441     }
442 
443     /***
444      * get a long-style listing of files in the current directory. NOTE: this
445      * calls the list() method internally with the "." path.
446      * 
447      * @exception IOException
448      *                    an IO error occurred
449      * @exception FileNotFoundException
450      *                    the "." path doesn't exist
451      * @exception ServerResponseException
452      *                    the server reported an error
453      * @return a Vector of Strings containing the list information
454      */
455     public synchronized Vector getList() throws IOException,
456 	    FileNotFoundException, ServerResponseException {
457 	return getList(null);
458     }
459 
460     /***
461      * get name list of files in the given directory. Similar to getList() but
462      * returns filenames only where getList() returns other, system dependant
463      * information.
464      * 
465      * @param path
466      *                the path of the directory that we want the name list of
467      * @exception IOException
468      *                    an IO error occurred
469      * @exception ServerResponseException
470      *                    the server reported an error
471      * @exception FileNotFoundException
472      *                    the requested path does not exist
473      * @return Vector of Strings containing filenames
474      */
475     public synchronized Vector getNameList(String path) throws IOException,
476 	    ServerResponseException, FileNotFoundException {
477 	Vector filenames = new Vector();
478 	ByteArrayOutputStream buffer = new ByteArrayOutputStream();
479 	Getter sparky;
480 
481 	if (passive == true) {
482 	    // do passive style transfers
483 	    if (connection == null) {
484 		connection = new PassiveConnection(pasv());
485 	    }
486 	    sparky = new PassiveGetter(buffer, connection);
487 	} else {
488 	    ActiveGetter aget = new ActiveGetter(buffer);
489 	    // tell server which port we'll listen on
490 	    port(getInetAddress(), aget.getPort());
491 	    sparky = aget;
492 	}
493 
494 	// start transfer
495 	sparky.addConnectionListeners(connectionListeners);
496 	sparky.addTransferListeners(transferListeners);
497 	sparky.start();
498 
499 	// initiate the nlst command...
500 	try {
501 	    nlst(path);
502 	} catch (FileNotFoundException fnfe) {
503 	    sparky.cancel();
504 	    throw fnfe;
505 	} catch (IOException ioe) {
506 	    sparky.cancel();
507 	    throw ioe;
508 	} catch (ServerResponseException sree) {
509 	    sparky.cancel();
510 	    throw sree;
511 	} catch (Exception e) {
512 	    sparky.cancel();
513 	    throw new ServerResponseException(e.getMessage());
514 	} finally {
515 	    // transfer complete
516 	    try {
517 		sparky.join();
518 	    } catch (InterruptedException ie) { /* it's ok */
519 	    }
520 	}
521 
522 	connection = null;
523 
524 	// build file list
525 	BufferedReader data = new BufferedReader(new InputStreamReader(
526 		new ByteArrayInputStream(buffer.toByteArray())));
527 	boolean done = false;
528 	while (!done) {
529 	    String line = data.readLine();
530 	    if (line == null) {
531 		done = true;
532 	    } else {
533 		filenames.addElement(line);
534 	    }
535 	}
536 
537 	return filenames;
538     }
539 
540     /***
541      * get name list of files in the current directory. Similar to getList() but
542      * returns filenames only where getList() returns other, system dependant
543      * information.
544      * 
545      * @exception IOException
546      *                    an IO error occurred
547      * @exception ServerResponseException
548      *                    the server reported an error
549      * @exception FileNotFoundException
550      *                    the requested path does not exist
551      * @return Vector of Strings containing filenames
552      */
553     public synchronized Vector getNameList() throws IOException,
554 	    ServerResponseException, FileNotFoundException {
555 	return getNameList(null);
556     }
557 
558     /***
559      * GET the named file, FTP style.
560      * 
561      * @param path
562      *                the name of the file to GET. This can be a full or partial
563      *                path.
564      * @param out
565      *                the OutputStream to write the file data to
566      * @exception IOException
567      *                    an IO error occurred
568      * @exception ServerResponseException
569      *                    the server reported an error
570      * @exception FileNotFoundException
571      *                    the given path does not exist
572      */
573     public synchronized void get(String path, OutputStream out)
574 	    throws IOException, FileNotFoundException, ServerResponseException {
575 	Getter get;
576 	if (passive == true) {
577 	    // do a passive transfer
578 	    if (connection == null) {
579 		connection = new PassiveConnection(pasv());
580 	    }
581 	    get = new PassiveGetter(out, connection);
582 	} else {
583 	    // do a non-passive (active) transfer
584 	    ActiveGetter aget = new ActiveGetter(out);
585 	    get = aget;
586 	    port(getInetAddress(), aget.getPort());
587 	}
588 	get.setMode(mode);
589 	get.setType(fileType);
590 	get.addConnectionListeners(connectionListeners);
591 	get.addTransferListeners(transferListeners);
592 	get.start();
593 
594 	// start transmission
595 	try {
596 	    retr(path);
597 	} catch (FileNotFoundException fnfe) {
598 	    get.cancel();
599 	    throw fnfe;
600 	} catch (IOException ioe) {
601 	    get.cancel();
602 	    throw ioe;
603 	} catch (ServerResponseException sree) {
604 	    get.cancel();
605 	    throw sree;
606 	} finally {
607 	    // wait for thread to end
608 	    try {
609 		get.join();
610 	    } catch (InterruptedException ie) { /* not really an error */
611 	    }
612 	}
613 
614 	connection = null;
615 
616     }
617 
618     /***
619      * Renames remote file from source to target. Uses rnfr and rnto internally.
620      * 
621      * @param source
622      *                file to rename
623      * @param target
624      *                new filename
625      * @exception IOException
626      *                    an IO error occurred
627      * @exception ServerResponseException
628      *                    the server reported an error
629      * @exception FileNotFoundException
630      *                    the source file does not exist
631      */
632     public synchronized void rename(String source, String target)
633 	    throws IOException, ServerResponseException, FileNotFoundException {
634 	rnfr(source);
635 	rnto(target);
636 	return;
637     }
638 
639     /***
640      * run some basic tests. eventually this method should be removed in favor
641      * of a decent testing framework.
642      * 
643      * @param Arguments
644      *                an array of command-line-argument Strings
645      */
646     public static void main(String Arguments[]) {
647 	FtpClient c = new FtpClient();
648 	try {
649 	    c.open("localhost");
650 	    c.noop();
651 
652 	    c.setPassive(true); // use passive transfers
653 
654 	    c.user("jaiger");
655 
656 	    c.type(TYPE_IMAGE); // should cause files to remain same size after
657 	    // being transfered back and forth
658 
659 	    System.out.println("current directory is: " + c.pwd());
660 
661 	    c.cwd("docq");
662 	    // c.cwd("bad-directory-name");
663 	    System.out.println("current directory is: " + c.pwd());
664 
665 	    c.cdup();
666 	    System.out.println("current directory is: " + c.pwd());
667 
668 	    // c.admin("MyPassword");
669 	    System.out.println("idle timer set to " + c.idle() + " seconds.");
670 	    c.idle(1800);
671 	    System.out.println("idle timer set to " + c.idle() + " seconds.");
672 
673 	    // set file structure
674 	    c.stru(STRU_FILE);
675 	    // c.stru(STRU_RECORD);
676 	    c.stru(STRU_TIFF);
677 	    // c.stru(STRU_PAGE);
678 	    c.stru(STRU_FILE);
679 
680 	    // send temp file (stot)
681 	    {
682 		String filename = "test.ps";
683 		FileInputStream file = new FileInputStream(filename);
684 
685 		String f = c.putTemporary(file);
686 		System.out.println("filename= " + f);
687 
688 		// test size command
689 		long local_size, remote_size;
690 		local_size = (new RandomAccessFile(filename, "r").length());
691 		remote_size = c.size(f);
692 		System.out.println(filename + " local size is " + local_size);
693 		System.out.println(f + " remote size is " + remote_size);
694 
695 		// retrieve the temp file now
696 		FileOutputStream out_file = new FileOutputStream(filename
697 			+ ".retr");
698 		c.get(f, out_file);
699 		local_size = (new RandomAccessFile(filename + ".retr", "r")
700 			.length());
701 		System.out.println(filename + ".retr size is " + local_size);
702 
703 		// retrieve the temp file now (using ZLIB mode)
704 		FileOutputStream zip_file = new FileOutputStream(filename
705 			+ ".gz");
706 		c.mode(MODE_ZLIB);
707 		c.get(f, zip_file);
708 		local_size = (new RandomAccessFile(filename + ".gz", "r")
709 			.length());
710 		System.out.println(filename + ".gz size is " + local_size);
711 		c.mode(MODE_STREAM);
712 
713 	    }
714 	    // end stot/retr test
715 
716 	    // test list command
717 	    {
718 		Vector files;
719 		int counter;
720 
721 		// list current directory
722 		files = c.getList();
723 		for (counter = 0; counter < files.size(); counter++) {
724 		    System.out.println((String) files.elementAt(counter));
725 		}
726 
727 		// list /tmp directory
728 		files = c.getList("/tmp");
729 		for (counter = 0; counter < files.size(); counter++) {
730 		    System.out.println((String) files.elementAt(counter));
731 		}
732 
733 		// list /tmp directory (with mode ZLIB)
734 		c.mode(MODE_ZLIB);
735 		files = c.getList("/tmp");
736 		for (counter = 0; counter < files.size(); counter++) {
737 		    System.out.println((String) files.elementAt(counter));
738 		}
739 		c.mode(MODE_STREAM);
740 
741 		try {
742 		    // attempt to list file that doesn't exist
743 		    c.getList("/joey-joe-joe-jr.shabba-do"); // that's the
744 		    // worst name
745 		    // I've ever
746 		    // heard.
747 		    System.out.println("ERROR: file not found was expected");
748 		} catch (FileNotFoundException fnfe) {
749 		    // expected this, continue
750 		    System.out.println("GOOD: file not found, as expected");
751 		}
752 
753 		// list current directory, should be the same as above
754 		files = c.getList();
755 		for (counter = 0; counter < files.size(); counter++) {
756 		    System.out.println((String) files.elementAt(counter));
757 		}
758 	    }
759 	    // end list test
760 
761 	    // test nlst command
762 	    {
763 		Vector files;
764 		int counter;
765 
766 		// list /tmp directory
767 		files = c.getNameList("/tmp");
768 		for (counter = 0; counter < files.size(); counter++) {
769 		    System.out.println((String) files.elementAt(counter));
770 		}
771 		// list /tmp directory
772 		files = c.getNameList("/tmp");
773 		for (counter = 0; counter < files.size(); counter++) {
774 		    System.out.println((String) files.elementAt(counter));
775 		}
776 
777 		// list /tmp directory (using mode ZLIB)
778 		c.mode(MODE_ZLIB);
779 		files = c.getNameList("/tmp");
780 		for (counter = 0; counter < files.size(); counter++) {
781 		    System.out.println((String) files.elementAt(counter));
782 		}
783 		c.mode(MODE_STREAM);
784 
785 		// list current directory
786 		files = c.getNameList();
787 		for (counter = 0; counter < files.size(); counter++) {
788 		    System.out.println((String) files.elementAt(counter));
789 		}
790 
791 	    }
792 	    // end nlst test
793 
794 	    // get system type string
795 	    String system = c.syst();
796 	    System.out.println("system type: " + system + ".");
797 
798 	    c.noop();
799 
800 	    // stat tests
801 	    {
802 		// test normal server status message
803 		Vector status = c.stat();
804 		int counter;
805 		for (counter = 0; counter < status.size(); counter++) {
806 		    System.out.println(status.elementAt(counter));
807 		}
808 
809 		// test directory status
810 		status = c.stat("docq");
811 		for (counter = 0; counter < status.size(); counter++) {
812 		    System.out.println(status.elementAt(counter));
813 		}
814 
815 		// test non-existing directory status
816 		try {
817 		    status = c.stat("joey-joe-joe-junior-shabba-do");
818 		    for (counter = 0; counter < status.size(); counter++) {
819 			System.out.println(status.elementAt(counter));
820 		    }
821 		} catch (FileNotFoundException fnfe) {
822 		    System.out
823 			    .println("GOOD: file not found.  this is what we expected");
824 		}
825 	    }
826 
827 	    c.noop();
828 
829 	    c.quit();
830 
831 	} catch (Exception e) {
832 	    log.error(e.getMessage(), e);
833 	}
834 	System.out.println("main: end");
835     }
836 }
837 
838 // end of file