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}