View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package org.xnap.gui.component;
20  
21  import java.io.File;
22  import java.io.FileFilter;
23  
24  import javax.swing.DefaultComboBoxModel;
25  
26  import org.apache.log4j.Logger;
27  import org.xnap.util.FileHelper;
28  
29  /***
30   * This class provides completion for path prefixes on the local file system.
31   */
32  public class FileCompletionModel extends DefaultComboBoxModel 
33  	implements CompletionModel
34  {
35  	//--- Constant(s) ---
36  
37  	//--- Data field(s) ---
38  
39  	private boolean completeOnlyDirectories = false;
40  
41  	private FileFilter dirFilter = new DirectoryFileFilter();
42  
43      private static Logger logger = Logger.getLogger(FileCompletionModel.class);
44  	
45  	//--- Constructor(s) ---
46  
47  	/***
48  	 * Constructs a new file completion model.
49  	 *
50  	 * @param completeOnlyDirectories if true only subdirectories are
51  	 * suggested as possible completions, otherwise files are taken into
52  	 * account too 
53  	 */
54      public FileCompletionModel(boolean completeOnlyDirectories)
55  	{
56  		this.completeOnlyDirectories = completeOnlyDirectories;
57  	}
58  
59  	/***
60  	 * Constructs a new file completion model.
61  	 *
62  	 * The default setting is to complete subdirectories and files.
63  	 */
64  	public FileCompletionModel()
65  	{
66  		this(false);
67  	}
68  
69      //--- Method(s) ---
70  
71  	/***
72  	 * Returns true if there are files in the local file system having the
73  	 * given prefix.
74  	 *
75  	 * First we test if the given prefix denotes a file which exists and is a
76  	 * directory. If that is the case all the files contained in that
77  	 * directory are added to the model.
78  	 * 
79  	 * Regardless of the above results the parent directory if there is one is
80  	 * searched for files matching the prefix. Thus we get all possible
81  	 * completions that make sense.
82  	 */
83  	public boolean complete(String prefix)
84  	{
85  		removeAllElements();
86  
87  		File file = new File(prefix);
88  		if (file.exists() && file.isDirectory()) {
89  			File[] files = file.listFiles(dirFilter);
90  			for (int i = 0; files != null && i < files.length; i++) {
91  				addElement(files[i].getAbsolutePath());
92  			}
93  		}
94  		/* look for files in the parent directory having file.getName() as
95             prefix.  */
96  		/* else */ if (file.getParentFile() != null) {
97  			File[] files = file.getParentFile().listFiles
98  				(new PrefixFileFilter(file.getName()));
99  			for (int i = 0; files != null && i < files.length; i++) {
100 				addElement(files[i].getAbsolutePath());
101 			}
102 		}
103 		return getSize() > 0;
104 	}
105 
106 	/***
107 	 * This method's behaviour is a matter of taste.
108 	 *
109 	 * It first checks if the given prefix is an existing directory. If that
110 	 * is the case the commmon unique prefix of all files found in the
111 	 * directory is returned.
112 	 *
113 	 * Only if the above fails the parent directory is searched for the common
114 	 * unique prefix.
115 	 */
116 	public String completeUniquePrefix(String prefix)
117 	{
118 		File file = new File(prefix);
119 		if (file.exists() && file.isDirectory()) {
120 			File[] files = file.listFiles(dirFilter);
121 			StringBuffer sb = new StringBuffer
122 				(FileHelper.appendSeparator(file.getAbsolutePath()));
123 			for (int i = 0; matches(files, i); i++) {
124 				sb.append(files[0].getName().charAt(i));
125 			}
126 			return sb.toString();
127 		}
128 		else if (file.getParentFile() != null) {
129 			File[] files = file.getParentFile().listFiles
130 				(new PrefixFileFilter(file.getName()));
131 			StringBuffer sb = new StringBuffer
132 				(FileHelper.appendSeparator
133 				 (file.getParentFile().getAbsolutePath()));
134 			for (int i = 0; matches(files, i); i++) {
135 				sb.append(files[0].getName().charAt(i));
136 			}
137 			return sb.toString();
138 		}
139 		return prefix;
140 	}
141 
142 	/***
143 	 * Returns true if all files in the given array have the same character at
144 	 * position <code>index</code>.
145 	 *
146 	 * @param files the array of files, can be null or empty
147 	 * @param index the position in the filenames that is tested for equality
148 	 * @return false if the array is null or empty, true if all filenames of
149 	 * the files in the array have the same character at position
150 	 * <code>index</code> 
151 	 */
152 	private boolean matches(File[] files, int index)
153 	{
154 		if (files == null || files.length == 0) {
155 			return false;
156 		}
157 		if (files.length == 1) {
158 			return index < files[0].getName().length();
159 		}
160 		for (int i = 0; i < files.length - 1; i++) {
161 			if (files[i].getName().length() == index 
162 				|| files[i+1].getName().length() == index) {
163 				return false;
164 			}
165 			else if (files[i].getName().charAt(index) 
166 					 != files[i+1].getName().charAt(index)) {
167 				return false;
168 			}
169 		}
170 		return true;
171 	}
172 
173 	/***
174 	 * Does nothing, since we don't do any data keeping of our own.
175 	 */
176 	public void insert(Object object)
177 	{
178 
179 	}
180 
181 	/***
182 	 * Does nothing, since we don't do any data keeping of our own.
183 	 */
184 	public void remove(Object object)
185 	{
186 
187 	}
188 
189 	/***
190 	 * This class checks in its {@link DirectoryFileFilter#accept(File)}
191 	 * method if the given file is a directory in case the constructor {@link
192 	 * FileCompletionModel#FileCompletionModel(boolean)
193 	 * FileCompletionModel(true)} was used.
194 	 */
195 	private class DirectoryFileFilter implements FileFilter
196 	{
197 		public boolean accept(File pathname)
198 		{
199 			return completeOnlyDirectories ? pathname.isDirectory() : true;
200 		}
201 	}
202 
203 	private class PrefixFileFilter extends DirectoryFileFilter
204 	{
205 		private String prefix;
206 
207 		public PrefixFileFilter(String prefix)
208 		{
209 			this.prefix = prefix;
210 		}
211 
212 		public boolean accept(File pathname)
213 		{
214 			return super.accept(pathname)
215 				&& pathname.getName().startsWith(prefix);
216 		} 
217 	}
218 }