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