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