001/**
002 * Please feel free to use any fragment of the code in this file that you need in your own
003 * work. As far as I am concerned, it's in the public domain. No permission is necessary
004 * or required. Credit is always appreciated if you use a large chunk or base a
005 * significant product on one of my examples, but that's not required either.
006 *
007 * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
008 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
009 * PURPOSE.
010 *
011 * --- Joseph A. Huwaldt
012 */
013package jahuwaldt.io;
014
015import java.io.File;
016import java.io.FileFilter;
017import java.io.FilenameFilter;
018import java.util.List;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.Iterator;
022import static java.util.Objects.isNull;
023import static java.util.Objects.nonNull;
024import static java.util.Objects.requireNonNull;
025import java.util.StringTokenizer;
026
027/**
028 * A convenience implementation of FilenameFilter and FileFilter that filters out all
029 * files except for those type extensions that it knows about.
030 *
031 * Extensions are of the type ".foo", which is typically found on Windows and Unix boxes,
032 * but not on the Macintosh prior to OS X. Case is ignored.
033 *
034 * <p> Modified by: Joseph A. Huwaldt </p>
035 *
036 * @author Joseph A. Huwaldt, Date: June 18, 2004
037 * @version September 16, 2016
038 */
039public class ExtFilenameFilter implements FilenameFilter, FileFilter {
040    //  This class now requires Java 1.7 or later!
041
042    private List<String> filters = null;
043
044    private HashMap<String, ExtFilenameFilter> nameFilters = null;
045
046    private String description = null;
047
048    private String fullDescription = null;
049
050    private boolean useExtensionsInDescription = true;
051
052    /**
053     * Creates a filename filter. If no filters are added, then all files are accepted.
054     *
055     * @see #addExtension(java.lang.String)
056     */
057    public ExtFilenameFilter() {
058        this((String)null, (String)null);
059    }
060
061    /**
062     * Creates a filename filter that accepts files with the given extension. Example: new
063     * ExtFilenameFilter("jpg");
064     *
065     * @param extension The file name extension to use for this filter. Null extensions
066     *                  are ignored.
067     * @see #addExtension(java.lang.String)
068     */
069    public ExtFilenameFilter(String extension) {
070        this(extension, null);
071    }
072
073    /**
074     * Creates a file filter that accepts the given file type. Example:
075     * <code>new ExtFilenameFilter("jpg", "JPEG Image Images");</code>
076     * <p>
077     * Note that the "." before the extension is not needed, but it is fine if it is
078     * there.</p>
079     *
080     * @param extension   The file name extension to use for this filter. Null extensions
081     *                    are ignored.
082     * @param description A description of the file type of this filter. Null is fine.
083     * @see #addExtension(java.lang.String)
084     */
085    public ExtFilenameFilter(String extension, String description) {
086        this(new String[]{extension}, description);
087    }
088
089    /**
090     * Creates a file filter from the given string array. Example:
091     * <code>new ExtFilenameFilter(String {"gif", "jpg"});</code>
092     * <p>
093     * Note that the "." before the extension is not needed, but it is fine if it is
094     * there.</p>
095     *
096     * @param filters An array of String objects where each entry is a file name extension
097     *                to be included in this filter. May not be null.
098     * @see #addExtension(java.lang.String)
099     */
100    public ExtFilenameFilter(String... filters) {
101        this(requireNonNull(filters), null);
102    }
103
104    /**
105     * Creates a file filter from the given string array and description. Example:
106     * <code>new ExtFilenameFilter(String {"gif", "jpg"}, "Gif and JPG Images");</code>
107     * <p>
108     * Note that the "." before the extension is not needed, but it is fine if it is
109     * there.</p>
110     *
111     * @param filters     An array of String objects where each entry is a file name
112     *                    extension to be included in this filter. Any null members of the
113     *                    array are ignored. The array itself may not be null.
114     * @param description The description of the extensions in this filter set. Null is
115     *                    fine.
116     * @see #addExtension(java.lang.String)
117     */
118    public ExtFilenameFilter(String[] filters, String description) {
119        requireNonNull(filters);
120        this.filters = new ArrayList<>();
121        for (String filter : filters) {
122            // add filters one by one
123            if (nonNull(filter))
124                addExtension(filter);
125        }
126        setDescription(description);
127        nameFilters = new HashMap<>(4);
128    }
129
130    /**
131     * Return true if this file should be shown , false if it should not.
132     *
133     * @param f The file that is to be tested for compatibility with this filter.
134     */
135    @Override
136    public boolean accept(File f) {
137        if (nonNull(f)) {
138            if (f.isDirectory())
139                return true;
140
141            String name = f.getName().toLowerCase();
142            if (nonNull(nameFilters.get(name)))
143                return true;
144
145            return extensionMatch(name);
146        }
147        return false;
148    }
149
150    /**
151     * Return true if this file should be included in a file list, false if it shouldn't.
152     *
153     * @param dir  The directory in which the file was found.
154     * @param name The name of the file.
155     */
156    @Override
157    public boolean accept(File dir, String name) {
158        if (nonNull(dir) && nonNull(name)) {
159
160            if (nonNull(nameFilters.get(name)))
161                return true;
162
163            return extensionMatch(name);
164        }
165        return false;
166    }
167
168    /**
169     * Tests to see if the specified name ends with any of the allow extensions.
170     *
171     * @param fileName The file name to test.
172     * @return <code>true</code> if the file name ends with any of the allowed extensions,
173     *         <code>false</code> if it does not. If there are no extensions defined, this
174     *         will always return true (all files will match).
175     */
176    private boolean extensionMatch(String fileName) {
177        if (filters.isEmpty())
178            return true;
179
180        for (String ext : filters) {
181            if (getExtension(fileName).equals(ext))
182                return true;
183        }
184
185        return false;
186    }
187
188    /**
189     * Return the extension portion of the file's name. The extension will always be
190     * returned in lower case and without the ".".
191     *
192     * @param name The file name for which the extension is to be returned. May not be
193     *             null.
194     * @return The extension portion of the file's name without the "." or "" if there is
195     *         no extension.
196     * @see #getExtension(java.io.File)
197     */
198    public static String getExtension(String name) {
199        int i = name.lastIndexOf('.');
200        if (i > 0 && i < name.length() - 1)
201            return name.substring(i + 1).toLowerCase().trim();
202        return "";
203    }
204
205    /**
206     * Return the extension portion of the file's name. The extension will always be
207     * returned in lower case and without the ".".
208     *
209     * @param f The file object for which the extension is to be returned. May not be
210     *          null.
211     * @return The extension portion of the file's name without the "." or "" if there is
212     *         no extension.
213     * @see #getExtension(java.lang.String)
214     */
215    public static String getExtension(File f) {
216        requireNonNull(f, "f == null");
217        return getExtension(f.getName());
218    }
219
220    /**
221     * Adds a file name "dot" extension to filter against.
222     * <p>
223     * For example: the following code will create a filter that filters out all files
224     * except those that end in ".jpg" and ".tif":
225     * <pre>
226     * ExtFilenameFilter filter = new ExtFilenameFilter(); filter.addExtension("jpg");
227     * filter.addExtension("tif");
228     * </pre></p>
229     * <p>
230     * Note that the "." before the extension is not needed, but it is fine if it is
231     * there.
232     * </p>
233     *
234     * @param extension The file name extension to be added to this filter. May not be
235     *                  null.
236     */
237    public final void addExtension(String extension) {
238        requireNonNull(extension, "extension == null");
239
240        if (!extension.equals("")) {
241            //  Make sure that the extension never starts with a ".".
242            while (extension.startsWith("."))
243                extension = extension.substring(1);
244
245            //  Store the extension in our database of extensions.
246            filters.add(extension.toLowerCase());
247        }
248
249        fullDescription = null;
250    }
251
252    /**
253     * Adds a full filename to filter against.
254     * <p>
255     * For example: the following code will create a filter that filters out all files
256     * except those that end in ".jpg" and ".tif" or have the name "foo.bar":
257     * <pre>
258     * ExtFilenameFilter filter = new ExtFilenameFilter();
259     * filter.addExtension("jpg");
260     * filter.addExtension("tif");
261     * filter.addFileName("foo.bar");
262     * </pre></p>
263     *
264     * @param fileName A full file name to add to this filter for filtering against. May
265     *                 not be null.
266     */
267    public void addFilename(String fileName) {
268        requireNonNull(fileName, "fileName == null");
269        if (!fileName.equals("")) {
270            nameFilters.put(fileName.toLowerCase(), this);
271        }
272    }
273
274    /**
275     * Adds a list of extensions parsed from a comma, space or tab delimited list.
276     * <p>
277     * For example, the following will create a filter that filters out all files except
278     * those that end in ".jpg" and ".png":
279     * <pre>
280     *    ExtFilenameFilter filter = new ExtFilenameFilter();
281     *    filter.addExtensions("jpg,png,gif");
282     * </pre></p>
283     *
284     * @param extensionList A delimited list of extensions to add to the filter. May not
285     *                      be null.
286     */
287    public void addExtensions(String extensionList) {
288        requireNonNull(extensionList, "extensionList == null");
289        StringTokenizer tokenizer = new StringTokenizer(extensionList, ", \t");
290        while (tokenizer.hasMoreTokens()) {
291            addExtension(tokenizer.nextToken());
292        }
293    }
294
295    /**
296     * Returns the human readable description of this filter. For example: "JPEG and GIF
297     * Image Files (*.jpg, *.gif)"
298     *
299     * @return The description of this filter.
300     * @see #setDescription(java.lang.String)
301     * @see #setExtensionListInDescription(boolean)
302     * @see #isExtensionListInDescription()
303     */
304    public String getDescription() {
305        if (isNull(fullDescription)) {
306            if (isNull(description) || isExtensionListInDescription()) {
307                if (nonNull(description))
308                    fullDescription = description;
309                fullDescription += " (";
310
311                // build the description from the extension list
312                Iterator<String> extensions = filters.iterator();
313                if (nonNull(extensions)) {
314                    fullDescription += extensions.next().substring(1);
315                    while (extensions.hasNext())
316                        fullDescription += ", " + extensions.next().substring(1);
317                }
318                fullDescription += ")";
319
320            } else {
321                fullDescription = description;
322            }
323        }
324        return fullDescription;
325    }
326
327    /**
328     * Sets the human readable description of this filter. For example:
329     * filter.setDescription("GIF and JPEG Images");
330     *
331     * @param description The description to be used for this filter. Null is fine.
332     * @see #setDescription(java.lang.String)
333     * @see #setExtensionListInDescription(boolean)
334     * @see #isExtensionListInDescription()
335     */
336    public final void setDescription(String description) {
337        this.description = description;
338        fullDescription = null;
339    }
340
341    /**
342     * Determines whether the extension list (.jpg,.gif, etc) should show up in the human
343     * readable description.
344     * <p>
345     * Only relevant if a description was provided in the constructor or using
346     * <code>setDescription()</code></p>
347     *
348     * @param useExtInDescription Set to true to use the extension list in the description
349     *                            of this filter.
350     * @see #getDescription()
351     * @see #setDescription(java.lang.String)
352     * @see #isExtensionListInDescription()
353     */
354    public void setExtensionListInDescription(boolean useExtInDescription) {
355        useExtensionsInDescription = useExtInDescription;
356        fullDescription = null;
357    }
358
359    /**
360     * Returns whether the extension list (.jpg,.gif, etc) should show up in the human
361     * readable description.
362     * </p>
363     * Only relevant if a description was provided in the constructor or using
364     * <code>setDescription()</code></p>
365     *
366     * @return true if the extension list is a part of the human readable description.
367     * @see #getDescription()
368     * @see #setDescription(java.lang.String)
369     * @see #setExtensionListInDescription(boolean)
370     */
371    public boolean isExtensionListInDescription() {
372        return useExtensionsInDescription;
373    }
374
375}