001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.io.filefilter;
018
019import java.io.File;
020import java.io.Serializable;
021import java.nio.file.FileVisitResult;
022import java.nio.file.Path;
023import java.nio.file.attribute.BasicFileAttributes;
024import java.util.List;
025import java.util.Objects;
026import java.util.stream.Stream;
027
028import org.apache.commons.io.IOCase;
029import org.apache.commons.io.file.PathUtils;
030
031/**
032 * Filters files based on the suffix (what the file name ends with).
033 * This is used in retrieving all the files of a particular type.
034 * <p>
035 * For example, to retrieve and print all {@code *.java} files
036 * in the current directory:
037 * </p>
038 * <h2>Using Classic IO</h2>
039 * <pre>
040 * File dir = FileUtils.current();
041 * String[] files = dir.list(new SuffixFileFilter(".java"));
042 * for (String file : files) {
043 *     System.out.println(file);
044 * }
045 * </pre>
046 *
047 * <h2>Using NIO</h2>
048 * <pre>
049 * final Path dir = PathUtils.current();
050 * final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(new SuffixFileFilter(".java"));
051 * //
052 * // Walk one dir
053 * Files.<b>walkFileTree</b>(dir, Collections.emptySet(), 1, visitor);
054 * System.out.println(visitor.getPathCounters());
055 * System.out.println(visitor.getFileList());
056 * //
057 * visitor.getPathCounters().reset();
058 * //
059 * // Walk dir tree
060 * Files.<b>walkFileTree</b>(dir, visitor);
061 * System.out.println(visitor.getPathCounters());
062 * System.out.println(visitor.getDirList());
063 * System.out.println(visitor.getFileList());
064 * </pre>
065 * <h2>Deprecating Serialization</h2>
066 * <p>
067 * <em>Serialization is deprecated and will be removed in 3.0.</em>
068 * </p>
069 *
070 * @since 1.0
071 * @see FileFilterUtils#suffixFileFilter(String)
072 * @see FileFilterUtils#suffixFileFilter(String, IOCase)
073 */
074public class SuffixFileFilter extends AbstractFileFilter implements Serializable {
075
076    private static final long serialVersionUID = -3389157631240246157L;
077
078    /** The file name suffixes to search for */
079    private final String[] suffixes;
080
081    /** Whether the comparison is case-sensitive. */
082    private final IOCase ioCase;
083
084    /**
085     * Constructs a new Suffix file filter for a list of suffixes.
086     *
087     * @param suffixes  the suffixes to allow, must not be null
088     * @throws IllegalArgumentException if the suffix list is null
089     * @throws ClassCastException if the list does not contain Strings
090     */
091    public SuffixFileFilter(final List<String> suffixes) {
092        this(suffixes, IOCase.SENSITIVE);
093    }
094
095    /**
096     * Constructs a new Suffix file filter for a list of suffixes
097     * specifying case-sensitivity.
098     *
099     * @param suffixes  the suffixes to allow, must not be null
100     * @param ioCase  how to handle case sensitivity, null means case-sensitive
101     * @throws IllegalArgumentException if the suffix list is null
102     * @throws ClassCastException if the list does not contain Strings
103     * @since 1.4
104     */
105    public SuffixFileFilter(final List<String> suffixes, final IOCase ioCase) {
106        Objects.requireNonNull(suffixes, "suffixes");
107        this.suffixes = suffixes.toArray(EMPTY_STRING_ARRAY);
108        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
109    }
110
111    /**
112     * Constructs a new Suffix file filter for a single extension.
113     *
114     * @param suffix  the suffix to allow, must not be null
115     * @throws IllegalArgumentException if the suffix is null
116     */
117    public SuffixFileFilter(final String suffix) {
118        this(suffix, IOCase.SENSITIVE);
119    }
120
121    /**
122     * Constructs a new Suffix file filter for an array of suffixes.
123     * <p>
124     * The array is not cloned, so could be changed after constructing the
125     * instance. This would be inadvisable however.
126     *
127     * @param suffixes  the suffixes to allow, must not be null
128     * @throws NullPointerException if the suffix array is null
129     */
130    public SuffixFileFilter(final String... suffixes) {
131        this(suffixes, IOCase.SENSITIVE);
132    }
133
134    /**
135     * Constructs a new Suffix file filter for a single extension
136     * specifying case-sensitivity.
137     *
138     * @param suffix  the suffix to allow, must not be null
139     * @param ioCase  how to handle case sensitivity, null means case-sensitive
140     * @throws NullPointerException if the suffix is null
141     * @since 1.4
142     */
143    public SuffixFileFilter(final String suffix, final IOCase ioCase) {
144        Objects.requireNonNull(suffix, "suffix");
145        this.suffixes = new String[] {suffix};
146        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
147    }
148
149    /**
150     * Constructs a new Suffix file filter for an array of suffixes
151     * specifying case-sensitivity.
152     *
153     * @param suffixes  the suffixes to allow, must not be null
154     * @param ioCase  how to handle case sensitivity, null means case-sensitive
155     * @throws NullPointerException if the suffix array is null
156     * @since 1.4
157     */
158    public SuffixFileFilter(final String[] suffixes, final IOCase ioCase) {
159        Objects.requireNonNull(suffixes, "suffixes");
160        this.suffixes = suffixes.clone();
161        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
162    }
163
164    /**
165     * Checks to see if the file name ends with the suffix.
166     *
167     * @param file  the File to check
168     * @return true if the file name ends with one of our suffixes
169     */
170    @Override
171    public boolean accept(final File file) {
172        return accept(file.getName());
173    }
174
175    /**
176     * Checks to see if the file name ends with the suffix.
177     *
178     * @param file  the File directory
179     * @param name  the file name
180     * @return true if the file name ends with one of our suffixes
181     */
182    @Override
183    public boolean accept(final File file, final String name) {
184        return accept(name);
185    }
186
187    /**
188     * Checks to see if the file name ends with the suffix.
189     * @param path  the File to check
190     *
191     * @return true if the file name ends with one of our suffixes
192     * @since 2.9.0
193     */
194    @Override
195    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
196        return toFileVisitResult(accept(PathUtils.getFileNameString(path)));
197    }
198
199    private boolean accept(final String name) {
200        return Stream.of(suffixes).anyMatch(suffix -> ioCase.checkEndsWith(name, suffix));
201    }
202
203    /**
204     * Provide a String representation of this file filter.
205     *
206     * @return a String representation
207     */
208    @Override
209    public String toString() {
210        final StringBuilder buffer = new StringBuilder();
211        buffer.append(super.toString());
212        buffer.append("(");
213        append(suffixes, buffer);
214        buffer.append(")");
215        return buffer.toString();
216    }
217
218}