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.api; 021 022import java.beans.PropertyDescriptor; 023import java.lang.reflect.InvocationTargetException; 024import java.util.Collection; 025import java.util.List; 026import java.util.StringTokenizer; 027 028import org.apache.commons.beanutils.BeanUtilsBean; 029import org.apache.commons.beanutils.ConversionException; 030import org.apache.commons.beanutils.ConvertUtilsBean; 031import org.apache.commons.beanutils.Converter; 032import org.apache.commons.beanutils.PropertyUtils; 033import org.apache.commons.beanutils.PropertyUtilsBean; 034import org.apache.commons.beanutils.converters.ArrayConverter; 035import org.apache.commons.beanutils.converters.BooleanConverter; 036import org.apache.commons.beanutils.converters.ByteConverter; 037import org.apache.commons.beanutils.converters.CharacterConverter; 038import org.apache.commons.beanutils.converters.DoubleConverter; 039import org.apache.commons.beanutils.converters.FloatConverter; 040import org.apache.commons.beanutils.converters.IntegerConverter; 041import org.apache.commons.beanutils.converters.LongConverter; 042import org.apache.commons.beanutils.converters.ShortConverter; 043 044import com.google.common.collect.Lists; 045 046/** 047 * A Java Bean that implements the component lifecycle interfaces by 048 * calling the bean's setters for all configuration attributes. 049 * @author lkuehne 050 */ 051public class AutomaticBean 052 implements Configurable, Contextualizable { 053 /** The configuration of this bean. */ 054 private Configuration configuration; 055 056 /** 057 * Creates a BeanUtilsBean that is configured to use 058 * type converters that throw a ConversionException 059 * instead of using the default value when something 060 * goes wrong. 061 * 062 * @return a configured BeanUtilsBean 063 */ 064 private static BeanUtilsBean createBeanUtilsBean() { 065 final ConvertUtilsBean cub = new ConvertUtilsBean(); 066 067 cub.register(new BooleanConverter(), Boolean.TYPE); 068 cub.register(new BooleanConverter(), Boolean.class); 069 cub.register(new ArrayConverter( 070 boolean[].class, new BooleanConverter()), boolean[].class); 071 cub.register(new ByteConverter(), Byte.TYPE); 072 cub.register(new ByteConverter(), Byte.class); 073 cub.register(new ArrayConverter(byte[].class, new ByteConverter()), 074 byte[].class); 075 cub.register(new CharacterConverter(), Character.TYPE); 076 cub.register(new CharacterConverter(), Character.class); 077 cub.register(new ArrayConverter(char[].class, new CharacterConverter()), 078 char[].class); 079 cub.register(new DoubleConverter(), Double.TYPE); 080 cub.register(new DoubleConverter(), Double.class); 081 cub.register(new ArrayConverter(double[].class, new DoubleConverter()), 082 double[].class); 083 cub.register(new FloatConverter(), Float.TYPE); 084 cub.register(new FloatConverter(), Float.class); 085 cub.register(new ArrayConverter(float[].class, new FloatConverter()), 086 float[].class); 087 cub.register(new IntegerConverter(), Integer.TYPE); 088 cub.register(new IntegerConverter(), Integer.class); 089 cub.register(new ArrayConverter(int[].class, new IntegerConverter()), 090 int[].class); 091 cub.register(new LongConverter(), Long.TYPE); 092 cub.register(new LongConverter(), Long.class); 093 cub.register(new ArrayConverter(long[].class, new LongConverter()), 094 long[].class); 095 cub.register(new ShortConverter(), Short.TYPE); 096 cub.register(new ShortConverter(), Short.class); 097 cub.register(new ArrayConverter(short[].class, new ShortConverter()), 098 short[].class); 099 cub.register(new RelaxedStringArrayConverter(), String[].class); 100 101 // BigDecimal, BigInteger, Class, Date, String, Time, TimeStamp 102 // do not use defaults in the default configuration of ConvertUtilsBean 103 104 return new BeanUtilsBean(cub, new PropertyUtilsBean()); 105 } 106 107 /** 108 * Implements the Configurable interface using bean introspection. 109 * 110 * <p>Subclasses are allowed to add behaviour. After the bean 111 * based setup has completed first the method 112 * {@link #finishLocalSetup finishLocalSetup} 113 * is called to allow completion of the bean's local setup, 114 * after that the method {@link #setupChild setupChild} 115 * is called for each {@link Configuration#getChildren child Configuration} 116 * of {@code configuration}. 117 * 118 * @see Configurable 119 */ 120 @Override 121 public final void configure(Configuration config) 122 throws CheckstyleException { 123 configuration = config; 124 125 final String[] attributes = config.getAttributeNames(); 126 127 for (final String key : attributes) { 128 final String value = config.getAttribute(key); 129 130 tryCopyProperty(config.getName(), key, value, true); 131 } 132 133 finishLocalSetup(); 134 135 final Configuration[] childConfigs = config.getChildren(); 136 for (final Configuration childConfig : childConfigs) { 137 setupChild(childConfig); 138 } 139 } 140 141 /** 142 * Recheck property and try to copy it. 143 * @param moduleName name of the module/class 144 * @param key key of value 145 * @param value value 146 * @param recheck whether to check for property existence before copy 147 * @throws CheckstyleException then property defined incorrectly 148 */ 149 private void tryCopyProperty(String moduleName, String key, Object value, boolean recheck) 150 throws CheckstyleException { 151 152 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 153 154 try { 155 if (recheck) { 156 // BeanUtilsBean.copyProperties silently ignores missing setters 157 // for key, so we have to go through great lengths here to 158 // figure out if the bean property really exists. 159 final PropertyDescriptor pd = 160 PropertyUtils.getPropertyDescriptor(this, key); 161 if (pd == null) { 162 final String message = String.format("Property '%s' in module %s does not " 163 + "exist, please check the documentation", key, moduleName); 164 throw new CheckstyleException(message); 165 } 166 } 167 // finally we can set the bean property 168 beanUtils.copyProperty(this, key, value); 169 } 170 catch (final InvocationTargetException | IllegalAccessException 171 | NoSuchMethodException e) { 172 // There is no way to catch IllegalAccessException | NoSuchMethodException 173 // as we do PropertyUtils.getPropertyDescriptor before beanUtils.copyProperty 174 // so we have to join these exceptions with InvocationTargetException 175 // to satisfy UTs coverage 176 final String message = String.format("Cannot set property '%s' to '%s' in module %s", 177 key, value, moduleName); 178 throw new CheckstyleException(message, e); 179 } 180 catch (final IllegalArgumentException | ConversionException e) { 181 final String message = String.format("illegal value '%s' for property '%s' of " 182 + "module %s", value, key, moduleName); 183 throw new CheckstyleException(message, e); 184 } 185 } 186 187 /** 188 * Implements the Contextualizable interface using bean introspection. 189 * @see Contextualizable 190 */ 191 @Override 192 public final void contextualize(Context context) 193 throws CheckstyleException { 194 195 final Collection<String> attributes = context.getAttributeNames(); 196 197 for (final String key : attributes) { 198 final Object value = context.get(key); 199 200 tryCopyProperty(getClass().getName(), key, value, false); 201 } 202 } 203 204 /** 205 * Returns the configuration that was used to configure this component. 206 * @return the configuration that was used to configure this component. 207 */ 208 protected final Configuration getConfiguration() { 209 return configuration; 210 } 211 212 /** 213 * Provides a hook to finish the part of this component's setup that 214 * was not handled by the bean introspection. 215 * <p> 216 * The default implementation does nothing. 217 * </p> 218 * @throws CheckstyleException if there is a configuration error. 219 */ 220 protected void finishLocalSetup() throws CheckstyleException { 221 // No code by default, should be overridden only by demand at subclasses 222 } 223 224 /** 225 * Called by configure() for every child of this component's Configuration. 226 * <p> 227 * The default implementation does nothing. 228 * </p> 229 * @param childConf a child of this component's Configuration 230 * @throws CheckstyleException if there is a configuration error. 231 * @see Configuration#getChildren 232 */ 233 protected void setupChild(Configuration childConf) 234 throws CheckstyleException { 235 // No code by default, should be overridden only by demand at subclasses 236 } 237 238 /** 239 * A converter that does not care whether the array elements contain String 240 * characters like '*' or '_'. The normal ArrayConverter class has problems 241 * with this characters. 242 */ 243 private static class RelaxedStringArrayConverter implements Converter { 244 @SuppressWarnings({"unchecked", "rawtypes"}) 245 @Override 246 public Object convert(Class type, Object value) { 247 // Convert to a String and trim it for the tokenizer. 248 final StringTokenizer st = new StringTokenizer( 249 value.toString().trim(), ","); 250 final List<String> result = Lists.newArrayList(); 251 252 while (st.hasMoreTokens()) { 253 final String token = st.nextToken(); 254 result.add(token.trim()); 255 } 256 257 return result.toArray(new String[result.size()]); 258 } 259 } 260}