blob: e7a35f69f96951a7aeb1703b9bfcd6ddcaf8ded1 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.api;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.beanutils.converters.ArrayConverter;
import org.apache.commons.beanutils.converters.BooleanConverter;
import org.apache.commons.beanutils.converters.ByteConverter;
import org.apache.commons.beanutils.converters.CharacterConverter;
import org.apache.commons.beanutils.converters.DoubleConverter;
import org.apache.commons.beanutils.converters.FloatConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.beanutils.converters.LongConverter;
import org.apache.commons.beanutils.converters.ShortConverter;
import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
/**
* A Java Bean that implements the component lifecycle interfaces by
* calling the bean's setters for all configuration attributes.
* @author lkuehne
*/
// -@cs[AbstractClassName] We can not brake compatibility with previous versions.
public abstract class AutomaticBean
implements Configurable, Contextualizable {
/**
* Enum to specify behaviour regarding ignored modules.
*/
public enum OutputStreamOptions {
/**
* Close stream in the end.
*/
CLOSE,
/**
* Do nothing in the end.
*/
NONE
}
/** Comma separator for StringTokenizer. */
private static final String COMMA_SEPARATOR = ",";
/** The configuration of this bean. */
private Configuration configuration;
/**
* Provides a hook to finish the part of this component's setup that
* was not handled by the bean introspection.
* <p>
* The default implementation does nothing.
* </p>
* @throws CheckstyleException if there is a configuration error.
*/
protected abstract void finishLocalSetup() throws CheckstyleException;
/**
* Creates a BeanUtilsBean that is configured to use
* type converters that throw a ConversionException
* instead of using the default value when something
* goes wrong.
*
* @return a configured BeanUtilsBean
*/
private static BeanUtilsBean createBeanUtilsBean() {
final ConvertUtilsBean cub = new ConvertUtilsBean();
registerIntegralTypes(cub);
registerCustomTypes(cub);
return new BeanUtilsBean(cub, new PropertyUtilsBean());
}
/**
* Register basic types of JDK like boolean, int, and String to use with BeanUtils. All these
* types are found in the {@code java.lang} package.
* @param cub
* Instance of {@link ConvertUtilsBean} to register types with.
*/
private static void registerIntegralTypes(ConvertUtilsBean cub) {
cub.register(new BooleanConverter(), Boolean.TYPE);
cub.register(new BooleanConverter(), Boolean.class);
cub.register(new ArrayConverter(
boolean[].class, new BooleanConverter()), boolean[].class);
cub.register(new ByteConverter(), Byte.TYPE);
cub.register(new ByteConverter(), Byte.class);
cub.register(new ArrayConverter(byte[].class, new ByteConverter()),
byte[].class);
cub.register(new CharacterConverter(), Character.TYPE);
cub.register(new CharacterConverter(), Character.class);
cub.register(new ArrayConverter(char[].class, new CharacterConverter()),
char[].class);
cub.register(new DoubleConverter(), Double.TYPE);
cub.register(new DoubleConverter(), Double.class);
cub.register(new ArrayConverter(double[].class, new DoubleConverter()),
double[].class);
cub.register(new FloatConverter(), Float.TYPE);
cub.register(new FloatConverter(), Float.class);
cub.register(new ArrayConverter(float[].class, new FloatConverter()),
float[].class);
cub.register(new IntegerConverter(), Integer.TYPE);
cub.register(new IntegerConverter(), Integer.class);
cub.register(new ArrayConverter(int[].class, new IntegerConverter()),
int[].class);
cub.register(new LongConverter(), Long.TYPE);
cub.register(new LongConverter(), Long.class);
cub.register(new ArrayConverter(long[].class, new LongConverter()),
long[].class);
cub.register(new ShortConverter(), Short.TYPE);
cub.register(new ShortConverter(), Short.class);
cub.register(new ArrayConverter(short[].class, new ShortConverter()),
short[].class);
cub.register(new RelaxedStringArrayConverter(), String[].class);
// BigDecimal, BigInteger, Class, Date, String, Time, TimeStamp
// do not use defaults in the default configuration of ConvertUtilsBean
}
/**
* Register custom types of JDK like URI and Checkstyle specific classes to use with BeanUtils.
* None of these types should be found in the {@code java.lang} package.
* @param cub
* Instance of {@link ConvertUtilsBean} to register types with.
*/
private static void registerCustomTypes(ConvertUtilsBean cub) {
cub.register(new PatternConverter(), Pattern.class);
cub.register(new SeverityLevelConverter(), SeverityLevel.class);
cub.register(new ScopeConverter(), Scope.class);
cub.register(new UriConverter(), URI.class);
cub.register(new RelaxedAccessModifierArrayConverter(), AccessModifier[].class);
}
/**
* Implements the Configurable interface using bean introspection.
*
* <p>Subclasses are allowed to add behaviour. After the bean
* based setup has completed first the method
* {@link #finishLocalSetup finishLocalSetup}
* is called to allow completion of the bean's local setup,
* after that the method {@link #setupChild setupChild}
* is called for each {@link Configuration#getChildren child Configuration}
* of {@code configuration}.
*
* @see Configurable
*/
@Override
public final void configure(Configuration config)
throws CheckstyleException {
configuration = config;
final String[] attributes = config.getAttributeNames();
for (final String key : attributes) {
final String value = config.getAttribute(key);
tryCopyProperty(config.getName(), key, value, true);
}
finishLocalSetup();
final Configuration[] childConfigs = config.getChildren();
for (final Configuration childConfig : childConfigs) {
setupChild(childConfig);
}
}
/**
* Recheck property and try to copy it.
* @param moduleName name of the module/class
* @param key key of value
* @param value value
* @param recheck whether to check for property existence before copy
* @throws CheckstyleException then property defined incorrectly
*/
private void tryCopyProperty(String moduleName, String key, Object value, boolean recheck)
throws CheckstyleException {
final BeanUtilsBean beanUtils = createBeanUtilsBean();
try {
if (recheck) {
// BeanUtilsBean.copyProperties silently ignores missing setters
// for key, so we have to go through great lengths here to
// figure out if the bean property really exists.
final PropertyDescriptor descriptor =
PropertyUtils.getPropertyDescriptor(this, key);
if (descriptor == null) {
final String message = String.format(Locale.ROOT, "Property '%s' in module %s "
+ "does not exist, please check the documentation", key, moduleName);
throw new CheckstyleException(message);
}
}
// finally we can set the bean property
beanUtils.copyProperty(this, key, value);
}
catch (final InvocationTargetException | IllegalAccessException
| NoSuchMethodException ex) {
// There is no way to catch IllegalAccessException | NoSuchMethodException
// as we do PropertyUtils.getPropertyDescriptor before beanUtils.copyProperty
// so we have to join these exceptions with InvocationTargetException
// to satisfy UTs coverage
final String message = String.format(Locale.ROOT,
"Cannot set property '%s' to '%s' in module %s", key, value, moduleName);
throw new CheckstyleException(message, ex);
}
catch (final IllegalArgumentException | ConversionException ex) {
final String message = String.format(Locale.ROOT, "illegal value '%s' for property "
+ "'%s' of module %s", value, key, moduleName);
throw new CheckstyleException(message, ex);
}
}
/**
* Implements the Contextualizable interface using bean introspection.
* @see Contextualizable
*/
@Override
public final void contextualize(Context context)
throws CheckstyleException {
final Collection<String> attributes = context.getAttributeNames();
for (final String key : attributes) {
final Object value = context.get(key);
tryCopyProperty(getClass().getName(), key, value, false);
}
}
/**
* Returns the configuration that was used to configure this component.
* @return the configuration that was used to configure this component.
*/
protected final Configuration getConfiguration() {
return configuration;
}
/**
* Called by configure() for every child of this component's Configuration.
* <p>
* The default implementation throws {@link CheckstyleException} if
* {@code childConf} is {@code null} because it doesn't support children. It
* must be overridden to validate and support children that are wanted.
* </p>
*
* @param childConf a child of this component's Configuration
* @throws CheckstyleException if there is a configuration error.
* @see Configuration#getChildren
*/
protected void setupChild(Configuration childConf)
throws CheckstyleException {
if (childConf != null) {
throw new CheckstyleException(childConf.getName() + " is not allowed as a child in "
+ configuration.getName() + ". Please review 'Parent Module' section "
+ "for this Check in web documentation if Check is standard.");
}
}
/** A converter that converts strings to patterns. */
private static class PatternConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
return CommonUtils.createPattern(value.toString());
}
}
/** A converter that converts strings to severity level. */
private static class SeverityLevelConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
return SeverityLevel.getInstance(value.toString());
}
}
/** A converter that converts strings to scope. */
private static class ScopeConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
return Scope.getInstance(value.toString());
}
}
/** A converter that converts strings to uri. */
private static class UriConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
final String url = value.toString();
URI result = null;
if (!CommonUtils.isBlank(url)) {
try {
result = CommonUtils.getUriByFilename(url);
}
catch (CheckstyleException ex) {
throw new IllegalArgumentException(ex);
}
}
return result;
}
}
/**
* A converter that does not care whether the array elements contain String
* characters like '*' or '_'. The normal ArrayConverter class has problems
* with this characters.
*/
private static class RelaxedStringArrayConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
// Convert to a String and trim it for the tokenizer.
final StringTokenizer tokenizer = new StringTokenizer(
value.toString().trim(), COMMA_SEPARATOR);
final List<String> result = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
final String token = tokenizer.nextToken();
result.add(token.trim());
}
return result.toArray(new String[result.size()]);
}
}
/**
* A converter that converts strings to {@link AccessModifier}.
* This implementation does not care whether the array elements contain characters like '_'.
* The normal {@link ArrayConverter} class has problems with this character.
*/
private static class RelaxedAccessModifierArrayConverter implements Converter {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object convert(Class type, Object value) {
// Converts to a String and trims it for the tokenizer.
final StringTokenizer tokenizer = new StringTokenizer(
value.toString().trim(), COMMA_SEPARATOR);
final List<AccessModifier> result = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
final String token = tokenizer.nextToken();
result.add(AccessModifier.getInstance(token.trim()));
}
return result.toArray(new AccessModifier[result.size()]);
}
}
}