Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2012 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
* |
40 |
* Portions Copyrighted 2012 Sun Microsystems, Inc. |
41 |
*/ |
42 |
package org.netbeans.modules.openide.filesystems; |
43 |
|
44 |
import java.io.File; |
45 |
import java.util.ArrayList; |
46 |
import java.util.Collections; |
47 |
import java.util.Comparator; |
48 |
import java.util.HashMap; |
49 |
import java.util.HashSet; |
50 |
import java.util.LinkedList; |
51 |
import java.util.List; |
52 |
import java.util.Map; |
53 |
import java.util.Set; |
54 |
import java.util.logging.Level; |
55 |
import java.util.logging.Logger; |
56 |
import java.util.regex.Matcher; |
57 |
import java.util.regex.Pattern; |
58 |
import javax.swing.filechooser.FileFilter; |
59 |
import org.openide.filesystems.FileObject; |
60 |
import org.openide.filesystems.FileUtil; |
61 |
|
62 |
/** |
63 |
* Support methods for creation of registered {@link FileFilter file filters}. |
64 |
*/ |
65 |
public final class FileFilterSupport { |
66 |
|
67 |
/** |
68 |
* The logger. |
69 |
*/ |
70 |
private static final Logger LOG = Logger.getLogger( |
71 |
FileFilterSupport.class.getName()); |
72 |
|
73 |
/** |
74 |
* Hide the default constructor. |
75 |
*/ |
76 |
private FileFilterSupport() { |
77 |
} |
78 |
|
79 |
/** |
80 |
* Construct description for {@link FileFilter} that accepts files with |
81 |
* specified extension. |
82 |
* |
83 |
* @param displayName Human readable display name (e.g. "HTML files") |
84 |
* @param elements List of accepted filter elements. |
85 |
* |
86 |
* @return Display name (description) for the filter. |
87 |
*/ |
88 |
private static String constructFilterDisplayName(String displayName, |
89 |
List<FilterElement> elements) { |
90 |
StringBuilder sb = new StringBuilder(displayName); |
91 |
boolean first = true; |
92 |
sb.append(" ["); //NOI18N |
93 |
for (FilterElement el : elements) { |
94 |
if (first) { |
95 |
first = false; |
96 |
} else { |
97 |
sb.append(", "); //NOI18N |
98 |
} |
99 |
sb.append(el.getName()); |
100 |
} |
101 |
sb.append("]"); //NOI18N |
102 |
return sb.toString(); |
103 |
} |
104 |
|
105 |
/** |
106 |
* Check whether passed file is accepted by filter for specified list of |
107 |
* extensions. |
108 |
* |
109 |
* @param file File to be accepted or rejected. |
110 |
* @param elements List of accepted filter elements. |
111 |
* |
112 |
* @return True if the file is accepted, false if it is rejected. |
113 |
* |
114 |
* @see FileFilterSupport |
115 |
*/ |
116 |
private static boolean accept(File file, List<FilterElement> elements) { |
117 |
if (file != null) { |
118 |
if (file.isDirectory()) { |
119 |
return true; |
120 |
} |
121 |
for (FilterElement elm : elements) { |
122 |
if (elm.accept(file)) { |
123 |
return true; |
124 |
} |
125 |
} |
126 |
} |
127 |
return false; |
128 |
} |
129 |
|
130 |
public static List<FileFilter> findRegisteredFileFilters() { |
131 |
List<FileFilter> filters = new LinkedList<FileFilter>(); |
132 |
FileObject root = FileUtil.getConfigFile( |
133 |
"Services/MIMEResolver"); //NOI18N |
134 |
Map<String, Set<FileObject>> filterNameToResolversMap = |
135 |
new HashMap<String, Set<FileObject>>(); |
136 |
for (FileObject child : root.getChildren()) { |
137 |
if (child.isFolder()) { |
138 |
continue; |
139 |
} |
140 |
int i = 0; |
141 |
String f; |
142 |
while ((f = (String) child.getAttribute("fileChooser." + i))//NOI18N |
143 |
!= null) { |
144 |
Set<FileObject> set = filterNameToResolversMap.get(f); |
145 |
if (set == null) { |
146 |
set = new HashSet<FileObject>(); |
147 |
filterNameToResolversMap.put(f, set); |
148 |
} |
149 |
set.add(child); |
150 |
i++; |
151 |
} |
152 |
} |
153 |
for (Map.Entry<String, Set<FileObject>> e : |
154 |
filterNameToResolversMap.entrySet()) { |
155 |
filters.add(createFilter(e.getKey(), e.getValue())); |
156 |
} |
157 |
return sortFiltersByDescription(filters); |
158 |
} |
159 |
|
160 |
private static FileFilter createFilter(final String name, |
161 |
final Set<FileObject> resolvers) { |
162 |
ArrayList<FilterElement> elems = new ArrayList<FilterElement>(3); |
163 |
String lastAtt; |
164 |
for (FileObject fo : resolvers) { |
165 |
int i = 0; |
166 |
while ((lastAtt = (String) fo.getAttribute( |
167 |
"ext." + i)) != null) { //NOI18N |
168 |
addExtensionToList(elems, lastAtt); |
169 |
i++; |
170 |
} |
171 |
int n = 0; |
172 |
while ((lastAtt = (String) fo.getAttribute("fileName." //NOI18N |
173 |
+ (n++))) != null) { |
174 |
addNameToList(elems, lastAtt); |
175 |
} |
176 |
String type; |
177 |
if ((type = (String) fo.getAttribute("mimeType")) != null) {//NOI18N |
178 |
addMimeTypeExts(elems, type); |
179 |
} |
180 |
int t = 0; |
181 |
while ((type = (String) fo.getAttribute( |
182 |
"mimeType." + (t++))) != null) { //NOI18N |
183 |
addMimeTypeExts(elems, type); |
184 |
} |
185 |
} |
186 |
sortFilterElements(elems); |
187 |
return new FileFilterImpl(name, elems); |
188 |
} |
189 |
|
190 |
/** |
191 |
* Add all extensions assigned to a MIME Type to the extension list. |
192 |
*/ |
193 |
private static void addMimeTypeExts(List<FilterElement> exts, String type) { |
194 |
addAllExtensionsToList(exts, FileUtil.getMIMETypeExtensions(type)); |
195 |
} |
196 |
|
197 |
/** |
198 |
* Add new items to list of extensions, prevent duplicates. |
199 |
* |
200 |
* @param list List of extensions to alter. |
201 |
* @param toAdd List of extensions (without starting dot) to add. |
202 |
*/ |
203 |
private static void addAllExtensionsToList(List<FilterElement> list, |
204 |
List<String> toAdd) { |
205 |
for (String s : toAdd) { |
206 |
addExtensionToList(list, s); |
207 |
} |
208 |
} |
209 |
|
210 |
/** |
211 |
* Add new item to list of extensions, prevent duplacates. |
212 |
* |
213 |
* @param list List of extensions to alter. |
214 |
* @param s Extensions without starting dot. |
215 |
*/ |
216 |
private static void addExtensionToList(List<FilterElement> list, |
217 |
String ext) { |
218 |
addFilterElementToList(list, FilterElement.createForExtension(ext)); |
219 |
} |
220 |
|
221 |
private static void addNameToList(List<FilterElement> list, String name) { |
222 |
Pattern p = Pattern.compile( |
223 |
"\\[([^,]+), (true|false), (true|false)\\](\\S*)"); //NOI18N |
224 |
Matcher m = p.matcher(name); |
225 |
if (m.find()) { |
226 |
String fileName = m.group(1); |
227 |
boolean substring = m.group(2).equals("true"); //NOI18N |
228 |
boolean ignoreCase = m.group(3).equals("true"); //NOI18N |
229 |
String extension = m.group(4); |
230 |
addFilterElementToList(list, FilterElement.createForFileName( |
231 |
fileName, extension, substring, ignoreCase)); |
232 |
} else { |
233 |
LOG.log(Level.INFO, "Incorrect name pattern {0}", name); //NOI18N |
234 |
} |
235 |
} |
236 |
|
237 |
private static void addFilterElementToList(List<FilterElement> list, |
238 |
FilterElement newItem) { |
239 |
|
240 |
for (int i = 0; i < list.size(); i++) { |
241 |
FilterElement el = list.get(i); |
242 |
FilterElement.ComparisonResult result = newItem.compare(el); |
243 |
switch (result) { |
244 |
case DIFFERENT: |
245 |
continue; |
246 |
case THE_SAME: |
247 |
case WORSE: |
248 |
return; |
249 |
case BETTER: |
250 |
list.set(i, newItem); |
251 |
return; |
252 |
} |
253 |
} |
254 |
list.add(newItem); |
255 |
} |
256 |
|
257 |
private static List<FileFilter> sortFiltersByDescription( |
258 |
List<FileFilter> list) { |
259 |
|
260 |
Collections.sort(list, new Comparator<FileFilter>() { |
261 |
@Override |
262 |
public int compare(FileFilter o1, FileFilter o2) { |
263 |
return o1.getDescription().compareTo(o2.getDescription()); |
264 |
} |
265 |
}); |
266 |
return list; |
267 |
} |
268 |
|
269 |
private static List<FilterElement> sortFilterElements( |
270 |
List<FilterElement> elements) { |
271 |
Collections.sort(elements, new Comparator<FilterElement>() { |
272 |
@Override |
273 |
public int compare(FilterElement o1, FilterElement o2) { |
274 |
return o1.getName().compareTo(o2.getName()); |
275 |
} |
276 |
}); |
277 |
return elements; |
278 |
} |
279 |
|
280 |
private static class FileFilterImpl extends FileFilter { |
281 |
|
282 |
private final String name; |
283 |
List<FilterElement> filterElements; |
284 |
|
285 |
public FileFilterImpl(String name, List<FilterElement> elements) { |
286 |
this.name = name; |
287 |
this.filterElements = elements; |
288 |
} |
289 |
|
290 |
@Override |
291 |
public boolean accept(File pathname) { |
292 |
return FileFilterSupport.accept(pathname, filterElements); |
293 |
} |
294 |
|
295 |
@Override |
296 |
public String getDescription() { |
297 |
return FileFilterSupport.constructFilterDisplayName( |
298 |
name, filterElements); |
299 |
} |
300 |
} |
301 |
|
302 |
/** |
303 |
* Element of File Filter. One accepted extension or file name pattern. |
304 |
*/ |
305 |
private static abstract class FilterElement { |
306 |
|
307 |
public abstract String getName(); |
308 |
|
309 |
public abstract boolean accept(File f); |
310 |
|
311 |
/** |
312 |
* Compare two filter elements. Correct implementation of this method |
313 |
* prevents adding duplicite elements to the filter. |
314 |
*/ |
315 |
public abstract ComparisonResult compare(FilterElement e); |
316 |
|
317 |
public static FilterElement createForExtension(String ext) { |
318 |
return new ExtensionBasedFilterElement(ext); |
319 |
} |
320 |
|
321 |
public static FilterElement createForFileName(String name, |
322 |
String extension, boolean substring, boolean ignoreCase) { |
323 |
return new NameBasedFilterElement(name, extension, |
324 |
substring, ignoreCase); |
325 |
} |
326 |
|
327 |
public static enum ComparisonResult { |
328 |
|
329 |
THE_SAME, BETTER, WORSE, DIFFERENT |
330 |
} |
331 |
|
332 |
private static class ExtensionBasedFilterElement extends FilterElement { |
333 |
|
334 |
private final String extension; |
335 |
|
336 |
public ExtensionBasedFilterElement(String extension) { |
337 |
if (extension != null) { |
338 |
this.extension = extension; |
339 |
} else { |
340 |
throw new NullPointerException(); |
341 |
} |
342 |
} |
343 |
|
344 |
@Override |
345 |
public String getName() { |
346 |
return "." + extension; //NOI18N |
347 |
} |
348 |
|
349 |
@Override |
350 |
public boolean accept(File f) { |
351 |
return f.getName().toLowerCase().endsWith( |
352 |
"." + extension.toLowerCase()); //NOI18N |
353 |
} |
354 |
|
355 |
@Override |
356 |
public ComparisonResult compare(FilterElement e) { |
357 |
if (!(e instanceof ExtensionBasedFilterElement)) { |
358 |
return ComparisonResult.DIFFERENT; |
359 |
} |
360 |
ExtensionBasedFilterElement x = (ExtensionBasedFilterElement) e; |
361 |
if (x == null) { |
362 |
throw new NullPointerException(); |
363 |
} |
364 |
if (this.extension.equals(x.extension)) { |
365 |
return ComparisonResult.THE_SAME; |
366 |
} else if (this.extension.equalsIgnoreCase(x.extension) |
367 |
&& this.extension.length() > 1) { |
368 |
if (Character.isUpperCase(x.extension.charAt(0))) { |
369 |
return ComparisonResult.BETTER; //this better, x worse |
370 |
} else { |
371 |
return ComparisonResult.WORSE; // this worse, x better |
372 |
} |
373 |
} else { |
374 |
return ComparisonResult.DIFFERENT; |
375 |
} |
376 |
} |
377 |
} |
378 |
|
379 |
private static class NameBasedFilterElement extends FilterElement { |
380 |
|
381 |
String name; |
382 |
String ext; |
383 |
boolean substring; |
384 |
boolean ignoreCase; |
385 |
Pattern p; |
386 |
|
387 |
public NameBasedFilterElement(String name, String ext, |
388 |
boolean substring, boolean ignoreCase) { |
389 |
this.name = name; |
390 |
this.ext = ext; |
391 |
this.substring = substring; |
392 |
this.ignoreCase = ignoreCase; |
393 |
StringBuilder sb = new StringBuilder(); |
394 |
if (ignoreCase) { |
395 |
sb.append("(?i)"); //NOI18N |
396 |
} |
397 |
if (substring) { |
398 |
sb.append(".*"); //NOI18N |
399 |
} |
400 |
sb.append(name); //NOI18N |
401 |
if (substring) { |
402 |
sb.append(".*"); //NOI18N |
403 |
} |
404 |
if (!ext.isEmpty()) { |
405 |
sb.append("\\."); //NOI18N |
406 |
sb.append(ext); |
407 |
} |
408 |
p = Pattern.compile(sb.toString()); |
409 |
} |
410 |
|
411 |
@Override |
412 |
public String getName() { |
413 |
return name + (ext.isEmpty() ? "" : "." + ext); //NOI18N |
414 |
} |
415 |
|
416 |
@Override |
417 |
public boolean accept(File f) { |
418 |
return p.matcher(f.getName()).matches(); |
419 |
} |
420 |
|
421 |
@Override |
422 |
public ComparisonResult compare(FilterElement e) { |
423 |
if (e == null) { |
424 |
throw new NullPointerException(); |
425 |
} else if (!(e instanceof NameBasedFilterElement)) { |
426 |
return ComparisonResult.DIFFERENT; |
427 |
} |
428 |
NameBasedFilterElement x = (NameBasedFilterElement) e; |
429 |
if (this.name.equals(x.name) && this.ext.equals(x.ext)) { |
430 |
if (this.substring == x.substring |
431 |
&& this.ignoreCase == x.ignoreCase) { |
432 |
return ComparisonResult.THE_SAME; |
433 |
} else { |
434 |
return compareFlags(x); |
435 |
} |
436 |
} else if (this.ext.equalsIgnoreCase(x.ext) |
437 |
&& this.name.equalsIgnoreCase(x.name) |
438 |
&& (this.ignoreCase || x.ignoreCase)) { |
439 |
if (this.substring == x.substring |
440 |
&& this.ignoreCase == x.ignoreCase) { |
441 |
if (Character.isLowerCase(this.name.charAt(0))) { |
442 |
return ComparisonResult.BETTER; |
443 |
} else { |
444 |
return ComparisonResult.WORSE; |
445 |
} |
446 |
} else { |
447 |
return compareFlags(x); |
448 |
} |
449 |
} else { |
450 |
return ComparisonResult.DIFFERENT; |
451 |
} |
452 |
} |
453 |
|
454 |
private ComparisonResult compareFlags(NameBasedFilterElement x) { |
455 |
if (this.substring == x.substring |
456 |
&& this.ignoreCase) { |
457 |
return ComparisonResult.BETTER; |
458 |
} else if (this.ignoreCase == x.ignoreCase |
459 |
&& this.substring) { |
460 |
return ComparisonResult.BETTER; |
461 |
} else if (this.substring != x.substring |
462 |
&& this.ignoreCase != x.ignoreCase) { |
463 |
return ComparisonResult.DIFFERENT; |
464 |
} else { |
465 |
return ComparisonResult.WORSE; |
466 |
} |
467 |
} |
468 |
} |
469 |
} |
470 |
} |