1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
105
106 File parent = file.getParentFile();
107
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 }