View Javadoc

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