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