001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.utils;
021
022import java.io.Closeable;
023import java.io.File;
024import java.io.IOException;
025import java.lang.reflect.Constructor;
026import java.lang.reflect.InvocationTargetException;
027import java.net.MalformedURLException;
028import java.net.URI;
029import java.net.URISyntaxException;
030import java.net.URL;
031import java.nio.file.Path;
032import java.nio.file.Paths;
033import java.util.regex.Pattern;
034import java.util.regex.PatternSyntaxException;
035
036import org.apache.commons.beanutils.ConversionException;
037
038import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
039
040/**
041 * Contains utility methods.
042 *
043 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
044 */
045public final class CommonUtils {
046
047    /** Prefix for the exception when unable to find resource. */
048    private static final String UNABLE_TO_FIND_EXCEPTION_PREFIX = "Unable to find: ";
049
050    /** Stop instances being created. **/
051    private CommonUtils() {
052
053    }
054
055    /**
056     * Returns whether the file extension matches what we are meant to process.
057     *
058     * @param file
059     *            the file to be checked.
060     * @param fileExtensions
061     *            files extensions, empty property in config makes it matches to all.
062     * @return whether there is a match.
063     */
064    public static boolean matchesFileExtension(File file, String... fileExtensions) {
065        boolean result = false;
066        if (fileExtensions == null || fileExtensions.length == 0) {
067            result = true;
068        }
069        else {
070            // normalize extensions so all of them have a leading dot
071            final String[] withDotExtensions = new String[fileExtensions.length];
072            for (int i = 0; i < fileExtensions.length; i++) {
073                final String extension = fileExtensions[i];
074                if (startsWithChar(extension, '.')) {
075                    withDotExtensions[i] = extension;
076                }
077                else {
078                    withDotExtensions[i] = "." + extension;
079                }
080            }
081
082            final String fileName = file.getName();
083            for (final String fileExtension : withDotExtensions) {
084                if (fileName.endsWith(fileExtension)) {
085                    result = true;
086                }
087            }
088        }
089
090        return result;
091    }
092
093    /**
094     * Returns whether the specified string contains only whitespace up to the specified index.
095     *
096     * @param index
097     *            index to check up to
098     * @param line
099     *            the line to check
100     * @return whether there is only whitespace
101     */
102    public static boolean hasWhitespaceBefore(int index, String line) {
103        for (int i = 0; i < index; i++) {
104            if (!Character.isWhitespace(line.charAt(i))) {
105                return false;
106            }
107        }
108        return true;
109    }
110
111    /**
112     * Returns the length of a string ignoring all trailing whitespace.
113     * It is a pity that there is not a trim() like
114     * method that only removed the trailing whitespace.
115     *
116     * @param line
117     *            the string to process
118     * @return the length of the string ignoring all trailing whitespace
119     **/
120    public static int lengthMinusTrailingWhitespace(String line) {
121        int len = line.length();
122        for (int i = len - 1; i >= 0; i--) {
123            if (!Character.isWhitespace(line.charAt(i))) {
124                break;
125            }
126            len--;
127        }
128        return len;
129    }
130
131    /**
132     * Returns the length of a String prefix with tabs expanded.
133     * Each tab is counted as the number of characters is
134     * takes to jump to the next tab stop.
135     *
136     * @param inputString
137     *            the input String
138     * @param toIdx
139     *            index in string (exclusive) where the calculation stops
140     * @param tabWidth
141     *            the distance between tab stop position.
142     * @return the length of string.substring(0, toIdx) with tabs expanded.
143     */
144    public static int lengthExpandedTabs(String inputString,
145            int toIdx,
146            int tabWidth) {
147        int len = 0;
148        for (int idx = 0; idx < toIdx; idx++) {
149            if (inputString.charAt(idx) == '\t') {
150                len = (len / tabWidth + 1) * tabWidth;
151            }
152            else {
153                len++;
154            }
155        }
156        return len;
157    }
158
159    /**
160     * Validates whether passed string is a valid pattern or not.
161     *
162     * @param pattern
163     *            string to validate
164     * @return true if the pattern is valid false otherwise
165     */
166    public static boolean isPatternValid(String pattern) {
167        try {
168            Pattern.compile(pattern);
169        }
170        catch (final PatternSyntaxException ignored) {
171            return false;
172        }
173        return true;
174    }
175
176    /**
177     * Helper method to create a regular expression.
178     *
179     * @param pattern
180     *            the pattern to match
181     * @return a created regexp object
182     * @throws ConversionException
183     *             if unable to create Pattern object.
184     **/
185    public static Pattern createPattern(String pattern) {
186        try {
187            return Pattern.compile(pattern);
188        }
189        catch (final PatternSyntaxException e) {
190            throw new ConversionException(
191                    "Failed to initialise regular expression " + pattern, e);
192        }
193    }
194
195    /**
196     * @param type
197     *            the fully qualified name. Cannot be null
198     * @return the base class name from a fully qualified name
199     */
200    public static String baseClassName(String type) {
201        final int i = type.lastIndexOf('.');
202
203        if (i == -1) {
204            return type;
205        }
206        else {
207            return type.substring(i + 1);
208        }
209    }
210
211    /**
212     * Constructs a normalized relative path between base directory and a given path.
213     *
214     * @param baseDirectory
215     *            the base path to which given path is relativized
216     * @param path
217     *            the path to relativize against base directory
218     * @return the relative normalized path between base directory and
219     *     path or path if base directory is null.
220     */
221    public static String relativizeAndNormalizePath(final String baseDirectory, final String path) {
222        if (baseDirectory == null) {
223            return path;
224        }
225        final Path pathAbsolute = Paths.get(path).normalize();
226        final Path pathBase = Paths.get(baseDirectory).normalize();
227        return pathBase.relativize(pathAbsolute).toString();
228    }
229
230    /**
231     * Tests if this string starts with the specified prefix.
232     * <p>
233     * It is faster version of {@link String#startsWith(String)} optimized for
234     *  one-character prefixes at the expense of
235     * some readability. Suggested by SimplifyStartsWith PMD rule:
236     * http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
237     * </p>
238     *
239     * @param value
240     *            the {@code String} to check
241     * @param prefix
242     *            the prefix to find
243     * @return {@code true} if the {@code char} is a prefix of the given {@code String};
244     *  {@code false} otherwise.
245     */
246    public static boolean startsWithChar(String value, char prefix) {
247        return !value.isEmpty() && value.charAt(0) == prefix;
248    }
249
250    /**
251     * Tests if this string ends with the specified suffix.
252     * <p>
253     * It is faster version of {@link String#endsWith(String)} optimized for
254     *  one-character suffixes at the expense of
255     * some readability. Suggested by SimplifyStartsWith PMD rule:
256     * http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
257     * </p>
258     *
259     * @param value
260     *            the {@code String} to check
261     * @param suffix
262     *            the suffix to find
263     * @return {@code true} if the {@code char} is a suffix of the given {@code String};
264     *  {@code false} otherwise.
265     */
266    public static boolean endsWithChar(String value, char suffix) {
267        return !value.isEmpty() && value.charAt(value.length() - 1) == suffix;
268    }
269
270    /**
271     * Gets constructor of targetClass.
272     * @param targetClass
273     *            from which constructor is returned
274     * @param parameterTypes
275     *            of constructor
276     * @return constructor of targetClass or {@link IllegalStateException} if any exception occurs
277     * @see Class#getConstructor(Class[])
278     */
279    public static Constructor<?> getConstructor(Class<?> targetClass, Class<?>... parameterTypes) {
280        try {
281            return targetClass.getConstructor(parameterTypes);
282        }
283        catch (NoSuchMethodException ex) {
284            throw new IllegalStateException(ex);
285        }
286    }
287
288    /**
289     * @param constructor
290     *            to invoke
291     * @param parameters
292     *            to pass to constructor
293     * @param <T>
294     *            type of constructor
295     * @return new instance of class or {@link IllegalStateException} if any exception occurs
296     * @see Constructor#newInstance(Object...)
297     */
298    public static <T> T invokeConstructor(Constructor<T> constructor, Object... parameters) {
299        try {
300            return constructor.newInstance(parameters);
301        }
302        catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
303            throw new IllegalStateException(ex);
304        }
305    }
306
307    /**
308     * Closes a stream re-throwing IOException as IllegalStateException.
309     *
310     * @param closeable
311     *            Closeable object
312     */
313    public static void close(Closeable closeable) {
314        if (closeable == null) {
315            return;
316        }
317        try {
318            closeable.close();
319        }
320        catch (IOException e) {
321            throw new IllegalStateException("Cannot close the stream", e);
322        }
323    }
324
325    /**
326     * Resolve the specified filename to a URI.
327     * @param filename name os the file
328     * @return resolved header file URI
329     * @throws CheckstyleException on failure
330     */
331    public static URI getUriByFilename(String filename) throws CheckstyleException {
332        // figure out if this is a File or a URL
333        URI uri;
334        try {
335            final URL url = new URL(filename);
336            uri = url.toURI();
337        }
338        catch (final URISyntaxException | MalformedURLException ignored) {
339            uri = null;
340        }
341
342        if (uri == null) {
343            final File file = new File(filename);
344            if (file.exists()) {
345                uri = file.toURI();
346            }
347            else {
348                // check to see if the file is in the classpath
349                try {
350                    final URL configUrl = CommonUtils.class
351                            .getResource(filename);
352                    if (configUrl == null) {
353                        throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename);
354                    }
355                    uri = configUrl.toURI();
356                }
357                catch (final URISyntaxException e) {
358                    throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename, e);
359                }
360            }
361        }
362
363        return uri;
364    }
365}