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