View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  Oliver Burn
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  package com.puppycrawl.tools.checkstyle.api;
20  
21  import com.google.common.collect.Lists;
22  import java.beans.PropertyDescriptor;
23  import java.lang.reflect.InvocationTargetException;
24  import java.util.Collection;
25  import java.util.List;
26  import java.util.StringTokenizer;
27  import org.apache.commons.beanutils.BeanUtilsBean;
28  import org.apache.commons.beanutils.ConversionException;
29  import org.apache.commons.beanutils.ConvertUtilsBean;
30  import org.apache.commons.beanutils.Converter;
31  import org.apache.commons.beanutils.PropertyUtils;
32  import org.apache.commons.beanutils.PropertyUtilsBean;
33  import org.apache.commons.beanutils.converters.ArrayConverter;
34  import org.apache.commons.beanutils.converters.BooleanConverter;
35  import org.apache.commons.beanutils.converters.ByteConverter;
36  import org.apache.commons.beanutils.converters.CharacterConverter;
37  import org.apache.commons.beanutils.converters.DoubleConverter;
38  import org.apache.commons.beanutils.converters.FloatConverter;
39  import org.apache.commons.beanutils.converters.IntegerConverter;
40  import org.apache.commons.beanutils.converters.LongConverter;
41  import org.apache.commons.beanutils.converters.ShortConverter;
42  
43  /**
44   * A Java Bean that implements the component lifecycle interfaces by
45   * calling the bean's setters for all configuration attributes.
46   * @author lkuehne
47   */
48  public class AutomaticBean
49      implements Configurable, Contextualizable
50  {
51      /** the configuration of this bean */
52      private Configuration configuration;
53  
54  
55      /**
56       * Creates a BeanUtilsBean that is configured to use
57       * type converters that throw a ConversionException
58       * instead of using the default value when something
59       * goes wrong.
60       *
61       * @return a configured BeanUtilsBean
62       */
63      private static BeanUtilsBean createBeanUtilsBean()
64      {
65          final ConvertUtilsBean cub = new ConvertUtilsBean();
66          // TODO: is there a smarter way to tell beanutils not to use defaults?
67          cub.register(new BooleanConverter(), Boolean.TYPE);
68          cub.register(new BooleanConverter(), Boolean.class);
69          cub.register(new ArrayConverter(
70              boolean[].class, new BooleanConverter()), boolean[].class);
71          cub.register(new ByteConverter(), Byte.TYPE);
72          cub.register(new ByteConverter(), Byte.class);
73          cub.register(new ArrayConverter(byte[].class, new ByteConverter()),
74              byte[].class);
75          cub.register(new CharacterConverter(), Character.TYPE);
76          cub.register(new CharacterConverter(), Character.class);
77          cub.register(new ArrayConverter(char[].class, new CharacterConverter()),
78              char[].class);
79          cub.register(new DoubleConverter(), Double.TYPE);
80          cub.register(new DoubleConverter(), Double.class);
81          cub.register(new ArrayConverter(double[].class, new DoubleConverter()),
82              double[].class);
83          cub.register(new FloatConverter(), Float.TYPE);
84          cub.register(new FloatConverter(), Float.class);
85          cub.register(new ArrayConverter(float[].class, new FloatConverter()),
86              float[].class);
87          cub.register(new IntegerConverter(), Integer.TYPE);
88          cub.register(new IntegerConverter(), Integer.class);
89          cub.register(new ArrayConverter(int[].class, new IntegerConverter()),
90              int[].class);
91          cub.register(new LongConverter(), Long.TYPE);
92          cub.register(new LongConverter(), Long.class);
93          cub.register(new ArrayConverter(long[].class, new LongConverter()),
94              long[].class);
95          cub.register(new ShortConverter(), Short.TYPE);
96          cub.register(new ShortConverter(), Short.class);
97          cub.register(new ArrayConverter(short[].class, new ShortConverter()),
98              short[].class);
99          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 }