001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.api; 021 022import java.beans.PropertyDescriptor; 023import java.lang.reflect.InvocationTargetException; 024import java.net.URI; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.List; 028import java.util.Locale; 029import java.util.StringTokenizer; 030import java.util.regex.Pattern; 031 032import org.apache.commons.beanutils.BeanUtilsBean; 033import org.apache.commons.beanutils.ConversionException; 034import org.apache.commons.beanutils.ConvertUtilsBean; 035import org.apache.commons.beanutils.Converter; 036import org.apache.commons.beanutils.PropertyUtils; 037import org.apache.commons.beanutils.PropertyUtilsBean; 038import org.apache.commons.beanutils.converters.ArrayConverter; 039import org.apache.commons.beanutils.converters.BooleanConverter; 040import org.apache.commons.beanutils.converters.ByteConverter; 041import org.apache.commons.beanutils.converters.CharacterConverter; 042import org.apache.commons.beanutils.converters.DoubleConverter; 043import org.apache.commons.beanutils.converters.FloatConverter; 044import org.apache.commons.beanutils.converters.IntegerConverter; 045import org.apache.commons.beanutils.converters.LongConverter; 046import org.apache.commons.beanutils.converters.ShortConverter; 047 048import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier; 049import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 050 051/** 052 * A Java Bean that implements the component lifecycle interfaces by 053 * calling the bean's setters for all configuration attributes. 054 * @author lkuehne 055 */ 056// -@cs[AbstractClassName] We can not brake compatibility with previous versions. 057public abstract class AutomaticBean 058 implements Configurable, Contextualizable { 059 060 /** 061 * Enum to specify behaviour regarding ignored modules. 062 */ 063 public enum OutputStreamOptions { 064 065 /** 066 * Close stream in the end. 067 */ 068 CLOSE, 069 070 /** 071 * Do nothing in the end. 072 */ 073 NONE 074 075 } 076 077 /** Comma separator for StringTokenizer. */ 078 private static final String COMMA_SEPARATOR = ","; 079 080 /** The configuration of this bean. */ 081 private Configuration configuration; 082 083 /** 084 * Provides a hook to finish the part of this component's setup that 085 * was not handled by the bean introspection. 086 * <p> 087 * The default implementation does nothing. 088 * </p> 089 * @throws CheckstyleException if there is a configuration error. 090 */ 091 protected abstract void finishLocalSetup() throws CheckstyleException; 092 093 /** 094 * Creates a BeanUtilsBean that is configured to use 095 * type converters that throw a ConversionException 096 * instead of using the default value when something 097 * goes wrong. 098 * 099 * @return a configured BeanUtilsBean 100 */ 101 private static BeanUtilsBean createBeanUtilsBean() { 102 final ConvertUtilsBean cub = new ConvertUtilsBean(); 103 104 registerIntegralTypes(cub); 105 registerCustomTypes(cub); 106 107 return new BeanUtilsBean(cub, new PropertyUtilsBean()); 108 } 109 110 /** 111 * Register basic types of JDK like boolean, int, and String to use with BeanUtils. All these 112 * types are found in the {@code java.lang} package. 113 * @param cub 114 * Instance of {@link ConvertUtilsBean} to register types with. 115 */ 116 private static void registerIntegralTypes(ConvertUtilsBean cub) { 117 cub.register(new BooleanConverter(), Boolean.TYPE); 118 cub.register(new BooleanConverter(), Boolean.class); 119 cub.register(new ArrayConverter( 120 boolean[].class, new BooleanConverter()), boolean[].class); 121 cub.register(new ByteConverter(), Byte.TYPE); 122 cub.register(new ByteConverter(), Byte.class); 123 cub.register(new ArrayConverter(byte[].class, new ByteConverter()), 124 byte[].class); 125 cub.register(new CharacterConverter(), Character.TYPE); 126 cub.register(new CharacterConverter(), Character.class); 127 cub.register(new ArrayConverter(char[].class, new CharacterConverter()), 128 char[].class); 129 cub.register(new DoubleConverter(), Double.TYPE); 130 cub.register(new DoubleConverter(), Double.class); 131 cub.register(new ArrayConverter(double[].class, new DoubleConverter()), 132 double[].class); 133 cub.register(new FloatConverter(), Float.TYPE); 134 cub.register(new FloatConverter(), Float.class); 135 cub.register(new ArrayConverter(float[].class, new FloatConverter()), 136 float[].class); 137 cub.register(new IntegerConverter(), Integer.TYPE); 138 cub.register(new IntegerConverter(), Integer.class); 139 cub.register(new ArrayConverter(int[].class, new IntegerConverter()), 140 int[].class); 141 cub.register(new LongConverter(), Long.TYPE); 142 cub.register(new LongConverter(), Long.class); 143 cub.register(new ArrayConverter(long[].class, new LongConverter()), 144 long[].class); 145 cub.register(new ShortConverter(), Short.TYPE); 146 cub.register(new ShortConverter(), Short.class); 147 cub.register(new ArrayConverter(short[].class, new ShortConverter()), 148 short[].class); 149 cub.register(new RelaxedStringArrayConverter(), String[].class); 150 151 // BigDecimal, BigInteger, Class, Date, String, Time, TimeStamp 152 // do not use defaults in the default configuration of ConvertUtilsBean 153 } 154 155 /** 156 * Register custom types of JDK like URI and Checkstyle specific classes to use with BeanUtils. 157 * None of these types should be found in the {@code java.lang} package. 158 * @param cub 159 * Instance of {@link ConvertUtilsBean} to register types with. 160 */ 161 private static void registerCustomTypes(ConvertUtilsBean cub) { 162 cub.register(new PatternConverter(), Pattern.class); 163 cub.register(new SeverityLevelConverter(), SeverityLevel.class); 164 cub.register(new ScopeConverter(), Scope.class); 165 cub.register(new UriConverter(), URI.class); 166 cub.register(new RelaxedAccessModifierArrayConverter(), AccessModifier[].class); 167 } 168 169 /** 170 * Implements the Configurable interface using bean introspection. 171 * 172 * <p>Subclasses are allowed to add behaviour. After the bean 173 * based setup has completed first the method 174 * {@link #finishLocalSetup finishLocalSetup} 175 * is called to allow completion of the bean's local setup, 176 * after that the method {@link #setupChild setupChild} 177 * is called for each {@link Configuration#getChildren child Configuration} 178 * of {@code configuration}. 179 * 180 * @see Configurable 181 */ 182 @Override 183 public final void configure(Configuration config) 184 throws CheckstyleException { 185 configuration = config; 186 187 final String[] attributes = config.getAttributeNames(); 188 189 for (final String key : attributes) { 190 final String value = config.getAttribute(key); 191 192 tryCopyProperty(config.getName(), key, value, true); 193 } 194 195 finishLocalSetup(); 196 197 final Configuration[] childConfigs = config.getChildren(); 198 for (final Configuration childConfig : childConfigs) { 199 setupChild(childConfig); 200 } 201 } 202 203 /** 204 * Recheck property and try to copy it. 205 * @param moduleName name of the module/class 206 * @param key key of value 207 * @param value value 208 * @param recheck whether to check for property existence before copy 209 * @throws CheckstyleException then property defined incorrectly 210 */ 211 private void tryCopyProperty(String moduleName, String key, Object value, boolean recheck) 212 throws CheckstyleException { 213 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 214 215 try { 216 if (recheck) { 217 // BeanUtilsBean.copyProperties silently ignores missing setters 218 // for key, so we have to go through great lengths here to 219 // figure out if the bean property really exists. 220 final PropertyDescriptor descriptor = 221 PropertyUtils.getPropertyDescriptor(this, key); 222 if (descriptor == null) { 223 final String message = String.format(Locale.ROOT, "Property '%s' in module %s " 224 + "does not exist, please check the documentation", key, moduleName); 225 throw new CheckstyleException(message); 226 } 227 } 228 // finally we can set the bean property 229 beanUtils.copyProperty(this, key, value); 230 } 231 catch (final InvocationTargetException | IllegalAccessException 232 | NoSuchMethodException ex) { 233 // There is no way to catch IllegalAccessException | NoSuchMethodException 234 // as we do PropertyUtils.getPropertyDescriptor before beanUtils.copyProperty 235 // so we have to join these exceptions with InvocationTargetException 236 // to satisfy UTs coverage 237 final String message = String.format(Locale.ROOT, 238 "Cannot set property '%s' to '%s' in module %s", key, value, moduleName); 239 throw new CheckstyleException(message, ex); 240 } 241 catch (final IllegalArgumentException | ConversionException ex) { 242 final String message = String.format(Locale.ROOT, "illegal value '%s' for property " 243 + "'%s' of module %s", value, key, moduleName); 244 throw new CheckstyleException(message, ex); 245 } 246 } 247 248 /** 249 * Implements the Contextualizable interface using bean introspection. 250 * @see Contextualizable 251 */ 252 @Override 253 public final void contextualize(Context context) 254 throws CheckstyleException { 255 final Collection<String> attributes = context.getAttributeNames(); 256 257 for (final String key : attributes) { 258 final Object value = context.get(key); 259 260 tryCopyProperty(getClass().getName(), key, value, false); 261 } 262 } 263 264 /** 265 * Returns the configuration that was used to configure this component. 266 * @return the configuration that was used to configure this component. 267 */ 268 protected final Configuration getConfiguration() { 269 return configuration; 270 } 271 272 /** 273 * Called by configure() for every child of this component's Configuration. 274 * <p> 275 * The default implementation throws {@link CheckstyleException} if 276 * {@code childConf} is {@code null} because it doesn't support children. It 277 * must be overridden to validate and support children that are wanted. 278 * </p> 279 * 280 * @param childConf a child of this component's Configuration 281 * @throws CheckstyleException if there is a configuration error. 282 * @see Configuration#getChildren 283 */ 284 protected void setupChild(Configuration childConf) 285 throws CheckstyleException { 286 if (childConf != null) { 287 throw new CheckstyleException(childConf.getName() + " is not allowed as a child in " 288 + configuration.getName() + ". Please review 'Parent Module' section " 289 + "for this Check in web documentation if Check is standard."); 290 } 291 } 292 293 /** A converter that converts strings to patterns. */ 294 private static class PatternConverter implements Converter { 295 296 @SuppressWarnings({"unchecked", "rawtypes"}) 297 @Override 298 public Object convert(Class type, Object value) { 299 return CommonUtils.createPattern(value.toString()); 300 } 301 302 } 303 304 /** A converter that converts strings to severity level. */ 305 private static class SeverityLevelConverter implements Converter { 306 307 @SuppressWarnings({"unchecked", "rawtypes"}) 308 @Override 309 public Object convert(Class type, Object value) { 310 return SeverityLevel.getInstance(value.toString()); 311 } 312 313 } 314 315 /** A converter that converts strings to scope. */ 316 private static class ScopeConverter implements Converter { 317 318 @SuppressWarnings({"unchecked", "rawtypes"}) 319 @Override 320 public Object convert(Class type, Object value) { 321 return Scope.getInstance(value.toString()); 322 } 323 324 } 325 326 /** A converter that converts strings to uri. */ 327 private static class UriConverter implements Converter { 328 329 @SuppressWarnings({"unchecked", "rawtypes"}) 330 @Override 331 public Object convert(Class type, Object value) { 332 final String url = value.toString(); 333 URI result = null; 334 335 if (!CommonUtils.isBlank(url)) { 336 try { 337 result = CommonUtils.getUriByFilename(url); 338 } 339 catch (CheckstyleException ex) { 340 throw new IllegalArgumentException(ex); 341 } 342 } 343 344 return result; 345 } 346 347 } 348 349 /** 350 * A converter that does not care whether the array elements contain String 351 * characters like '*' or '_'. The normal ArrayConverter class has problems 352 * with this characters. 353 */ 354 private static class RelaxedStringArrayConverter implements Converter { 355 356 @SuppressWarnings({"unchecked", "rawtypes"}) 357 @Override 358 public Object convert(Class type, Object value) { 359 // Convert to a String and trim it for the tokenizer. 360 final StringTokenizer tokenizer = new StringTokenizer( 361 value.toString().trim(), COMMA_SEPARATOR); 362 final List<String> result = new ArrayList<>(); 363 364 while (tokenizer.hasMoreTokens()) { 365 final String token = tokenizer.nextToken(); 366 result.add(token.trim()); 367 } 368 369 return result.toArray(new String[result.size()]); 370 } 371 372 } 373 374 /** 375 * A converter that converts strings to {@link AccessModifier}. 376 * This implementation does not care whether the array elements contain characters like '_'. 377 * The normal {@link ArrayConverter} class has problems with this character. 378 */ 379 private static class RelaxedAccessModifierArrayConverter implements Converter { 380 381 @SuppressWarnings({"unchecked", "rawtypes"}) 382 @Override 383 public Object convert(Class type, Object value) { 384 // Converts to a String and trims it for the tokenizer. 385 final StringTokenizer tokenizer = new StringTokenizer( 386 value.toString().trim(), COMMA_SEPARATOR); 387 final List<AccessModifier> result = new ArrayList<>(); 388 389 while (tokenizer.hasMoreTokens()) { 390 final String token = tokenizer.nextToken(); 391 result.add(AccessModifier.getInstance(token.trim())); 392 } 393 394 return result.toArray(new AccessModifier[result.size()]); 395 } 396 397 } 398 399}