001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.api;
020
021import com.google.common.collect.Lists;
022import com.google.common.collect.Maps;
023
024import java.io.Closeable;
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.IOException;
028import java.io.InputStreamReader;
029import java.io.LineNumberReader;
030import java.io.UnsupportedEncodingException;
031import java.util.List;
032import java.util.concurrent.ConcurrentMap;
033import java.util.regex.Pattern;
034import java.util.regex.PatternSyntaxException;
035
036import org.apache.commons.beanutils.ConversionException;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039
040/**
041 * Contains utility methods.
042 *
043 * @author Oliver Burn
044 * @version 1.0
045 */
046public final class Utils
047{
048    /** Map of all created regular expressions **/
049    private static final ConcurrentMap<String, Pattern> CREATED_RES =
050        Maps.newConcurrentMap();
051    /** Shared instance of logger for exception logging. */
052    private static final Log EXCEPTION_LOG =
053        LogFactory.getLog("com.puppycrawl.tools.checkstyle.ExceptionLog");
054
055    ///CLOVER:OFF
056    /** stop instances being created **/
057    private Utils()
058    {
059    }
060    ///CLOVER:ON
061
062    /**
063     * Accessor for shared instance of logger which should be
064     * used to log all exceptions occurred during <code>FileSetCheck</code>
065     * work (<code>debug()</code> should be used).
066     * @return shared exception logger.
067     */
068    public static Log getExceptionLogger()
069    {
070        return EXCEPTION_LOG;
071    }
072
073    /**
074     * Returns whether the specified string contains only whitespace up to the
075     * specified index.
076     *
077     * @param index index to check up to
078     * @param line the line to check
079     * @return whether there is only whitespace
080     */
081    public static boolean whitespaceBefore(int index, String line)
082    {
083        for (int i = 0; i < index; i++) {
084            if (!Character.isWhitespace(line.charAt(i))) {
085                return false;
086            }
087        }
088        return true;
089    }
090
091    /**
092     * Returns the length of a string ignoring all trailing whitespace. It is a
093     * pity that there is not a trim() like method that only removed the
094     * trailing whitespace.
095     * @param line the string to process
096     * @return the length of the string ignoring all trailing whitespace
097     **/
098    public static int lengthMinusTrailingWhitespace(String line)
099    {
100        int len = line.length();
101        for (int i = len - 1; i >= 0; i--) {
102            if (!Character.isWhitespace(line.charAt(i))) {
103                break;
104            }
105            len--;
106        }
107        return len;
108    }
109
110    /**
111     * Returns the length of a String prefix with tabs expanded.
112     * Each tab is counted as the number of characters is takes to
113     * jump to the next tab stop.
114     * @param string the input String
115     * @param toIdx index in string (exclusive) where the calculation stops
116     * @param tabWidth the distance between tab stop position.
117     * @return the length of string.substring(0, toIdx) with tabs expanded.
118     */
119    public static int lengthExpandedTabs(String string,
120                                         int toIdx,
121                                         int tabWidth)
122    {
123        int len = 0;
124        for (int idx = 0; idx < toIdx; idx++) {
125            if (string.charAt(idx) == '\t') {
126                len = (len / tabWidth + 1) * tabWidth;
127            }
128            else {
129                len++;
130            }
131        }
132        return len;
133    }
134
135    /**
136     * This is a factory method to return an Pattern object for the specified
137     * regular expression. It calls {@link #getPattern(String, int)} with the
138     * compile flags defaults to 0.
139     * @return an Pattern object for the supplied pattern
140     * @param pattern the regular expression pattern
141     * @throws PatternSyntaxException an invalid pattern was supplied
142     **/
143    public static Pattern getPattern(String pattern)
144        throws PatternSyntaxException
145    {
146        return getPattern(pattern, 0);
147    }
148
149    /**
150     * This is a factory method to return an Pattern object for the specified
151     * regular expression and compile flags.
152     * @return an Pattern object for the supplied pattern
153     * @param pattern the regular expression pattern
154     * @param compileFlags the compilation flags
155     * @throws PatternSyntaxException an invalid pattern was supplied
156     **/
157    public static Pattern getPattern(String pattern, int compileFlags)
158        throws PatternSyntaxException
159    {
160        final String key = pattern + ":flags-" + compileFlags;
161        Pattern retVal = CREATED_RES.get(key);
162        if (retVal == null) {
163            retVal = Pattern.compile(pattern, compileFlags);
164            CREATED_RES.putIfAbsent(key, retVal);
165        }
166        return retVal;
167    }
168
169    /**
170     * Loads the contents of a file in a String array.
171     * @return the lines in the file
172     * @param fileName the name of the file to load
173     * @throws IOException error occurred
174     * @deprecated consider using {@link FileText} instead
175     **/
176    @Deprecated
177    public static String[] getLines(String fileName)
178        throws IOException
179    {
180        return getLines(
181            fileName,
182            System.getProperty("file.encoding", "UTF-8"));
183    }
184
185    /**
186     * Loads the contents of a file in a String array using
187     * the named charset.
188     * @return the lines in the file
189     * @param fileName the name of the file to load
190     * @param charsetName the name of a supported charset
191     * @throws IOException error occurred
192     * @deprecated consider using {@link FileText} instead
193     **/
194    @Deprecated
195    public static String[] getLines(String fileName, String charsetName)
196        throws IOException
197    {
198        final List<String> lines = Lists.newArrayList();
199        final FileInputStream fr = new FileInputStream(fileName);
200        LineNumberReader lnr = null;
201        try {
202            lnr = new LineNumberReader(new InputStreamReader(fr, charsetName));
203        }
204        catch (final UnsupportedEncodingException ex) {
205            fr.close();
206            final String message = "unsupported charset: " + ex.getMessage();
207            throw new UnsupportedEncodingException(message);
208        }
209        try {
210            while (true) {
211                final String l = lnr.readLine();
212                if (l == null) {
213                    break;
214                }
215                lines.add(l);
216            }
217        }
218        finally {
219            Utils.closeQuietly(lnr);
220        }
221        return lines.toArray(new String[lines.size()]);
222    }
223
224    /**
225     * Helper method to create a regular expression.
226     * @param pattern the pattern to match
227     * @return a created regexp object
228     * @throws ConversionException if unable to create Pattern object.
229     **/
230    public static Pattern createPattern(String pattern)
231        throws ConversionException
232    {
233        Pattern retVal = null;
234        try {
235            retVal = getPattern(pattern);
236        }
237        catch (final PatternSyntaxException e) {
238            throw new ConversionException(
239                "Failed to initialise regexp expression " + pattern, e);
240        }
241        return retVal;
242    }
243
244    /**
245     * @return the base class name from a fully qualified name
246     * @param type the fully qualified name. Cannot be null
247     */
248    public static String baseClassname(String type)
249    {
250        final int i = type.lastIndexOf(".");
251        return (i == -1) ? type : type.substring(i + 1);
252    }
253
254    /**
255     * Create a stripped down version of a filename.
256     * @param basedir the prefix to strip off the original filename
257     * @param fileName the original filename
258     * @return the filename where an initial prefix of basedir is stripped
259     */
260    public static String getStrippedFileName(
261            final String basedir, final String fileName)
262    {
263        final String stripped;
264        if ((basedir == null) || !fileName.startsWith(basedir)) {
265            stripped = fileName;
266        }
267        else {
268            // making the assumption that there is text after basedir
269            final int skipSep = basedir.endsWith(File.separator) ? 0 : 1;
270            stripped = fileName.substring(basedir.length() + skipSep);
271        }
272        return stripped;
273    }
274
275    /**
276     * Closes the supplied {@link Closeable} object ignoring an
277     * {@link IOException} if it is thrown. Honestly, what are you going to
278     * do if you cannot close a file.
279     * @param shutting the object to be closed.
280     */
281    public static void closeQuietly(Closeable shutting)
282    {
283        if (null != shutting) {
284            try {
285                shutting.close();
286            }
287            catch (IOException e) {
288                ; // ignore
289            }
290        }
291    }
292}