View Javadoc

1   /*
2    * $HeadURL: $
3    * $Date: $
4    * $Revision: $
5    * $Author: $
6    * 
7    * Copyright (c) 2005 MindTree Consulting Ltd. 
8    * 
9    * This file is part of Insight.
10   * 
11   * Insight is free software: you can redistribute it and/or modify it under the 
12   * terms of the GNU General Public License as published by the Free Software 
13   * Foundation, either version 3 of the License, or (at your option) any later 
14   * version.
15   * 
16   * Insight is distributed in the hope that it will be useful, but 
17   * WITHOUT ANY WARRANTY; without even the implied warranty of 
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
19   * Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public License along with 
22   * Insight.  If not, see <http://www.gnu.org/licenses/>.
23   */
24  
25  package com.mindtree.techworks.insight.download.ftpbrowse;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.net.InetAddress;
30  import java.net.MalformedURLException;
31  import java.net.PasswordAuthentication;
32  import java.net.SocketException;
33  import java.net.URL;
34  import java.net.UnknownHostException;
35  import java.util.logging.Level;
36  import java.util.logging.Logger;
37  
38  import javax.swing.filechooser.FileSystemView;
39  
40  import org.apache.commons.net.ftp.FTPClient;
41  import org.apache.commons.net.ftp.FTPFile;
42  import org.apache.commons.net.ftp.FTPListParseEngine;
43  import org.apache.commons.net.ftp.FTPReply;
44  
45  /**
46   * This class is used for browsing a remote file system. This class uses an
47   * instance of the {@link org.apache.commons.net.ftp.FTPClient FTPClient}to
48   * connect to the underlying FTP Source. Connection timeouts may occur.
49   * 
50   * @see javax.swing.filechooser.FileSystemView FileSystemView
51   * @author Bindul Bhowmik
52   * @version $Revision: 27 $ $Date: 2007-12-16 04:58:03 -0700 (Sun, 16 Dec 2007) $
53   */
54  public class FTPRemoteFileSystemView extends FileSystemView {
55  
56  	//
57  	// Constants
58  	//
59  
60  	/**
61  	 * The logger instance for this class
62  	 */
63  	protected static final Logger logger = Logger
64  			.getLogger(FTPRemoteFileSystemView.class.getName());
65  
66  	/**
67  	 * Password Authentication to use for anonymous access
68  	 */
69  	public static final PasswordAuthentication anonPassAuth = new PasswordAuthentication(
70  			"anonymous", "insight@mindtree.com".toCharArray());
71  
72  	/**
73  	 * The root file system path
74  	 */
75  	protected static final String FILE_SYSTEM_ROOT_NAME = "/";
76  
77  	/**
78  	 * Seperator character between files
79  	 */
80  	public static final String FILE_SEPERATOR = "/";
81  
82  	//
83  	// Instance variables
84  	//
85  
86  	/**
87  	 * The FTPClient Object used to browse the underlying source.
88  	 */
89  	private FTPClient ftpClient;
90  
91  	/**
92  	 * The URL String to the underlying connection
93  	 */
94  	private URL url;
95  
96  	/**
97  	 * The Authentication information
98  	 */
99  	private PasswordAuthentication passwordAuthentication;
100 
101 	/**
102 	 * The default directory of the connection
103 	 */
104 	private String homeDirectory;
105 
106 	//
107 	// Constructors
108 	//
109 
110 	/**
111 	 * Creates a new instance of the FTP Remote File System View.
112 	 * 
113 	 * @param url
114 	 * @param passwordAuthentication
115 	 */
116 	public FTPRemoteFileSystemView (URL url,
117 			PasswordAuthentication passwordAuthentication) {
118 
119 		this.url = url;
120 		if (null != passwordAuthentication) {
121 			this.passwordAuthentication = passwordAuthentication;
122 		} else {
123 			this.passwordAuthentication = anonPassAuth;
124 		}
125 	}
126 
127 	/**
128 	 * Creates an instance of the <code>FTPRemoteFileSystemView</code>. The
129 	 * host name supplied should be the same as one would pass to construct an
130 	 * URL object.
131 	 * 
132 	 * @see URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
133 	 *      URL
134 	 * @param host The address of the host. This can be a host name or IP
135 	 *            address.
136 	 * @param port The port to connect to.
137 	 * @param passwordAuthentication The credentials to connect with. This can
138 	 *            be null.
139 	 * @throws FTPBrowseException If a <code>URL</code> cannot be formed with
140 	 *             the values.
141 	 */
142 	public FTPRemoteFileSystemView (String host, int port,
143 			PasswordAuthentication passwordAuthentication)
144 			throws FTPBrowseException {
145 
146 		try {
147 			url = new URL("ftp", host, port, null);
148 			if (null != passwordAuthentication) {
149 				this.passwordAuthentication = passwordAuthentication;
150 			} else {
151 				this.passwordAuthentication = anonPassAuth;
152 			}
153 		} catch (MalformedURLException e) {
154 			throw new FTPBrowseException(e.getMessage());
155 		}
156 	}
157 
158 	/**
159 	 * Creates an instance of the <code>FTPRemoteFileSystemView</code>. The
160 	 * host name supplied should be the same as one would pass to construct an
161 	 * URL object.
162 	 * 
163 	 * @see URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
164 	 *      URL
165 	 * @param host The address of the host. This can be a host name or IP
166 	 *            address.
167 	 * @param passwordAuthentication The credentials to use to connect. Can be
168 	 *            null.
169 	 * @throws FTPBrowseException If a <code>URL</code> cannot be formed with
170 	 *             the values.
171 	 */
172 	public FTPRemoteFileSystemView (String host,
173 			PasswordAuthentication passwordAuthentication)
174 			throws FTPBrowseException {
175 
176 		try {
177 			url = new URL("ftp", host, null);
178 			if (null != passwordAuthentication) {
179 				this.passwordAuthentication = passwordAuthentication;
180 			} else {
181 				this.passwordAuthentication = anonPassAuth;
182 			}
183 		} catch (MalformedURLException e) {
184 			throw new FTPBrowseException(e.getMessage());
185 		}
186 	}
187 	
188 	//
189 	// Accessors
190 	//
191 
192 	/**
193 	 * @return Returns the passwordAuthentication.
194 	 */
195 	public PasswordAuthentication getPasswordAuthentication () {
196 
197 		return passwordAuthentication;
198 	}
199 	
200 	/**
201 	 * @return Returns the url.
202 	 */
203 	public URL getUrl () {
204 
205 		return url;
206 	}
207 	
208 	//
209 	// Public Methods
210 	//
211 	
212 	/**
213 	 * If a connection to the server is still open, disconnects it.
214 	 */
215 	public void disconnect () {
216 
217 		if (null != ftpClient && ftpClient.isConnected()) {
218 			try {
219 				ftpClient.disconnect();
220 			} catch (IOException e) {
221 				logger.log(Level.WARNING, "IOEx while disconnecting", e);
222 			}
223 		}
224 	}
225 
226 	//
227 	// Utility Methods
228 	//
229 
230 	/**
231 	 * Creates an FTP Connection
232 	 * 
233 	 * @throws FTPBrowseException If the connection cannot be opened
234 	 */
235 	private synchronized void createFTPConnection () throws FTPBrowseException {
236 
237 		ftpClient = new FTPClient();
238 		try {
239 			InetAddress inetAddress = InetAddress.getByName(url.getHost());
240 			// The user might have specified the port to connect to (if other
241 			// than the default FTP port). URL API specifies that -1 is returned
242 			// if no port is set. Otherwise the use the port.
243 			if (url.getPort() == -1) {
244 				ftpClient.connect(inetAddress);
245 			} else {
246 				ftpClient.connect(inetAddress, url.getPort());
247 			}
248 
249 			if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
250 				throw new FTPBrowseException(ftpClient.getReplyString());
251 			}
252 			if (null != passwordAuthentication) {
253 				ftpClient.login(passwordAuthentication.getUserName(),
254 						new StringBuffer().append(
255 								passwordAuthentication.getPassword())
256 								.toString());
257 			}
258 			if (url.getPath().length() > 0) {
259 				ftpClient.changeWorkingDirectory(url.getPath());
260 			}
261 			homeDirectory = ftpClient.printWorkingDirectory();
262 		} catch (UnknownHostException e) {
263 			throw new FTPBrowseException(e.getMessage());
264 		} catch (SocketException e) {
265 			throw new FTPBrowseException(e.getMessage());
266 		} catch (FTPBrowseException e) {
267 			throw e;
268 		} catch (IOException e) {
269 			throw new FTPBrowseException(e.getMessage());
270 		}
271 	}
272 
273 	/**
274 	 * Private method used to check if an open connection is present, else
275 	 * creates one.
276 	 * 
277 	 * @throws FTPBrowseException FTPBrowseException is thrown if the connection
278 	 *             cannot be opened.
279 	 */
280 	private void checkConnection () throws FTPBrowseException {
281 
282 		if (null == ftpClient || !ftpClient.isConnected()) {
283 			createFTPConnection();
284 		}
285 	}
286 
287 	//
288 	// Overridden methods from FileSystemView
289 	//
290 
291 	/**
292 	 * @see javax.swing.filechooser.FileSystemView#createNewFolder(java.io.File)
293 	 */
294 	public File createNewFolder (File containingDir) throws IOException {
295 
296 		// This is a read only view of the remote system only.
297 		throw new FTPBrowseException(
298 				"This file system view supports READ ONLY support ONLY!");
299 	}
300 
301 	/**
302 	 * In the remote view home and default directory are considered to be the
303 	 * same.
304 	 * 
305 	 * @see javax.swing.filechooser.FileSystemView#getDefaultDirectory()
306 	 */
307 	public File getDefaultDirectory () {
308 
309 		return getHomeDirectory();
310 	}
311 
312 	/**
313 	 * @see javax.swing.filechooser.FileSystemView#getHomeDirectory()
314 	 */
315 	public File getHomeDirectory () {
316 
317 		try {
318 			checkConnection();
319 			//If home directory is the root directory then return that
320 			if (homeDirectory.equals(FILE_SYSTEM_ROOT_NAME)) {
321 				return getRoots()[0];
322 			}
323 
324 			// Get the file information
325 			FTPFileFile ftpFileFile = null;
326 			try {
327 				String parent = homeDirectory.substring(0, homeDirectory
328 						.lastIndexOf(FILE_SEPERATOR));
329 				ftpClient.changeWorkingDirectory(parent);
330 				FTPListParseEngine ftpListParseEngine = ftpClient
331 						.initiateListParsing();
332 				FTPFile [] returnedFiles = ftpListParseEngine.getFiles();
333 				String dirName = homeDirectory.substring(homeDirectory
334 						.lastIndexOf(FILE_SEPERATOR) + 1);
335 				for (int i = 0; i < returnedFiles.length; i++ ) {
336 					if (returnedFiles[i].getName().equals(dirName)) {
337 						returnedFiles[i].setName(parent + FILE_SEPERATOR
338 								+ returnedFiles[i].getName());
339 						ftpFileFile = new FTPFileFile(returnedFiles[i], this);
340 					}
341 				}
342 			} catch (FTPBrowseException e) {
343 				logger.log(Level.WARNING, "Problem browsing file system", e);
344 			} catch (IOException e) {
345 				logger.log(Level.WARNING, "Problem browsing file system", e);
346 			}
347 
348 			return ftpFileFile;
349 		} catch (FTPBrowseException e) {
350 			logger.log(Level.WARNING, "FTBEx", e);
351 		}
352 
353 		return null;
354 	}
355 
356 	/**
357 	 * @see javax.swing.filechooser.FileSystemView#getRoots()
358 	 */
359 	public File [] getRoots () {
360 
361 		FTPFileFile [] ftpFiles = null;
362 		try {
363 			checkConnection();
364 
365 			FTPFile ftpFile = new FTPFile();
366 			ftpFile.setName(FILE_SYSTEM_ROOT_NAME);
367 			ftpFile.setType(FTPFile.DIRECTORY_TYPE);
368 			FTPFileFile ftpFileFile = new FTPFileFile(ftpFile, this);
369 			ftpFiles = new FTPFileFile [1];
370 			ftpFiles[0] = ftpFileFile;
371 		} catch (FTPBrowseException e) {
372 			logger.log(Level.WARNING, "Could not get root file", e);
373 		}
374 		return ftpFiles;
375 	}
376 
377 	/**
378 	 * @see javax.swing.filechooser.FileSystemView#createFileObject(java.io.File,
379 	 *      java.lang.String)
380 	 */
381 	public File createFileObject (File dir, String filename) {
382 
383 		// We should never get here. If we ever do, call the parent and hope for
384 		// the best!!!
385 		logger.fine("Calling Super with: " + dir.toString() + " " + filename);
386 		return super.createFileObject(dir, filename);
387 	}
388 
389 	/**
390 	 * @see javax.swing.filechooser.FileSystemView#createFileObject(java.lang.String)
391 	 */
392 	public File createFileObject (String path) {
393 
394 		// We should never get here. If we ever do, call the parent and hope for
395 		// the best!!!
396 		logger.fine("Calling Super with: " + path);
397 		return super.createFileObject(path);
398 	}
399 
400 	/**
401 	 * @see javax.swing.filechooser.FileSystemView#getChild(java.io.File,
402 	 *      java.lang.String)
403 	 */
404 	public File getChild (File parent, String fileName) {
405 
406 		if (parent instanceof FTPFileFile) {
407 			FTPFile parentDir = ((FTPFileFile) parent).getFtpFile();
408 			FTPFileFile returnedFile = null;
409 			try {
410 				checkConnection();
411 				ftpClient.changeWorkingDirectory(parentDir.getName());
412 				FTPListParseEngine ftpListParseEngine = ftpClient
413 						.initiateListParsing();
414 				FTPFile [] returnedFiles = ftpListParseEngine.getFiles();
415 				// Check if a path name is present.
416 				if (fileName.indexOf(FILE_SEPERATOR) > -1) {
417 					fileName = fileName.substring(fileName
418 							.lastIndexOf(FILE_SEPERATOR) + 1);
419 				}
420 				for (int i = 0; i < returnedFiles.length; i++ ) {
421 					if (returnedFiles[i].getName().equals(fileName)) {
422 						returnedFiles[i].setName(parentDir.getName()
423 								+ FILE_SEPERATOR + returnedFiles[i].getName());
424 						returnedFile = new FTPFileFile(returnedFiles[i], this);
425 					}
426 				}
427 			} catch (FTPBrowseException e) {
428 				logger.log(Level.WARNING, "Problem browsing file system", e);
429 			} catch (IOException e) {
430 				logger.log(Level.WARNING, "Problem browsing file system", e);
431 			}
432 			return returnedFile;
433 		} else {
434 			// Should never get here!!!
435 			logger.fine("Calling Super with: " + parent.toString() + " " + fileName);
436 			return super.getChild(parent, fileName);
437 		}
438 	}
439 
440 	/**
441 	 * @see javax.swing.filechooser.FileSystemView#getFiles(java.io.File,
442 	 *      boolean)
443 	 */
444 	public synchronized File [] getFiles (File dir, boolean useFileHiding) {
445 
446 		if (dir instanceof FTPFileFile && dir.isDirectory()) {
447 			FTPFile ftpFile = ((FTPFileFile) dir).getFtpFile();
448 			String name = ftpFile.getName();
449 			try {
450 				checkConnection();
451 				String pwd = ftpClient.printWorkingDirectory();
452 				if (null == pwd || !pwd.equals(name)) {
453 					ftpClient.changeWorkingDirectory(name);
454 				}
455 				pwd = ftpClient.printWorkingDirectory();
456 				FTPListParseEngine ftpListParseEngine = ftpClient
457 						.initiateListParsing();
458 				FTPFile [] files = ftpListParseEngine.getFiles();
459 				FTPFileFile [] ftpFiles = new FTPFileFile [files.length];
460 				for (int i = 0; i < files.length; i++ ) {
461 					files[i].setName(pwd + FILE_SEPERATOR + files[i].getName());
462 					ftpFiles[i] = new FTPFileFile(files[i], this);
463 				}
464 				return ftpFiles;
465 			} catch (FTPBrowseException e) {
466 				logger.log(Level.WARNING, "Could not connect to host", e);
467 				return new FTPFileFile [0];
468 			} catch (IOException e) {
469 				logger.log(Level.WARNING, "Could not operate on host", e);
470 				return new FTPFileFile [0];
471 			}
472 		}
473 		// Should never get here
474 		logger.fine("Calling Super with: " + dir.toString() + " " + String.valueOf(useFileHiding));
475 		return super.getFiles(dir, useFileHiding);
476 	}
477 
478 	/**
479 	 * @see javax.swing.filechooser.FileSystemView#getParentDirectory(java.io.File)
480 	 */
481 	public File getParentDirectory (File dir) {
482 
483 		if (dir instanceof FTPFileFile) {
484 
485 			FTPFile ftpFile = ((FTPFileFile) dir).getFtpFile();
486 			String name = ftpFile.getName();
487 			if (name.equals(FILE_SYSTEM_ROOT_NAME)) {
488 				return null;
489 			}
490 
491 			String parent = name.substring(0, name.lastIndexOf(FILE_SEPERATOR));
492 
493 			// Parent is the root
494 			if (parent.equals(FILE_SYSTEM_ROOT_NAME)) {
495 				return getRoots()[0];
496 			}
497 
498 			// Parent of the parent to list the parent
499 			String pparent = parent.substring(0, parent
500 					.lastIndexOf(FILE_SEPERATOR));
501 			if (pparent.length() == 0) {
502 				pparent = FILE_SYSTEM_ROOT_NAME;
503 			}
504 
505 			FTPFileFile parentFile = null;
506 
507 			try {
508 				checkConnection();
509 				ftpClient.changeWorkingDirectory(pparent);
510 				FTPListParseEngine ftpListParseEngine = ftpClient
511 						.initiateListParsing();
512 				FTPFile [] returnedFiles = ftpListParseEngine.getFiles();
513 				String parentName = parent.substring(parent
514 						.lastIndexOf(FILE_SEPERATOR) + 1);
515 				for (int i = 0; i < returnedFiles.length; i++ ) {
516 					if (returnedFiles[i].getName().equals(parentName)) {
517 						returnedFiles[i].setName(pparent + FILE_SEPERATOR
518 								+ returnedFiles[i].getName());
519 						parentFile = new FTPFileFile(returnedFiles[i], this);
520 					}
521 				}
522 			} catch (FTPBrowseException e) {
523 				logger.log(Level.WARNING, "Problem browsing file system", e);
524 			} catch (IOException e) {
525 				logger.log(Level.WARNING, "Problem browsing file system", e);
526 			}
527 
528 			if (null == parentFile) {
529 				parentFile = (FTPFileFile) getRoots()[0];
530 			}
531 
532 			return parentFile;
533 		}
534 		
535 		logger.fine("Calling Super with: " + dir.toString());
536 		return super.getParentDirectory(dir);
537 	}
538 
539 	/**
540 	 * @see javax.swing.filechooser.FileSystemView#getSystemDisplayName(java.io.File)
541 	 */
542 	public String getSystemDisplayName (File f) {
543 
544 		if (f instanceof FTPFileFile) {
545 			FTPFile ftpFile = ((FTPFileFile) f).getFtpFile();
546 			String name = ftpFile.getName();
547 			if (FILE_SYSTEM_ROOT_NAME.equals(name)) {
548 				return url.getHost();
549 			} else {
550 				return f.getName();
551 			}
552 		} else {
553 			logger.fine("Calling Super with: " + f.getPath());
554 			return super.getSystemDisplayName(f);
555 		}
556 	}
557 
558 	/**
559 	 * Always returns null. The super class uses this to return special folder
560 	 * names such as 'Desktop' on Windows.
561 	 * 
562 	 * @see javax.swing.filechooser.FileSystemView#getSystemTypeDescription(java.io.File)
563 	 */
564 	public String getSystemTypeDescription (File f) {
565 
566 		return null;
567 	}
568 
569 	/**
570 	 * @see javax.swing.filechooser.FileSystemView#isComputerNode(java.io.File)
571 	 */
572 	public boolean isComputerNode (File dir) {
573 
574 		if (dir instanceof FTPFileFile) {
575 			FTPFile ftpFile = ((FTPFileFile) dir).getFtpFile();
576 			String name = ftpFile.getName();
577 			if (FILE_SYSTEM_ROOT_NAME.equals(name)) {
578 				return true;
579 			} else {
580 				return false;
581 			}
582 
583 		} else {
584 			return super.isComputerNode(dir);
585 		}
586 	}
587 
588 	/**
589 	 * Returns false, drives not supported on remote systems
590 	 * 
591 	 * @see javax.swing.filechooser.FileSystemView#isDrive(java.io.File)
592 	 */
593 	public boolean isDrive (File dir) {
594 
595 		return false;
596 	}
597 
598 	/**
599 	 * Determines if the file is a real file or a link to another file.
600 	 * 
601 	 * @see javax.swing.filechooser.FileSystemView#isFileSystem(java.io.File)
602 	 * @return <code>true</code> if it is an absolute file or
603 	 *         <code>false</code>
604 	 */
605 	public boolean isFileSystem (File f) {
606 
607 		if (f instanceof FTPFileFile) {
608 			FTPFile ftpFile = ((FTPFileFile) f).getFtpFile();
609 			return !ftpFile.isSymbolicLink();
610 		}
611 		logger.fine("Calling Super for: " + f.toString());
612 		return super.isFileSystem(f);
613 	}
614 
615 	/**
616 	 * @see javax.swing.filechooser.FileSystemView#isFileSystemRoot(java.io.File)
617 	 */
618 	public boolean isFileSystemRoot (File dir) {
619 
620 		if (dir instanceof FTPFileFile) {
621 			FTPFile ftpFile = ((FTPFileFile) dir).getFtpFile();
622 			String name = ftpFile.getName();
623 			if (FILE_SYSTEM_ROOT_NAME.equals(name)) {
624 				return true;
625 			} else {
626 				return false;
627 			}
628 		}
629 		logger.fine("Calling Super for: " + dir.toString());
630 		return super.isFileSystemRoot(dir);
631 	}
632 
633 	/**
634 	 * Returns false. No floppy drives are viewable.
635 	 * 
636 	 * @see javax.swing.filechooser.FileSystemView#isFloppyDrive(java.io.File)
637 	 */
638 	public boolean isFloppyDrive (File dir) {
639 
640 		return false;
641 	}
642 
643 	/**
644 	 * Hidden files are not supported now. Maybe later!
645 	 * 
646 	 * @see javax.swing.filechooser.FileSystemView#isHiddenFile(java.io.File)
647 	 */
648 	public boolean isHiddenFile (File f) {
649 
650 		return false;
651 	}
652 
653 	/**
654 	 * @see javax.swing.filechooser.FileSystemView#isParent(java.io.File,
655 	 *      java.io.File)
656 	 */
657 	public boolean isParent (File folder, File file) {
658 
659 		if (folder instanceof FTPFileFile && file instanceof FTPFileFile) {
660 			// If file is a FTPFileFile, you will always get back an FTPFileFile
661 			FTPFileFile calculatedParent = (FTPFileFile) getParentDirectory(file);
662 			String parentPath = ((FTPFileFile) folder).getFtpFile().getName();
663 			if (parentPath.equals(calculatedParent.getFtpFile().getName())) {
664 				return true;
665 			} else {
666 				return false;
667 			}
668 		}
669 		logger.fine("Calling Super for: " + folder.toString() + " " + file.toString());
670 		return super.isParent(folder, file);
671 	}
672 
673 	/**
674 	 * @see javax.swing.filechooser.FileSystemView#isRoot(java.io.File)
675 	 */
676 	public boolean isRoot (File f) {
677 
678 		if (f instanceof FTPFileFile) {
679 			FTPFile ftpFile = ((FTPFileFile) f).getFtpFile();
680 			String name = ftpFile.getName();
681 			if (FILE_SYSTEM_ROOT_NAME.equals(name)) {
682 				return true;
683 			} else {
684 				return false;
685 			}
686 		}
687 		logger.fine("Calling super for: " + f.toString());
688 		return super.isRoot(f);
689 	}
690 
691 	/**
692 	 * @see javax.swing.filechooser.FileSystemView#isTraversable(java.io.File)
693 	 */
694 	public Boolean isTraversable (File f) {
695 
696 		if (f instanceof FTPFileFile) {
697 			FTPFile ftpFile = ((FTPFileFile) f).getFtpFile();
698 			return new Boolean(ftpFile.isDirectory());
699 		}
700 		logger.fine("Calling super for: " + f.toString());
701 		return super.isTraversable(f);
702 	}
703 }