001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 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 * Helper method to create a regular expression. 058 * 059 * @param pattern 060 * the pattern to match 061 * @return a created regexp object 062 * @throws ConversionException 063 * if unable to create Pattern object. 064 **/ 065 public static Pattern createPattern(String pattern) { 066 return createPattern(pattern, 0); 067 } 068 069 /** 070 * Helper method to create a regular expression with a specific flags. 071 * 072 * @param pattern 073 * the pattern to match 074 * @param flags 075 * the flags to set 076 * @return a created regexp object 077 * @throws ConversionException 078 * if unable to create Pattern object. 079 **/ 080 public static Pattern createPattern(String pattern, int flags) { 081 try { 082 return Pattern.compile(pattern, flags); 083 } 084 catch (final PatternSyntaxException ex) { 085 throw new ConversionException( 086 "Failed to initialise regular expression " + pattern, ex); 087 } 088 } 089 090 /** 091 * Returns whether the file extension matches what we are meant to process. 092 * 093 * @param file 094 * the file to be checked. 095 * @param fileExtensions 096 * files extensions, empty property in config makes it matches to all. 097 * @return whether there is a match. 098 */ 099 public static boolean matchesFileExtension(File file, String... fileExtensions) { 100 boolean result = false; 101 if (fileExtensions == null || fileExtensions.length == 0) { 102 result = true; 103 } 104 else { 105 // normalize extensions so all of them have a leading dot 106 final String[] withDotExtensions = new String[fileExtensions.length]; 107 for (int i = 0; i < fileExtensions.length; i++) { 108 final String extension = fileExtensions[i]; 109 if (startsWithChar(extension, '.')) { 110 withDotExtensions[i] = extension; 111 } 112 else { 113 withDotExtensions[i] = "." + extension; 114 } 115 } 116 117 final String fileName = file.getName(); 118 for (final String fileExtension : withDotExtensions) { 119 if (fileName.endsWith(fileExtension)) { 120 result = true; 121 } 122 } 123 } 124 125 return result; 126 } 127 128 /** 129 * Returns whether the specified string contains only whitespace up to the specified index. 130 * 131 * @param index 132 * index to check up to 133 * @param line 134 * the line to check 135 * @return whether there is only whitespace 136 */ 137 public static boolean hasWhitespaceBefore(int index, String line) { 138 for (int i = 0; i < index; i++) { 139 if (!Character.isWhitespace(line.charAt(i))) { 140 return false; 141 } 142 } 143 return true; 144 } 145 146 /** 147 * Returns the length of a string ignoring all trailing whitespace. 148 * It is a pity that there is not a trim() like 149 * method that only removed the trailing whitespace. 150 * 151 * @param line 152 * the string to process 153 * @return the length of the string ignoring all trailing whitespace 154 **/ 155 public static int lengthMinusTrailingWhitespace(String line) { 156 int len = line.length(); 157 for (int i = len - 1; i >= 0; i--) { 158 if (!Character.isWhitespace(line.charAt(i))) { 159 break; 160 } 161 len--; 162 } 163 return len; 164 } 165 166 /** 167 * Returns the length of a String prefix with tabs expanded. 168 * Each tab is counted as the number of characters is 169 * takes to jump to the next tab stop. 170 * 171 * @param inputString 172 * the input String 173 * @param toIdx 174 * index in string (exclusive) where the calculation stops 175 * @param tabWidth 176 * the distance between tab stop position. 177 * @return the length of string.substring(0, toIdx) with tabs expanded. 178 */ 179 public static int lengthExpandedTabs(String inputString, 180 int toIdx, 181 int tabWidth) { 182 int len = 0; 183 for (int idx = 0; idx < toIdx; idx++) { 184 if (inputString.charAt(idx) == '\t') { 185 len = (len / tabWidth + 1) * tabWidth; 186 } 187 else { 188 len++; 189 } 190 } 191 return len; 192 } 193 194 /** 195 * Validates whether passed string is a valid pattern or not. 196 * 197 * @param pattern 198 * string to validate 199 * @return true if the pattern is valid false otherwise 200 */ 201 public static boolean isPatternValid(String pattern) { 202 try { 203 Pattern.compile(pattern); 204 } 205 catch (final PatternSyntaxException ignored) { 206 return false; 207 } 208 return true; 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 * @param <T> type of the target class object. 293 * @return constructor of targetClass or {@link IllegalStateException} if any exception occurs 294 * @see Class#getConstructor(Class[]) 295 */ 296 public static <T> Constructor<T> getConstructor(Class<T> targetClass, 297 Class<?>... parameterTypes) { 298 try { 299 return targetClass.getConstructor(parameterTypes); 300 } 301 catch (NoSuchMethodException ex) { 302 throw new IllegalStateException(ex); 303 } 304 } 305 306 /** 307 * @param constructor 308 * to invoke 309 * @param parameters 310 * to pass to constructor 311 * @param <T> 312 * type of constructor 313 * @return new instance of class or {@link IllegalStateException} if any exception occurs 314 * @see Constructor#newInstance(Object...) 315 */ 316 public static <T> T invokeConstructor(Constructor<T> constructor, Object... parameters) { 317 try { 318 return constructor.newInstance(parameters); 319 } 320 catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { 321 throw new IllegalStateException(ex); 322 } 323 } 324 325 /** 326 * Closes a stream re-throwing IOException as IllegalStateException. 327 * 328 * @param closeable 329 * Closeable object 330 */ 331 public static void close(Closeable closeable) { 332 if (closeable == null) { 333 return; 334 } 335 try { 336 closeable.close(); 337 } 338 catch (IOException ex) { 339 throw new IllegalStateException("Cannot close the stream", ex); 340 } 341 } 342 343 /** 344 * Resolve the specified filename to a URI. 345 * @param filename name os the file 346 * @return resolved header file URI 347 * @throws CheckstyleException on failure 348 */ 349 public static URI getUriByFilename(String filename) throws CheckstyleException { 350 // figure out if this is a File or a URL 351 URI uri; 352 try { 353 final URL url = new URL(filename); 354 uri = url.toURI(); 355 } 356 catch (final URISyntaxException | MalformedURLException ignored) { 357 uri = null; 358 } 359 360 if (uri == null) { 361 final File file = new File(filename); 362 if (file.exists()) { 363 uri = file.toURI(); 364 } 365 else { 366 // check to see if the file is in the classpath 367 try { 368 final URL configUrl = CommonUtils.class 369 .getResource(filename); 370 if (configUrl == null) { 371 throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename); 372 } 373 uri = configUrl.toURI(); 374 } 375 catch (final URISyntaxException ex) { 376 throw new CheckstyleException(UNABLE_TO_FIND_EXCEPTION_PREFIX + filename, ex); 377 } 378 } 379 } 380 381 return uri; 382 } 383 384 /** 385 * Puts part of line, which matches regexp into given template 386 * on positions $n where 'n' is number of matched part in line. 387 * @param template the string to expand. 388 * @param lineToPlaceInTemplate contains expression which should be placed into string. 389 * @param regexp expression to find in comment. 390 * @return the string, based on template filled with given lines 391 */ 392 public static String fillTemplateWithStringsByRegexp( 393 String template, String lineToPlaceInTemplate, Pattern regexp) { 394 final Matcher matcher = regexp.matcher(lineToPlaceInTemplate); 395 String result = template; 396 if (matcher.find()) { 397 for (int i = 0; i <= matcher.groupCount(); i++) { 398 // $n expands comment match like in Pattern.subst(). 399 result = result.replaceAll("\\$" + i, matcher.group(i)); 400 } 401 } 402 return result; 403 } 404}