Source code: com/puppycrawl/tools/checkstyle/api/AutomaticBean.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2003 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 org.apache.commons.beanutils.BeanUtils;
22 import org.apache.commons.beanutils.ConversionException;
23 import org.apache.commons.beanutils.ConvertUtils;
24 import org.apache.commons.beanutils.PropertyUtils;
25 import org.apache.commons.beanutils.converters.AbstractArrayConverter;
26 import org.apache.commons.beanutils.converters.BooleanArrayConverter;
27 import org.apache.commons.beanutils.converters.BooleanConverter;
28 import org.apache.commons.beanutils.converters.ByteArrayConverter;
29 import org.apache.commons.beanutils.converters.ByteConverter;
30 import org.apache.commons.beanutils.converters.CharacterArrayConverter;
31 import org.apache.commons.beanutils.converters.CharacterConverter;
32 import org.apache.commons.beanutils.converters.DoubleArrayConverter;
33 import org.apache.commons.beanutils.converters.DoubleConverter;
34 import org.apache.commons.beanutils.converters.FloatArrayConverter;
35 import org.apache.commons.beanutils.converters.FloatConverter;
36 import org.apache.commons.beanutils.converters.IntegerArrayConverter;
37 import org.apache.commons.beanutils.converters.IntegerConverter;
38 import org.apache.commons.beanutils.converters.LongArrayConverter;
39 import org.apache.commons.beanutils.converters.LongConverter;
40 import org.apache.commons.beanutils.converters.ShortArrayConverter;
41 import org.apache.commons.beanutils.converters.ShortConverter;
42
43 import java.lang.reflect.InvocationTargetException;
44
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.StringTokenizer;
48 import java.beans.PropertyDescriptor;
49
50
51 /**
52 * A Java Bean that implements the component lifecycle interfaces by
53 * calling the bean's setters for all configration attributes.
54 * @author lkuehne
55 */
56 public class AutomaticBean
57 implements Configurable, Contextualizable
58 {
59 static {
60 initConverters();
61 }
62
63 /**
64 * Setup the jakarta-commons-beanutils type converters so they throw
65 * a ConversionException instead of using the default value.
66 */
67 private static void initConverters()
68 {
69 // TODO: is there a smarter way to tell beanutils not to use defaults?
70
71 // If any runtime environment like ANT or an IDE would use beanutils
72 // with different converters we would really be stuck here.
73 // Having to configure a static utility class in this way is really
74 // strange, it seems like a design problem in BeanUtils
75 final boolean[] booleanArray = new boolean[0];
76 final byte[] byteArray = new byte[0];
77 final char[] charArray = new char[0];
78 final double[] doubleArray = new double[0];
79 final float[] floatArray = new float[0];
80 final int[] intArray = new int[0];
81 final long[] longArray = new long[0];
82 final short[] shortArray = new short[0];
83
84 ConvertUtils.register(new BooleanConverter(), Boolean.TYPE);
85 ConvertUtils.register(new BooleanConverter(), Boolean.class);
86 ConvertUtils.register(
87 new BooleanArrayConverter(), booleanArray.getClass());
88 ConvertUtils.register(new ByteConverter(), Byte.TYPE);
89 ConvertUtils.register(new ByteConverter(), Byte.class);
90 ConvertUtils.register(
91 new ByteArrayConverter(byteArray), byteArray.getClass());
92 ConvertUtils.register(new CharacterConverter(), Character.TYPE);
93 ConvertUtils.register(new CharacterConverter(), Character.class);
94 ConvertUtils.register(
95 new CharacterArrayConverter(), charArray.getClass());
96 ConvertUtils.register(new DoubleConverter(), Double.TYPE);
97 ConvertUtils.register(new DoubleConverter(), Double.class);
98 ConvertUtils.register(
99 new DoubleArrayConverter(doubleArray), doubleArray.getClass());
100 ConvertUtils.register(new FloatConverter(), Float.TYPE);
101 ConvertUtils.register(new FloatConverter(), Float.class);
102 ConvertUtils.register(new FloatArrayConverter(), floatArray.getClass());
103 ConvertUtils.register(new IntegerConverter(), Integer.TYPE);
104 ConvertUtils.register(new IntegerConverter(), Integer.class);
105 ConvertUtils.register(new IntegerArrayConverter(), intArray.getClass());
106 ConvertUtils.register(new LongConverter(), Long.TYPE);
107 ConvertUtils.register(new LongConverter(), Long.class);
108 ConvertUtils.register(new LongArrayConverter(), longArray.getClass());
109 ConvertUtils.register(new ShortConverter(), Short.TYPE);
110 ConvertUtils.register(new ShortConverter(), Short.class);
111 ConvertUtils.register(new ShortArrayConverter(), shortArray.getClass());
112 // TODO: investigate:
113 // StringArrayConverter doesn't properly convert an array of tokens with
114 // elements containing an underscore, "_".
115 // Hacked a replacement class :(
116 // ConvertUtils.register(new StringArrayConverter(),
117 // String[].class);
118 ConvertUtils.register(new StrArrayConverter(), String[].class);
119 ConvertUtils.register(new IntegerArrayConverter(), Integer[].class);
120
121 // BigDecimal, BigInteger, Class, Date, String, Time, TimeStamp
122 // do not use defaults in the default configuration of ConvertUtils
123 }
124
125 /** the configuration of this bean */
126 private Configuration mConfiguration;
127
128 /**
129 * Implements the Configurable interface using bean introspection.
130 *
131 * Subclasses are allowed to add behaviour. After the bean
132 * based setup has completed first the method
133 * {@link #finishLocalSetup finishLocalSetup}
134 * is called to allow completion of the bean's local setup,
135 * after that the method {@link #setupChild setupChild}
136 * is called for each {@link Configuration#getChildren child Configuration}
137 * of <code>aConfiguration</code>.
138 *
139 * @see Configurable
140 */
141 public final void configure(Configuration aConfiguration)
142 throws CheckstyleException
143 {
144 mConfiguration = aConfiguration;
145
146 // TODO: debug log messages
147 final String[] attributes = aConfiguration.getAttributeNames();
148
149 for (int i = 0; i < attributes.length; i++) {
150 final String key = attributes[i];
151 final String value = aConfiguration.getAttribute(key);
152
153 try {
154 // BeanUtils.copyProperties silently ignores missing setters
155 // for key, so we have to go through great lengths here to
156 // figure out if the bean property really exists.
157 PropertyDescriptor pd =
158 PropertyUtils.getPropertyDescriptor(this, key);
159 if (pd == null || pd.getWriteMethod() == null) {
160 throw new CheckstyleException(
161 "Property '" + key + "' in module "
162 + aConfiguration.getName()
163 + " does not exist, please check the documentation");
164 }
165
166 // finally we can set the bean property
167 BeanUtils.copyProperty(this, key, value);
168 }
169 catch (InvocationTargetException e) {
170 throw new CheckstyleException(
171 "Cannot set property '" + key + "' in module "
172 + aConfiguration.getName() + " to '" + value
173 + "': " + e.getTargetException().getMessage(), e);
174 }
175 catch (IllegalAccessException e) {
176 throw new CheckstyleException(
177 "cannot access " + key + " in "
178 + this.getClass().getName(), e);
179 }
180 catch (NoSuchMethodException e) {
181 throw new CheckstyleException(
182 "cannot access " + key + " in "
183 + this.getClass().getName(), e);
184 }
185 catch (IllegalArgumentException e) {
186 throw new CheckstyleException(
187 "illegal value '" + value + "' for property '" + key
188 + "' of module " + aConfiguration.getName(), e);
189 }
190 catch (ConversionException e) {
191 throw new CheckstyleException(
192 "illegal value '" + value + "' for property '" + key
193 + "' of module " + aConfiguration.getName(), e);
194 }
195
196 }
197
198 finishLocalSetup();
199
200 Configuration[] childConfigs = aConfiguration.getChildren();
201 for (int i = 0; i < childConfigs.length; i++) {
202 final Configuration childConfig = childConfigs[i];
203 setupChild(childConfig);
204 }
205 }
206
207 /**
208 * Implements the Contextualizable interface using bean introspection.
209 * @see Contextualizable
210 */
211 public final void contextualize(Context aContext)
212 throws CheckstyleException
213 {
214 // TODO: debug log messages
215 final String[] attributes = aContext.getAttributeNames();
216
217 for (int i = 0; i < attributes.length; i++) {
218 final String key = attributes[i];
219 final Object value = aContext.get(key);
220
221 try {
222 BeanUtils.copyProperty(this, key, value);
223 }
224 catch (InvocationTargetException e) {
225 // TODO: log.debug("The bean " + this.getClass()
226 // + " is not interested in " + value)
227 throw new CheckstyleException("cannot set property "
228 + key + " to value " + value + " in bean "
229 + this.getClass().getName(), e);
230 }
231 catch (IllegalAccessException e) {
232 throw new CheckstyleException(
233 "cannot access " + key + " in "
234 + this.getClass().getName(), e);
235 }
236 catch (IllegalArgumentException e) {
237 throw new CheckstyleException(
238 "illegal value '" + value + "' for property '" + key
239 + "' of bean " + this.getClass().getName(), e);
240 }
241 catch (ConversionException e) {
242 throw new CheckstyleException(
243 "illegal value '" + value + "' for property '" + key
244 + "' of bean " + this.getClass().getName(), e);
245 }
246 }
247 }
248
249 /**
250 * Returns the configuration that was used to configure this component.
251 * @return the configuration that was used to configure this component.
252 */
253 protected final Configuration getConfiguration()
254 {
255 return mConfiguration;
256 }
257
258 /**
259 * Provides a hook to finish the part of this compoent's setup that
260 * was not handled by the bean introspection.
261 * <p>
262 * The default implementation does nothing.
263 * </p>
264 * @throws CheckstyleException if there is a configuration error.
265 */
266 protected void finishLocalSetup() throws CheckstyleException
267 {
268 }
269
270 /**
271 * Called by configure() for every child of this component's Configuration.
272 * <p>
273 * The default implementation does nothing.
274 * </p>
275 * @param aChildConf a child of this component's Configuration
276 * @throws CheckstyleException if there is a configuration error.
277 * @see Configuration#getChildren
278 */
279 protected void setupChild(Configuration aChildConf)
280 throws CheckstyleException
281 {
282 }
283 }
284
285 /**
286 * <p>Standard Converter implementation that converts an incoming
287 * String into an array of String. On a conversion failure, returns
288 * a specified default value or throws a ConversionException depending
289 * on how this instance is constructed.</p>
290 *
291 * Hacked from
292 * http://cvs.apache.org/viewcvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/converters/StringArrayConverter.java
293 * because that implementation fails to convert array of tokens with elements
294 * containing an underscore, "_" :(
295 *
296 * @author Rick Giles
297 */
298
299
300 final class StrArrayConverter extends AbstractArrayConverter
301 {
302 /**
303 * <p>Model object for type comparisons.</p>
304 */
305 private static String[] sModel = new String[0];
306
307 /**
308 * Creates a new StrArrayConverter object.
309 */
310 public StrArrayConverter()
311 {
312 this.defaultValue = null;
313 this.useDefault = false;
314 }
315
316 /**
317 * Create a onverter that will return the specified default value
318 * if a conversion error occurs.
319 *
320 * @param aDefaultValue The default value to be returned
321 */
322 public StrArrayConverter(Object aDefaultValue)
323 {
324 this.defaultValue = aDefaultValue;
325 this.useDefault = true;
326 }
327
328 /**
329 * Convert the specified input object into an output object of the
330 * specified type.
331 *
332 * @param aType Data type to which this value should be converted
333 * @param aValue The input value to be converted
334 *
335 * @return the converted object
336 *
337 * @throws ConversionException if conversion cannot be performed
338 * successfully
339 */
340 public Object convert(Class aType, Object aValue)
341 throws ConversionException
342 {
343 // Deal with a null value
344 if (aValue == null) {
345 if (useDefault) {
346 return (defaultValue);
347 }
348 else {
349 throw new ConversionException("No value specified");
350 }
351 }
352
353 // Deal with the no-conversion-needed case
354 if (sModel.getClass() == aValue.getClass()) {
355 return (aValue);
356 }
357
358 // Parse the input value as a String into elements
359 // and convert to the appropriate type
360 try {
361 final List list = parseElements(aValue.toString());
362 final String[] results = new String[list.size()];
363
364 for (int i = 0; i < results.length; i++) {
365 results[i] = (String) list.get(i);
366 }
367 return (results);
368 }
369 catch (Exception e) {
370 if (useDefault) {
371 return (defaultValue);
372 }
373 else {
374 throw new ConversionException(aValue.toString(), e);
375 }
376 }
377 }
378
379 /**
380 * <p>Parse an incoming String of the form similar to an array initializer
381 * in the Java language into a <code>List</code> individual Strings
382 * for each element, according to the following rules.</p>
383 * <ul>
384 * <li>The string must have matching '{' and '}' delimiters around
385 * a comma-delimited list of values.</li>
386 * <li>Whitespace before and after each element is stripped.
387 * <li>If an element is itself delimited by matching single or double
388 * quotes, the usual rules for interpreting a quoted String apply.</li>
389 * </ul>
390 *
391 * @param aValue String value to be parsed
392 * @return the list of Strings parsed from the array
393 * @throws NullPointerException if <code>svalue</code>
394 * is <code>null</code>
395 */
396 protected List parseElements(String aValue)
397 throws NullPointerException
398 {
399 // Validate the passed argument
400 if (aValue == null) {
401 throw new NullPointerException();
402 }
403
404 // Trim any matching '{' and '}' delimiters
405 aValue = aValue.trim();
406
407 if (aValue.startsWith("{") && aValue.endsWith("}")) {
408 aValue = aValue.substring(1, aValue.length() - 1);
409 }
410
411 final StringTokenizer st = new StringTokenizer(aValue, ",");
412 final List retVal = new ArrayList();
413
414 while (st.hasMoreTokens()) {
415 final String token = st.nextToken();
416 retVal.add(token.trim());
417 }
418
419 return retVal;
420 }
421 }