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 aIndex index to check up to
078     * @param aLine the line to check
079     * @return whether there is only whitespace
080     */
081    public static boolean whitespaceBefore(int aIndex, String aLine)
082    {
083        for (int i = 0; i < aIndex; i++) {
084            if (!Character.isWhitespace(aLine.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 aLine the string to process
096     * @return the length of the string ignoring all trailing whitespace
097     **/
098    public static int lengthMinusTrailingWhitespace(String aLine)
099    {
100        int len = aLine.length();
101        for (int i = len - 1; i >= 0; i--) {
102            if (!Character.isWhitespace(aLine.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 aString the input String
115     * @param aToIdx index in aString (exclusive) where the calculation stops
116     * @param aTabWidth the distance between tab stop position.
117     * @return the length of aString.substring(0, aToIdx) with tabs expanded.
118     */
119    public static int lengthExpandedTabs(String aString,
120                                         int aToIdx,
121                                         int aTabWidth)
122    {
123        int len = 0;
124        final char[] chars = aString.toCharArray();
125        for (int idx = 0; idx < aToIdx; idx++) {
126            if (chars[idx] == '\t') {
127                len = (len / aTabWidth + 1) * aTabWidth;
128            }
129            else {
130                len++;
131            }
132        }
133        return len;
134    }
135
136    /**
137     * This is a factory method to return an Pattern object for the specified
138     * regular expression. It calls {@link #getPattern(String, int)} with the
139     * compile flags defaults to 0.
140     * @return an Pattern object for the supplied pattern
141     * @param aPattern the regular expression pattern
142     * @throws PatternSyntaxException an invalid pattern was supplied
143     **/
144    public static Pattern getPattern(String aPattern)
145        throws PatternSyntaxException
146    {
147        return getPattern(aPattern, 0);
148    }
149
150    /**
151     * This is a factory method to return an Pattern object for the specified
152     * regular expression and compile flags.
153     * @return an Pattern object for the supplied pattern
154     * @param aPattern the regular expression pattern
155     * @param aCompileFlags the compilation flags
156     * @throws PatternSyntaxException an invalid pattern was supplied
157     **/
158    public static Pattern getPattern(String aPattern, int aCompileFlags)
159        throws PatternSyntaxException
160    {
161        final String key = aPattern + ":flags-" + aCompileFlags;
162        Pattern retVal = CREATED_RES.get(key);
163        if (retVal == null) {
164            retVal = Pattern.compile(aPattern, aCompileFlags);
165            CREATED_RES.putIfAbsent(key, retVal);
166        }
167        return retVal;
168    }
169
170    /**
171     * Loads the contents of a file in a String array.
172     * @return the lines in the file
173     * @param aFileName the name of the file to load
174     * @throws IOException error occurred
175     * @deprecated consider using {@link FileText} instead
176     **/
177    @Deprecated
178    public static String[] getLines(String aFileName)
179        throws IOException
180    {
181        return getLines(
182            aFileName,
183            System.getProperty("file.encoding", "UTF-8"));
184    }
185
186    /**
187     * Loads the contents of a file in a String array using
188     * the named charset.
189     * @return the lines in the file
190     * @param aFileName the name of the file to load
191     * @param aCharsetName the name of a supported charset
192     * @throws IOException error occurred
193     * @deprecated consider using {@link FileText} instead
194     **/
195    @Deprecated
196    public static String[] getLines(String aFileName, String aCharsetName)
197        throws IOException
198    {
199        final List<String> lines = Lists.newArrayList();
200        final FileInputStream fr = new FileInputStream(aFileName);
201        LineNumberReader lnr = null;
202        try {
203            lnr = new LineNumberReader(new InputStreamReader(fr, aCharsetName));
204        }
205        catch (final UnsupportedEncodingException ex) {
206            fr.close();
207            final String message = "unsupported charset: " + ex.getMessage();
208            throw new UnsupportedEncodingException(message);
209        }
210        try {
211            while (true) {
212                final String l = lnr.readLine();
213                if (l == null) {
214                    break;
215                }
216                lines.add(l);
217            }
218        }
219        finally {
220            Utils.closeQuietly(lnr);
221        }
222        return lines.toArray(new String[lines.size()]);
223    }
224
225    /**
226     * Helper method to create a regular expression.
227     * @param aPattern the pattern to match
228     * @return a created regexp object
229     * @throws ConversionException if unable to create Pattern object.
230     **/
231    public static Pattern createPattern(String aPattern)
232        throws ConversionException
233    {
234        Pattern retVal = null;
235        try {
236            retVal = getPattern(aPattern);
237        }
238        catch (final PatternSyntaxException e) {
239            throw new ConversionException(
240                "Failed to initialise regexp expression " + aPattern, e);
241        }
242        return retVal;
243    }
244
245    /**
246     * @return the base class name from a fully qualified name
247     * @param aType the fully qualified name. Cannot be null
248     */
249    public static String baseClassname(String aType)
250    {
251        final int i = aType.lastIndexOf(".");
252        return (i == -1) ? aType : aType.substring(i + 1);
253    }
254
255    /**
256     * Create a stripped down version of a filename.
257     * @param aBasedir the prefix to strip off the original filename
258     * @param aFileName the original filename
259     * @return the filename where an initial prefix of basedir is stripped
260     */
261    public static String getStrippedFileName(
262            final String aBasedir, final String aFileName)
263    {
264        final String stripped;
265        if ((aBasedir == null) || !aFileName.startsWith(aBasedir)) {
266            stripped = aFileName;
267        }
268        else {
269            // making the assumption that there is text after basedir
270            final int skipSep = aBasedir.endsWith(File.separator) ? 0 : 1;
271            stripped = aFileName.substring(aBasedir.length() + skipSep);
272        }
273        return stripped;
274    }
275
276    /**
277     * Closes the supplied {@link Closeable} object ignoring an
278     * {@link IOException} if it is thrown. Honestly, what are you going to
279     * do if you cannot close a file.
280     * @param aShutting the object to be closed.
281     */
282    public static void closeQuietly(Closeable aShutting)
283    {
284        if (null != aShutting) {
285            try {
286                aShutting.close();
287            }
288            catch (IOException e) {
289                ; // ignore
290            }
291        }
292    }
293}