001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.Matcher;
034import java.util.regex.Pattern;
035import java.util.regex.PatternSyntaxException;
036
037import org.apache.commons.beanutils.ConversionException;
038
039import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
040
041/**
042 * Contains utility methods.
043 *
044 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
045 */
046public final class CommonUtils {
047
048    /** Copied from org.apache.commons.lang3.ArrayUtils. */
049    public static final String[] EMPTY_STRING_ARRAY = new String[0];
050    /** Copied from org.apache.commons.lang3.ArrayUtils. */
051    public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = new Integer[0];
052    /** Copied from org.apache.commons.lang3.ArrayUtils. */
053    public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
054    /** Copied from org.apache.commons.lang3.ArrayUtils. */
055    public static final int[] EMPTY_INT_ARRAY = new int[0];
056    /** Copied from org.apache.commons.lang3.ArrayUtils. */
057    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
058    /** Copied from org.apache.commons.lang3.ArrayUtils. */
059    public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
060
061    /** Prefix for the exception when unable to find resource. */
062    private static final String UNABLE_TO_FIND_EXCEPTION_PREFIX = "Unable to find: ";
063
064    /** Stop instances being created. **/
065    private CommonUtils() {
066
067    }
068
069    /**
070     * Helper method to create a regular expression.
071     *
072     * @param pattern
073     *            the pattern to match
074     * @return a created regexp object
075     * @throws ConversionException
076     *             if unable to create Pattern object.
077     **/
078    public static Pattern createPattern(String pattern) {
079        return createPattern(pattern, 0);
080    }
081
082    /**
083     * Helper method to create a regular expression with a specific flags.
084     *
085     * @param pattern
086     *            the pattern to match
087     * @param flags
088     *            the flags to set
089     * @return a created regexp object
090     * @throws IllegalArgumentException
091     *             if unable to create Pattern object.
092     **/
093    public static Pattern createPattern(String pattern, int flags) {
094        try {
095            return Pattern.compile(pattern, flags);
096        }
097        catch (final PatternSyntaxException ex) {
098            throw new IllegalArgumentException(
099                "Failed to initialise regular expression " + pattern, ex);
100        }
101    }
102
103    /**
104     * Returns whether the file extension matches what we are meant to process.
105     *
106     * @param file
107     *            the file to be checked.
108     * @param fileExtensions
109     *            files extensions, empty property in config makes it matches to all.
110     * @return whether there is a match.
111     */
112    public static boolean matchesFileExtension(File file, String... fileExtensions) {
113        boolean result = false;
114        if (fileExtensions == null || fileExtensions.length == 0) {
115            result = true;
116        }
117        else {
118            // normalize extensions so all of them have a leading dot
119            final String[] withDotExtensions = new String[fileExtensions.length];
120            for (int i = 0; i < fileExtensions.length; i++) {
121                final String extension = fileExtensions[i];
122                if (startsWithChar(extension, '.')) {
123                    withDotExtensions[i] = extension;
124                }
125                else {
126                    withDotExtensions[i] = "." + extension;
127                }
128            }
129
130            final String fileName = file.getName();
131            for (final String fileExtension : withDotExtensions) {
132                if (fileName.endsWith(fileExtension)) {
133                    result = true;
134                    break;
135                }
136            }
137        }
138
139        return result;
140    }
141
142    /**
143     * Returns whether the specified string contains only whitespace up to the specified index.
144     *
145     * @param index
146     *            index to check up to
147     * @param line
148     *            the line to check
149     * @return whether there is only whitespace
150     */
151    public static boolean hasWhitespaceBefore(int index, String line) {
152        boolean result = true;
153        for (int i = 0; i < index; i++) {
154            if (!Character.isWhitespace(line.charAt(i))) {
155                result = false;
156                break;
157            }
158        }
159        return result;
160    }
161
162    /**
163     * Returns the length of a string ignoring all trailing whitespace.
164     * It is a pity that there is not a trim() like
165     * method that only removed the trailing whitespace.
166     *
167     * @param line
168     *            the string to process
169     * @return the length of the string ignoring all trailing whitespace
170     **/
171    public static int lengthMinusTrailingWhitespace(String line) {
172        int len = line.length();
173        for (int i = len - 1; i >= 0; i--) {
174            if (!Character.isWhitespace(line.charAt(i))) {
175                break;
176            }
177            len--;
178        }
179        return len;
180    }
181
182    /**
183     * Returns the length of a String prefix with tabs expanded.
184     * Each tab is counted as the number of characters is
185     * takes to jump to the next tab stop.
186     *
187     * @param inputString
188     *            the input String
189     * @param toIdx
190     *            index in string (exclusive) where the calculation stops
191     * @param tabWidth
192     *            the distance between tab stop position.
193     * @return the length of string.substring(0, toIdx) with tabs expanded.
194     */
195    public static int lengthExpandedTabs(String inputString,
196            int toIdx,
197            int tabWidth) {
198        int len = 0;
199        for (int idx = 0; idx < toIdx; idx++) {
200            if (inputString.charAt(idx) == '\t') {
201                len = (len / tabWidth + 1) * tabWidth;
202            }
203            else {
204                len++;
205            }
206        }
207        return len;
208    }
209
210    /**
211     * Validates whether passed string is a valid pattern or not.
212     *
213     * @param pattern
214     *            string to validate
215     * @return true if the pattern is valid false otherwise
216     */
217    public static boolean isPatternValid(String pattern) {
218        boolean isValid = true;
219        try {
220            Pattern.compile(pattern);
221        }
222        catch (final PatternSyntaxException ignored) {
223            isValid = false;
224        }
225        return isValid;
226    }
227
228    /**
229     * Returns base class name from qualified name.
230     * @param type
231     *            the fully qualified name. Cannot be null
232     * @return the base class name from a fully qualified name
233     */
234    public static String baseClassName(String type) {
235        final String className;
236        final int index = type.lastIndexOf('.');
237        if (index == -1) {
238            className = type;
239        }
240        else {
241            className = type.substring(index + 1);
242        }
243        return className;
244    }
245
246    /**
247     * Constructs a normalized relative path between base directory and a given path.
248     *
249     * @param baseDirectory
250     *            the base path to which given path is relativized
251     * @param path
252     *            the path to relativize against base directory
253     * @return the relative normalized path between base directory and
254     *     path or path if base directory is null.
255     */
256    public static String relativizeAndNormalizePath(final String baseDirectory, final String path) {
257        final String resultPath;
258        if (baseDirectory == null) {
259            resultPath = path;
260        }
261        else {
262            final Path pathAbsolute = Paths.get(path).normalize();
263            final Path pathBase = Paths.get(baseDirectory).normalize();
264            resultPath = pathBase.relativize(pathAbsolute).toString();
265        }
266        return resultPath;
267    }
268
269    /**
270     * Tests if this string starts with the specified prefix.
271     * <p>
272     * It is faster version of {@link String#startsWith(String)} optimized for
273     *  one-character prefixes at the expense of
274     * some readability. Suggested by SimplifyStartsWith PMD rule:
275     * http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
276     * </p>
277     *
278     * @param value
279     *            the {@code String} to check
280     * @param prefix
281     *            the prefix to find
282     * @return {@code true} if the {@code char} is a prefix of the given {@code String};
283     *  {@code false} otherwise.
284     */
285    public static boolean startsWithChar(String value, char prefix) {
286        return !value.isEmpty() && value.charAt(0) == prefix;
287    }
288
289    /**
290     * Tests if this string ends with the specified suffix.
291     * <p>
292     * It is faster version of {@link String#endsWith(String)} optimized for
293     *  one-character suffixes at the expense of
294     * some readability. Suggested by SimplifyStartsWith PMD rule:
295     * http://pmd.sourceforge.net/pmd-5.3.1/pmd-java/rules/java/optimizations.html#SimplifyStartsWith
296     * </p>
297     *
298     * @param value
299     *            the {@code String} to check
300     * @param suffix
301     *            the suffix to find
302     * @return {@code true} if the {@code char} is a suffix of the given {@code String};
303     *  {@code false} otherwise.
304     */
305    public static boolean endsWithChar(String value, char suffix) {
306        return !value.isEmpty() && value.charAt(value.length() - 1) == suffix;
307    }
308
309    /**
310     * Gets constructor of targetClass.
311     * @param targetClass
312     *            from which constructor is returned
313     * @param parameterTypes
314     *            of constructor
315     * @param <T> type of the target class object.
316     * @return constructor of targetClass or {@link IllegalStateException} if any exception occurs
317     * @see Class#getConstructor(Class[])
318     */
319    public static <T> Constructor<T> getConstructor(Class<T> targetClass,
320                                                    Class<?>... parameterTypes) {
321        try {
322            return targetClass.getConstructor(parameterTypes);
323        }
324        catch (NoSuchMethodException ex) {
325            throw new IllegalStateException(ex);
326        }
327    }
328
329    /**
330     * Returns new instance of a class.
331     * @param constructor
332     *            to invoke
333     * @param parameters
334     *            to pass to constructor
335     * @param <T>
336     *            type of constructor
337     * @return new instance of class or {@link IllegalStateException} if any exception occurs
338     * @see Constructor#newInstance(Object...)
339     */
340    public static <T> T invokeConstructor(Constructor<T> constructor, Object... parameters) {
341        try {
342            return constructor.newInstance(parameters);
343        }
344        catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
345            throw new IllegalStateException(ex);
346        }
347    }
348
349    /**
350     * Closes a stream re-throwing IOException as IllegalStateException.
351     *
352     * @param closeable
353     *            Closeable object
354     */
355    public static void close(Closeable closeable) {
356        if (closeable != null) {
357            try {
358                closeable.close();
359            }
360            catch (IOException ex) {
361                throw new IllegalStateException("Cannot close the stream", ex);
362            }
363        }
364    }
365
366    /**
367     * Resolve the specified filename to a URI.
368     * @param filename name os the file
369     * @return resolved header file URI
370     * @throws CheckstyleException on failure
371     */
372    public static URI getUriByFilename(String filename) throws CheckstyleException {
373        // figure out if this is a File or a URL
374        URI uri;
375        try {
376            final URL url = new URL(filename);
377            uri = url.toURI();
378        }
379        catch (final URISyntaxException | MalformedURLException ignored) {
380            uri = null;
381        }
382
383        if (uri == null) {
384            final File file = new File(filename);
385            if (file.exists()) {
386                uri = file.toURI();
387            }
388            else {
389                // check to see if the file is in the classpath
390                try {
391                    final URL configUrl = CommonUtils.class
392                            .getResource(filename);
393                    if (configUrl == null) {
394                        throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename);
395                    }
396                    uri = configUrl.toURI();
397                }
398                catch (final URISyntaxException ex) {
399                    throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename, ex);
400                }
401            }
402        }
403
404        return uri;
405    }
406
407    /**
408     * Puts part of line, which matches regexp into given template
409     * on positions $n where 'n' is number of matched part in line.
410     * @param template the string to expand.
411     * @param lineToPlaceInTemplate contains expression which should be placed into string.
412     * @param regexp expression to find in comment.
413     * @return the string, based on template filled with given lines
414     */
415    public static String fillTemplateWithStringsByRegexp(
416        String template, String lineToPlaceInTemplate, Pattern regexp) {
417        final Matcher matcher = regexp.matcher(lineToPlaceInTemplate);
418        String result = template;
419        if (matcher.find()) {
420            for (int i = 0; i <= matcher.groupCount(); i++) {
421                // $n expands comment match like in Pattern.subst().
422                result = result.replaceAll("\\$" + i, matcher.group(i));
423            }
424        }
425        return result;
426    }
427
428    /**
429     * Returns file name without extension.
430     * We do not use the method from Guava library to reduce Checkstyle's dependencies
431     * on external libraries.
432     * @param fullFilename file name with extension.
433     * @return file name without extension.
434     */
435    public static String getFileNameWithoutExtension(String fullFilename) {
436        final String fileName = new File(fullFilename).getName();
437        final int dotIndex = fileName.lastIndexOf('.');
438        final String fileNameWithoutExtension;
439        if (dotIndex == -1) {
440            fileNameWithoutExtension = fileName;
441        }
442        else {
443            fileNameWithoutExtension = fileName.substring(0, dotIndex);
444        }
445        return fileNameWithoutExtension;
446    }
447
448    /**
449     * Returns file extension for the given file name
450     * or empty string if file does not have an extension.
451     * We do not use the method from Guava library to reduce Checkstyle's dependencies
452     * on external libraries.
453     * @param fileNameWithExtension file name with extension.
454     * @return file extension for the given file name
455     *         or empty string if file does not have an extension.
456     */
457    public static String getFileExtension(String fileNameWithExtension) {
458        final String fileName = Paths.get(fileNameWithExtension).toString();
459        final int dotIndex = fileName.lastIndexOf('.');
460        final String extension;
461        if (dotIndex == -1) {
462            extension = "";
463        }
464        else {
465            extension = fileName.substring(dotIndex + 1);
466        }
467        return extension;
468    }
469
470    /**
471     * Checks whether the given string is a valid identifier.
472     * @param str A string to check.
473     * @return true when the given string contains valid identifier.
474     */
475    public static boolean isIdentifier(String str) {
476        boolean isIdentifier = !str.isEmpty();
477
478        for (int i = 0; isIdentifier && i < str.length(); i++) {
479            if (i == 0) {
480                isIdentifier = Character.isJavaIdentifierStart(str.charAt(0));
481            }
482            else {
483                isIdentifier = Character.isJavaIdentifierPart(str.charAt(i));
484            }
485        }
486
487        return isIdentifier;
488    }
489
490    /**
491     * Checks whether the given string is a valid name.
492     * @param str A string to check.
493     * @return true when the given string contains valid name.
494     */
495    public static boolean isName(String str) {
496        boolean isName = !str.isEmpty();
497
498        final String[] identifiers = str.split("\\.", -1);
499        for (int i = 0; isName && i < identifiers.length; i++) {
500            isName = isIdentifier(identifiers[i]);
501        }
502
503        return isName;
504    }
505
506    /**
507     * Checks if the value arg is blank by either being null,
508     * empty, or contains only whitespace characters.
509     * @param value A string to check.
510     * @return true if the arg is blank.
511     */
512    public static boolean isBlank(String value) {
513        boolean result = true;
514        if (value != null && !value.isEmpty()) {
515            for (int i = 0; i < value.length(); i++) {
516                if (!Character.isWhitespace(value.charAt(i))) {
517                    result = false;
518                    break;
519                }
520            }
521        }
522        return result;
523    }
524}