001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.api; 020 021import com.google.common.collect.Lists; 022import java.beans.PropertyDescriptor; 023import java.lang.reflect.InvocationTargetException; 024import java.util.Collection; 025import java.util.List; 026import java.util.StringTokenizer; 027import org.apache.commons.beanutils.BeanUtilsBean; 028import org.apache.commons.beanutils.ConversionException; 029import org.apache.commons.beanutils.ConvertUtilsBean; 030import org.apache.commons.beanutils.Converter; 031import org.apache.commons.beanutils.PropertyUtils; 032import org.apache.commons.beanutils.PropertyUtilsBean; 033import org.apache.commons.beanutils.converters.ArrayConverter; 034import org.apache.commons.beanutils.converters.BooleanConverter; 035import org.apache.commons.beanutils.converters.ByteConverter; 036import org.apache.commons.beanutils.converters.CharacterConverter; 037import org.apache.commons.beanutils.converters.DoubleConverter; 038import org.apache.commons.beanutils.converters.FloatConverter; 039import org.apache.commons.beanutils.converters.IntegerConverter; 040import org.apache.commons.beanutils.converters.LongConverter; 041import org.apache.commons.beanutils.converters.ShortConverter; 042 043/** 044 * A Java Bean that implements the component lifecycle interfaces by 045 * calling the bean's setters for all configuration attributes. 046 * @author lkuehne 047 */ 048public class AutomaticBean 049 implements Configurable, Contextualizable 050{ 051 /** the configuration of this bean */ 052 private Configuration configuration; 053 054 055 /** 056 * Creates a BeanUtilsBean that is configured to use 057 * type converters that throw a ConversionException 058 * instead of using the default value when something 059 * goes wrong. 060 * 061 * @return a configured BeanUtilsBean 062 */ 063 private static BeanUtilsBean createBeanUtilsBean() 064 { 065 final ConvertUtilsBean cub = new ConvertUtilsBean(); 066 // TODO: is there a smarter way to tell beanutils not to use defaults? 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 * 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</code>. 117 * 118 * @param configuration {@inheritDoc} 119 * @throws CheckstyleException {@inheritDoc} 120 * @see Configurable 121 */ 122 @Override 123 public final void configure(Configuration configuration) 124 throws CheckstyleException 125 { 126 this.configuration = configuration; 127 128 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 129 130 // TODO: debug log messages 131 final String[] attributes = configuration.getAttributeNames(); 132 133 for (final String key : attributes) { 134 final String value = configuration.getAttribute(key); 135 136 try { 137 // BeanUtilsBean.copyProperties silently ignores missing setters 138 // for key, so we have to go through great lengths here to 139 // figure out if the bean property really exists. 140 final PropertyDescriptor pd = 141 PropertyUtils.getPropertyDescriptor(this, key); 142 if ((pd == null) || (pd.getWriteMethod() == null)) { 143 throw new CheckstyleException( 144 "Property '" + key + "' in module " 145 + configuration.getName() 146 + " does not exist, please check the documentation"); 147 } 148 149 // finally we can set the bean property 150 beanUtils.copyProperty(this, key, value); 151 } 152 catch (final InvocationTargetException e) { 153 throw new CheckstyleException( 154 "Cannot set property '" + key + "' in module " 155 + configuration.getName() + " to '" + value 156 + "': " + e.getTargetException().getMessage(), e); 157 } 158 catch (final IllegalAccessException e) { 159 throw new CheckstyleException( 160 "cannot access " + key + " in " 161 + this.getClass().getName(), e); 162 } 163 catch (final NoSuchMethodException e) { 164 throw new CheckstyleException( 165 "cannot access " + key + " in " 166 + this.getClass().getName(), e); 167 } 168 catch (final IllegalArgumentException e) { 169 throw new CheckstyleException( 170 "illegal value '" + value + "' for property '" + key 171 + "' of module " + configuration.getName(), e); 172 } 173 catch (final ConversionException e) { 174 throw new CheckstyleException( 175 "illegal value '" + value + "' for property '" + key 176 + "' of module " + configuration.getName(), e); 177 } 178 179 } 180 181 finishLocalSetup(); 182 183 final Configuration[] childConfigs = configuration.getChildren(); 184 for (final Configuration childConfig : childConfigs) { 185 setupChild(childConfig); 186 } 187 } 188 189 /** 190 * Implements the Contextualizable interface using bean introspection. 191 * @param context {@inheritDoc} 192 * @throws CheckstyleException {@inheritDoc} 193 * @see Contextualizable 194 */ 195 @Override 196 public final void contextualize(Context context) 197 throws CheckstyleException 198 { 199 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 200 201 // TODO: debug log messages 202 final Collection<String> attributes = context.getAttributeNames(); 203 204 for (final String key : attributes) { 205 final Object value = context.get(key); 206 207 try { 208 beanUtils.copyProperty(this, key, value); 209 } 210 catch (final InvocationTargetException e) { 211 // TODO: log.debug("The bean " + this.getClass() 212 // + " is not interested in " + value) 213 throw new CheckstyleException("cannot set property " 214 + key + " to value " + value + " in bean " 215 + this.getClass().getName(), e); 216 } 217 catch (final IllegalAccessException e) { 218 throw new CheckstyleException( 219 "cannot access " + key + " in " 220 + this.getClass().getName(), e); 221 } 222 catch (final IllegalArgumentException e) { 223 throw new CheckstyleException( 224 "illegal value '" + value + "' for property '" + key 225 + "' of bean " + this.getClass().getName(), e); 226 } 227 catch (final ConversionException e) { 228 throw new CheckstyleException( 229 "illegal value '" + value + "' for property '" + key 230 + "' of bean " + this.getClass().getName(), e); 231 } 232 } 233 } 234 235 /** 236 * Returns the configuration that was used to configure this component. 237 * @return the configuration that was used to configure this component. 238 */ 239 protected final Configuration getConfiguration() 240 { 241 return configuration; 242 } 243 244 /** 245 * Provides a hook to finish the part of this component's setup that 246 * was not handled by the bean introspection. 247 * <p> 248 * The default implementation does nothing. 249 * </p> 250 * @throws CheckstyleException if there is a configuration error. 251 */ 252 protected void finishLocalSetup() throws CheckstyleException 253 { 254 } 255 256 /** 257 * Called by configure() for every child of this component's Configuration. 258 * <p> 259 * The default implementation does nothing. 260 * </p> 261 * @param childConf a child of this component's Configuration 262 * @throws CheckstyleException if there is a configuration error. 263 * @see Configuration#getChildren 264 */ 265 protected void setupChild(Configuration childConf) 266 throws CheckstyleException 267 { 268 } 269 270 /** 271 * A converter that does not care whether the array elements contain String 272 * characters like '*' or '_'. The normal ArrayConverter class has problems 273 * with this characters. 274 */ 275 private static class RelaxedStringArrayConverter implements Converter 276 { 277 /** {@inheritDoc} */ 278 @Override 279 public Object convert(@SuppressWarnings("rawtypes") Class type, 280 Object value) 281 { 282 if (null == type) { 283 throw new ConversionException("Cannot convert from null."); 284 } 285 286 // Convert to a String and trim it for the tokenizer. 287 final StringTokenizer st = new StringTokenizer( 288 value.toString().trim(), ","); 289 final List<String> result = Lists.newArrayList(); 290 291 while (st.hasMoreTokens()) { 292 final String token = st.nextToken(); 293 result.add(token.trim()); 294 } 295 296 return result.toArray(new String[result.size()]); 297 } 298 } 299}