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 mConfiguration; 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>aConfiguration</code>. 117 * 118 * @param aConfiguration {@inheritDoc} 119 * @throws CheckstyleException {@inheritDoc} 120 * @see Configurable 121 */ 122 public final void configure(Configuration aConfiguration) 123 throws CheckstyleException 124 { 125 mConfiguration = aConfiguration; 126 127 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 128 129 // TODO: debug log messages 130 final String[] attributes = aConfiguration.getAttributeNames(); 131 132 for (final String key : attributes) { 133 final String value = aConfiguration.getAttribute(key); 134 135 try { 136 // BeanUtilsBean.copyProperties silently ignores missing setters 137 // for key, so we have to go through great lengths here to 138 // figure out if the bean property really exists. 139 final PropertyDescriptor pd = 140 PropertyUtils.getPropertyDescriptor(this, key); 141 if ((pd == null) || (pd.getWriteMethod() == null)) { 142 throw new CheckstyleException( 143 "Property '" + key + "' in module " 144 + aConfiguration.getName() 145 + " does not exist, please check the documentation"); 146 } 147 148 // finally we can set the bean property 149 beanUtils.copyProperty(this, key, value); 150 } 151 catch (final InvocationTargetException e) { 152 throw new CheckstyleException( 153 "Cannot set property '" + key + "' in module " 154 + aConfiguration.getName() + " to '" + value 155 + "': " + e.getTargetException().getMessage(), e); 156 } 157 catch (final IllegalAccessException e) { 158 throw new CheckstyleException( 159 "cannot access " + key + " in " 160 + this.getClass().getName(), e); 161 } 162 catch (final NoSuchMethodException e) { 163 throw new CheckstyleException( 164 "cannot access " + key + " in " 165 + this.getClass().getName(), e); 166 } 167 catch (final IllegalArgumentException e) { 168 throw new CheckstyleException( 169 "illegal value '" + value + "' for property '" + key 170 + "' of module " + aConfiguration.getName(), e); 171 } 172 catch (final ConversionException e) { 173 throw new CheckstyleException( 174 "illegal value '" + value + "' for property '" + key 175 + "' of module " + aConfiguration.getName(), e); 176 } 177 178 } 179 180 finishLocalSetup(); 181 182 final Configuration[] childConfigs = aConfiguration.getChildren(); 183 for (final Configuration childConfig : childConfigs) { 184 setupChild(childConfig); 185 } 186 } 187 188 /** 189 * Implements the Contextualizable interface using bean introspection. 190 * @param aContext {@inheritDoc} 191 * @throws CheckstyleException {@inheritDoc} 192 * @see Contextualizable 193 */ 194 public final void contextualize(Context aContext) 195 throws CheckstyleException 196 { 197 final BeanUtilsBean beanUtils = createBeanUtilsBean(); 198 199 // TODO: debug log messages 200 final Collection<String> attributes = aContext.getAttributeNames(); 201 202 for (final String key : attributes) { 203 final Object value = aContext.get(key); 204 205 try { 206 beanUtils.copyProperty(this, key, value); 207 } 208 catch (final InvocationTargetException e) { 209 // TODO: log.debug("The bean " + this.getClass() 210 // + " is not interested in " + value) 211 throw new CheckstyleException("cannot set property " 212 + key + " to value " + value + " in bean " 213 + this.getClass().getName(), e); 214 } 215 catch (final IllegalAccessException e) { 216 throw new CheckstyleException( 217 "cannot access " + key + " in " 218 + this.getClass().getName(), e); 219 } 220 catch (final IllegalArgumentException e) { 221 throw new CheckstyleException( 222 "illegal value '" + value + "' for property '" + key 223 + "' of bean " + this.getClass().getName(), e); 224 } 225 catch (final ConversionException e) { 226 throw new CheckstyleException( 227 "illegal value '" + value + "' for property '" + key 228 + "' of bean " + this.getClass().getName(), e); 229 } 230 } 231 } 232 233 /** 234 * Returns the configuration that was used to configure this component. 235 * @return the configuration that was used to configure this component. 236 */ 237 protected final Configuration getConfiguration() 238 { 239 return mConfiguration; 240 } 241 242 /** 243 * Provides a hook to finish the part of this component's setup that 244 * was not handled by the bean introspection. 245 * <p> 246 * The default implementation does nothing. 247 * </p> 248 * @throws CheckstyleException if there is a configuration error. 249 */ 250 protected void finishLocalSetup() throws CheckstyleException 251 { 252 } 253 254 /** 255 * Called by configure() for every child of this component's Configuration. 256 * <p> 257 * The default implementation does nothing. 258 * </p> 259 * @param aChildConf a child of this component's Configuration 260 * @throws CheckstyleException if there is a configuration error. 261 * @see Configuration#getChildren 262 */ 263 protected void setupChild(Configuration aChildConf) 264 throws CheckstyleException 265 { 266 } 267 268 /** 269 * A converter that does not care whether the array elements contain String 270 * characters like '*' or '_'. The normal ArrayConverter class has problems 271 * with this characters. 272 */ 273 private static class RelaxedStringArrayConverter implements Converter 274 { 275 /** {@inheritDoc} */ 276 public Object convert(@SuppressWarnings("rawtypes") Class aType, 277 Object aValue) 278 { 279 if (null == aType) { 280 throw new ConversionException("Cannot convert from null."); 281 } 282 283 // Convert to a String and trim it for the tokenizer. 284 final StringTokenizer st = new StringTokenizer( 285 aValue.toString().trim(), ","); 286 final List<String> result = Lists.newArrayList(); 287 288 while (st.hasMoreTokens()) { 289 final String token = st.nextToken(); 290 result.add(token.trim()); 291 } 292 293 return result.toArray(new String[result.size()]); 294 } 295 } 296}