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