001: ////////////////////////////////////////////////////////////////////////////////
002: // checkstyle: Checks Java source code for adherence to a set of rules.
003: // Copyright (C) 2001-2007 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: ////////////////////////////////////////////////////////////////////////////////
019: package com.puppycrawl.tools.checkstyle.api;
020:
021: import java.beans.PropertyDescriptor;
022: import java.lang.reflect.InvocationTargetException;
023: import java.util.ArrayList;
024: import java.util.List;
025: import java.util.StringTokenizer;
026: import org.apache.commons.beanutils.BeanUtilsBean;
027: import org.apache.commons.beanutils.ConversionException;
028: import org.apache.commons.beanutils.ConvertUtilsBean;
029: import org.apache.commons.beanutils.PropertyUtils;
030: import org.apache.commons.beanutils.PropertyUtilsBean;
031: import org.apache.commons.beanutils.converters.AbstractArrayConverter;
032: import org.apache.commons.beanutils.converters.BooleanArrayConverter;
033: import org.apache.commons.beanutils.converters.BooleanConverter;
034: import org.apache.commons.beanutils.converters.ByteArrayConverter;
035: import org.apache.commons.beanutils.converters.ByteConverter;
036: import org.apache.commons.beanutils.converters.CharacterArrayConverter;
037: import org.apache.commons.beanutils.converters.CharacterConverter;
038: import org.apache.commons.beanutils.converters.DoubleArrayConverter;
039: import org.apache.commons.beanutils.converters.DoubleConverter;
040: import org.apache.commons.beanutils.converters.FloatArrayConverter;
041: import org.apache.commons.beanutils.converters.FloatConverter;
042: import org.apache.commons.beanutils.converters.IntegerArrayConverter;
043: import org.apache.commons.beanutils.converters.IntegerConverter;
044: import org.apache.commons.beanutils.converters.LongArrayConverter;
045: import org.apache.commons.beanutils.converters.LongConverter;
046: import org.apache.commons.beanutils.converters.ShortArrayConverter;
047: import org.apache.commons.beanutils.converters.ShortConverter;
048:
049: /**
050: * A Java Bean that implements the component lifecycle interfaces by
051: * calling the bean's setters for all configration attributes.
052: * @author lkuehne
053: */
054: public class AutomaticBean implements Configurable, Contextualizable {
055: /** the configuration of this bean */
056: private Configuration mConfiguration;
057:
058: /**
059: * Creates a BeanUtilsBean that is configured to use
060: * type converters that throw a ConversionException
061: * instead of using the default value when something
062: * goes wrong.
063: *
064: * @return a configured BeanUtilsBean
065: */
066: private static BeanUtilsBean createBeanUtilsBean() {
067: final ConvertUtilsBean cub = new ConvertUtilsBean();
068:
069: // TODO: is there a smarter way to tell beanutils not to use defaults?
070:
071: final boolean[] booleanArray = new boolean[0];
072: final byte[] byteArray = new byte[0];
073: final char[] charArray = new char[0];
074: final double[] doubleArray = new double[0];
075: final float[] floatArray = new float[0];
076: final int[] intArray = new int[0];
077: final long[] longArray = new long[0];
078: final short[] shortArray = new short[0];
079:
080: cub.register(new BooleanConverter(), Boolean.TYPE);
081: cub.register(new BooleanConverter(), Boolean.class);
082: cub.register(new BooleanArrayConverter(), booleanArray
083: .getClass());
084: cub.register(new ByteConverter(), Byte.TYPE);
085: cub.register(new ByteConverter(), Byte.class);
086: cub.register(new ByteArrayConverter(byteArray), byteArray
087: .getClass());
088: cub.register(new CharacterConverter(), Character.TYPE);
089: cub.register(new CharacterConverter(), Character.class);
090: cub.register(new CharacterArrayConverter(), charArray
091: .getClass());
092: cub.register(new DoubleConverter(), Double.TYPE);
093: cub.register(new DoubleConverter(), Double.class);
094: cub.register(new DoubleArrayConverter(doubleArray), doubleArray
095: .getClass());
096: cub.register(new FloatConverter(), Float.TYPE);
097: cub.register(new FloatConverter(), Float.class);
098: cub.register(new FloatArrayConverter(), floatArray.getClass());
099: cub.register(new IntegerConverter(), Integer.TYPE);
100: cub.register(new IntegerConverter(), Integer.class);
101: cub.register(new IntegerArrayConverter(), intArray.getClass());
102: cub.register(new LongConverter(), Long.TYPE);
103: cub.register(new LongConverter(), Long.class);
104: cub.register(new LongArrayConverter(), longArray.getClass());
105: cub.register(new ShortConverter(), Short.TYPE);
106: cub.register(new ShortConverter(), Short.class);
107: cub.register(new ShortArrayConverter(), shortArray.getClass());
108: // TODO: investigate:
109: // StringArrayConverter doesn't properly convert an array of tokens with
110: // elements containing an underscore, "_".
111: // Hacked a replacement class :(
112: // cub.register(new StringArrayConverter(),
113: // String[].class);
114: cub.register(new StrArrayConverter(), String[].class);
115: cub.register(new IntegerArrayConverter(), Integer[].class);
116:
117: // BigDecimal, BigInteger, Class, Date, String, Time, TimeStamp
118: // do not use defaults in the default configuration of ConvertUtilsBean
119:
120: return new BeanUtilsBean(cub, new PropertyUtilsBean());
121: }
122:
123: /**
124: * Implements the Configurable interface using bean introspection.
125: *
126: * Subclasses are allowed to add behaviour. After the bean
127: * based setup has completed first the method
128: * {@link #finishLocalSetup finishLocalSetup}
129: * is called to allow completion of the bean's local setup,
130: * after that the method {@link #setupChild setupChild}
131: * is called for each {@link Configuration#getChildren child Configuration}
132: * of <code>aConfiguration</code>.
133: *
134: * @param aConfiguration {@inheritDoc}
135: * @throws CheckstyleException {@inheritDoc}
136: * @see Configurable
137: */
138: public final void configure(Configuration aConfiguration)
139: throws CheckstyleException {
140: mConfiguration = aConfiguration;
141:
142: final BeanUtilsBean beanUtils = createBeanUtilsBean();
143:
144: // TODO: debug log messages
145: final String[] attributes = aConfiguration.getAttributeNames();
146:
147: for (int i = 0; i < attributes.length; i++) {
148: final String key = attributes[i];
149: final String value = aConfiguration.getAttribute(key);
150:
151: try {
152: // BeanUtilsBean.copyProperties silently ignores missing setters
153: // for key, so we have to go through great lengths here to
154: // figure out if the bean property really exists.
155: final PropertyDescriptor pd = PropertyUtils
156: .getPropertyDescriptor(this , key);
157: if ((pd == null) || (pd.getWriteMethod() == null)) {
158: throw new CheckstyleException(
159: "Property '"
160: + key
161: + "' 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: } catch (final InvocationTargetException e) {
169: throw new CheckstyleException("Cannot set property '"
170: + key + "' in module "
171: + aConfiguration.getName() + " to '" + value
172: + "': " + e.getTargetException().getMessage(),
173: e);
174: } catch (final IllegalAccessException e) {
175: throw new CheckstyleException("cannot access " + key
176: + " in " + this .getClass().getName(), e);
177: } catch (final NoSuchMethodException e) {
178: throw new CheckstyleException("cannot access " + key
179: + " in " + this .getClass().getName(), e);
180: } catch (final IllegalArgumentException e) {
181: throw new CheckstyleException("illegal value '" + value
182: + "' for property '" + key + "' of module "
183: + aConfiguration.getName(), e);
184: } catch (final ConversionException e) {
185: throw new CheckstyleException("illegal value '" + value
186: + "' for property '" + key + "' of module "
187: + aConfiguration.getName(), e);
188: }
189:
190: }
191:
192: finishLocalSetup();
193:
194: final Configuration[] childConfigs = aConfiguration
195: .getChildren();
196: for (int i = 0; i < childConfigs.length; i++) {
197: final Configuration childConfig = childConfigs[i];
198: setupChild(childConfig);
199: }
200: }
201:
202: /**
203: * Implements the Contextualizable interface using bean introspection.
204: * @param aContext {@inheritDoc}
205: * @throws CheckstyleException {@inheritDoc}
206: * @see Contextualizable
207: */
208: public final void contextualize(Context aContext)
209: throws CheckstyleException {
210: final BeanUtilsBean beanUtils = createBeanUtilsBean();
211:
212: // TODO: debug log messages
213: final String[] attributes = aContext.getAttributeNames();
214:
215: for (int i = 0; i < attributes.length; i++) {
216: final String key = attributes[i];
217: final Object value = aContext.get(key);
218:
219: try {
220: beanUtils.copyProperty(this , key, value);
221: } catch (final InvocationTargetException e) {
222: // TODO: log.debug("The bean " + this.getClass()
223: // + " is not interested in " + value)
224: throw new CheckstyleException("cannot set property "
225: + key + " to value " + value + " in bean "
226: + this .getClass().getName(), e);
227: } catch (final IllegalAccessException e) {
228: throw new CheckstyleException("cannot access " + key
229: + " in " + this .getClass().getName(), e);
230: } catch (final IllegalArgumentException e) {
231: throw new CheckstyleException("illegal value '" + value
232: + "' for property '" + key + "' of bean "
233: + this .getClass().getName(), e);
234: } catch (final ConversionException e) {
235: throw new CheckstyleException("illegal value '" + value
236: + "' for property '" + key + "' of bean "
237: + this .getClass().getName(), e);
238: }
239: }
240: }
241:
242: /**
243: * Returns the configuration that was used to configure this component.
244: * @return the configuration that was used to configure this component.
245: */
246: protected final Configuration getConfiguration() {
247: return mConfiguration;
248: }
249:
250: /**
251: * Provides a hook to finish the part of this compoent's setup that
252: * was not handled by the bean introspection.
253: * <p>
254: * The default implementation does nothing.
255: * </p>
256: * @throws CheckstyleException if there is a configuration error.
257: */
258: protected void finishLocalSetup() throws CheckstyleException {
259: }
260:
261: /**
262: * Called by configure() for every child of this component's Configuration.
263: * <p>
264: * The default implementation does nothing.
265: * </p>
266: * @param aChildConf a child of this component's Configuration
267: * @throws CheckstyleException if there is a configuration error.
268: * @see Configuration#getChildren
269: */
270: protected void setupChild(Configuration aChildConf)
271: throws CheckstyleException {
272: }
273: }
274:
275: /**
276: * <p>Standard Converter implementation that converts an incoming
277: * String into an array of String. On a conversion failure, returns
278: * a specified default value or throws a ConversionException depending
279: * on how this instance is constructed.</p>
280: *
281: * Hacked from
282: * http://cvs.apache.org/viewcvs/jakarta-commons/beanutils/src/java/org/apache/commons/beanutils/converters/StringArrayConverter.java
283: * because that implementation fails to convert array of tokens with elements
284: * containing an underscore, "_" :(
285: *
286: * @author Rick Giles
287: */
288:
289: final class StrArrayConverter extends AbstractArrayConverter {
290: /**
291: * <p>Model object for type comparisons.</p>
292: */
293: private static final String[] MODEL = new String[0];
294:
295: /**
296: * Creates a new StrArrayConverter object.
297: */
298: public StrArrayConverter() {
299: this .defaultValue = null;
300: this .useDefault = false;
301: }
302:
303: /**
304: * Create a onverter that will return the specified default value
305: * if a conversion error occurs.
306: *
307: * @param aDefaultValue The default value to be returned
308: */
309: public StrArrayConverter(Object aDefaultValue) {
310: this .defaultValue = aDefaultValue;
311: this .useDefault = true;
312: }
313:
314: /**
315: * Convert the specified input object into an output object of the
316: * specified type.
317: *
318: * @param aType Data type to which this value should be converted
319: * @param aValue The input value to be converted
320: *
321: * @return the converted object
322: *
323: * @throws ConversionException if conversion cannot be performed
324: * successfully
325: */
326: public Object convert(Class aType, Object aValue)
327: throws ConversionException {
328: // Deal with a null value
329: if (aValue == null) {
330: if (useDefault) {
331: return (defaultValue);
332: }
333: throw new ConversionException("No value specified");
334: }
335:
336: // Deal with the no-conversion-needed case
337: if (MODEL.getClass() == aValue.getClass()) {
338: return (aValue);
339: }
340:
341: // Parse the input value as a String into elements
342: // and convert to the appropriate type
343: try {
344: final List list = parseElements(aValue.toString());
345: final String[] results = new String[list.size()];
346:
347: for (int i = 0; i < results.length; i++) {
348: results[i] = (String) list.get(i);
349: }
350: return (results);
351: } catch (final Exception e) {
352: if (useDefault) {
353: return (defaultValue);
354: }
355: throw new ConversionException(aValue.toString(), e);
356: }
357: }
358:
359: /**
360: * <p>
361: * Parse an incoming String of the form similar to an array initializer in
362: * the Java language into a <code>List</code> individual Strings for each
363: * element, according to the following rules.
364: * </p>
365: * <ul>
366: * <li>The string must have matching '{' and '}' delimiters around a
367: * comma-delimited list of values.</li>
368: * <li>Whitespace before and after each element is stripped.
369: * <li>If an element is itself delimited by matching single or double
370: * quotes, the usual rules for interpreting a quoted String apply.</li>
371: * </ul>
372: *
373: * @param aValue
374: * String value to be parsed
375: * @return the list of Strings parsed from the array
376: * @throws NullPointerException
377: * if <code>svalue</code> is <code>null</code>
378: */
379: protected List parseElements(final String aValue)
380: throws NullPointerException {
381: // Validate the passed argument
382: if (aValue == null) {
383: throw new NullPointerException();
384: }
385:
386: // Trim any matching '{' and '}' delimiters
387: String str = aValue.trim();
388: if (str.startsWith("{") && str.endsWith("}")) {
389: str = str.substring(1, str.length() - 1);
390: }
391:
392: final StringTokenizer st = new StringTokenizer(str, ",");
393: final List retVal = new ArrayList();
394:
395: while (st.hasMoreTokens()) {
396: final String token = st.nextToken();
397: retVal.add(token.trim());
398: }
399:
400: return retVal;
401: }
402: }
|