Move towards java7 compat

This commit is contained in:
Clément Fournier
2018-10-29 22:49:37 +01:00
parent 972a991cb0
commit b9a15b405a
11 changed files with 361 additions and 241 deletions

View File

@ -16,6 +16,8 @@ import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
import net.sourceforge.pmd.lang.metrics.ResultOption; import net.sourceforge.pmd.lang.metrics.ResultOption;
import net.sourceforge.pmd.properties.IntegerProperty; import net.sourceforge.pmd.properties.IntegerProperty;
import net.sourceforge.pmd.properties.newframework.PropertyDescriptor;
import net.sourceforge.pmd.properties.newframework.PropertyFactory;
/** /**
@ -25,8 +27,8 @@ import net.sourceforge.pmd.properties.IntegerProperty;
*/ */
public class CyclomaticComplexityRule extends AbstractApexRule { public class CyclomaticComplexityRule extends AbstractApexRule {
private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR private static final PropertyDescriptor<Integer> CLASS_LEVEL_DESCRIPTOR
= IntegerProperty.named("classReportLevel") = PropertyFactory.intProperty("classReportLevel")
.desc("Total class complexity reporting threshold") .desc("Total class complexity reporting threshold")
.range(1, 200) .range(1, 200)
.defaultValue(40) .defaultValue(40)

View File

@ -14,8 +14,9 @@ import java.util.stream.Collectors;
* @author Clément Fournier * @author Clément Fournier
* @since 6.7.0 * @since 6.7.0
*/ */
public abstract class AbstractPropertyDescriptor<T> implements PropertyDescriptor<T> { abstract class AbstractGenericPropertyDescriptor<T> implements PropertyDescriptor<T> {
protected final T defaultValue;
private final String name; private final String name;
private final String description; private final String description;
private final float uiOrder; private final float uiOrder;
@ -23,13 +24,16 @@ public abstract class AbstractPropertyDescriptor<T> implements PropertyDescripto
private final Class<?> type; private final Class<?> type;
protected AbstractPropertyDescriptor(String name, protected AbstractGenericPropertyDescriptor(String name,
String description, String description,
float uiOrder, float uiOrder,
Set<PropertyValidator<T>> validators, Class<?> type) { T defaultValue,
Set<PropertyValidator<T>> validators,
Class<?> type) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.uiOrder = uiOrder; this.uiOrder = uiOrder;
this.defaultValue = defaultValue;
this.validators = validators; this.validators = validators;
this.type = type; this.type = type;
} }
@ -53,6 +57,12 @@ public abstract class AbstractPropertyDescriptor<T> implements PropertyDescripto
} }
@Override
public T getDefaultValue() {
return defaultValue;
}
@Override @Override
public List<String> getErrorMessagesFor(T value) { public List<String> getErrorMessagesFor(T value) {
return validators.stream() return validators.stream()

View File

@ -1,76 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.properties.newframework;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
/**
* @author Clément Fournier
* @since 6.7.0
*/
public abstract class AbstractMultiValuePropertyBuilder<B extends AbstractPropertyBuilder<B, List<V>>, V> extends AbstractPropertyBuilder<B, List<V>> {
private final Set<PropertyValidator<V>> componentValidators = new LinkedHashSet<>();
private final Function<String, V> parser;
private final Class<V> type;
private List<V> defaultValues;
AbstractMultiValuePropertyBuilder(String name, Function<String, V> parser, Class<V> type) {
super(name);
this.parser = parser;
this.type = type;
}
/**
* Specify a default value.
*
* @param val List of values
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValues(Collection<? extends V> val) {
this.defaultValues = new ArrayList<>(val);
return (B) this;
}
/**
* Specify default values.
*
* @param val List of values
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValues(V... val) {
this.defaultValues = Arrays.asList(val);
return (B) this;
}
@Override
public PropertyDescriptor<List<V>> build() {
return new MultiValuePropertyDescriptor<>(
name,
description,
uiOrder,
defaultValues,
validators,
componentValidators,
parser,
type
);
}
}

View File

@ -4,27 +4,40 @@
package net.sourceforge.pmd.properties.newframework; package net.sourceforge.pmd.properties.newframework;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.properties.ValueParser;
/** /**
* Base class for generic property builders.
*
* @param <B> Concrete type of this builder instance
* @param <T> Type of values the property handles
*
* @author Clément Fournier * @author Clément Fournier
* @since 6.7.0 * @since 6.7.0
*/ */
public abstract class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<B, T>, T> { public abstract class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<B, T>, T> {
private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z][\\w-]*"); private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z][\\w-]*");
protected final Set<PropertyValidator<T>> validators = new LinkedHashSet<>(); private final Set<PropertyValidator<T>> validators = new LinkedHashSet<>();
protected String name; private String name;
protected String description; private String description;
protected float uiOrder = 0f; private float uiOrder = 0f;
private T defaultValue;
public AbstractPropertyBuilder(String name) { AbstractPropertyBuilder(String name) {
if (StringUtils.isBlank(name)) { if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("Name must be provided"); throw new IllegalArgumentException("Name must be provided");
} else if (!NAME_PATTERN.matcher(name).matches()) { } else if (!NAME_PATTERN.matcher(name).matches()) {
@ -34,6 +47,26 @@ public abstract class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<
} }
Set<PropertyValidator<T>> getValidators() {
return validators;
}
String getDescription() {
return description;
}
float getUiOrder() {
return uiOrder;
}
T getDefaultValue() {
return defaultValue;
}
/** /**
* Specify the description of the property. * Specify the description of the property.
* *
@ -66,8 +99,22 @@ public abstract class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected B addValidator(Predicate<T> pred, String errorMessage) { B addValidator(PropertyValidator<T> validator) {
validators.add(PropertyValidator.fromPredicate(pred, errorMessage)); validators.add(validator);
return (B) this;
}
/**
* Specify a default value.
*
* @param val Value
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValue(T val) {
this.defaultValue = val;
return (B) this; return (B) this;
} }
@ -88,4 +135,117 @@ public abstract class AbstractPropertyBuilder<B extends AbstractPropertyBuilder<
public String getName() { public String getName() {
return name; return name;
} }
/**
* Builder for a generic single-value property.
*
* <p>This class is abstract because the B type parameter
* prevents it to be instantiated anyway. That type parameter
* is of use to more refined concrete subclasses.
*
* @param <B> Concrete type of this builder instance
* @param <T> Type of values the property handles
*
* @author Clément Fournier
* @since 6.7.0
*/
public abstract static class GenericPropertyBuilder<B extends GenericPropertyBuilder<B, T>, T> extends AbstractPropertyBuilder<B, T> {
private final ValueParser<T> parser;
private final Class<T> type;
GenericPropertyBuilder(String name, ValueParser<T> parser, Class<T> type) {
super(name);
this.parser = parser;
this.type = type;
}
@Override
public PropertyDescriptor<T> build() {
return new GenericPropertyDescriptor<>(
getName(),
getDescription(),
getUiOrder(),
getDefaultValue(),
getValidators(),
parser,
type
);
}
}
/**
* Builder for a generic multi-value property.
*
* <p>This class is abstract because the B type parameter
* prevents it to be instantiated anyway. That type parameter
* is of use to more refined concrete subclasses.
*
* @param <B> Concrete type of this builder instance
* @param <V> Type of values the property handles. This is the component type of the list
*
* @author Clément Fournier
* @since 6.7.0
*/
public abstract static class AbstractGenericMultiPropertyBuilder<B extends AbstractPropertyBuilder<B, List<V>>, V> extends AbstractPropertyBuilder<B, List<V>> {
private final Set<PropertyValidator<V>> componentValidators = new LinkedHashSet<>();
private final Function<String, V> parser;
private final Class<V> type;
AbstractGenericMultiPropertyBuilder(String name, Function<String, V> parser, Class<V> type) {
super(name);
this.parser = parser;
this.type = type;
}
/**
* Specify a default value.
*
* @param val List of values
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValues(Collection<? extends V> val) {
super.defaultValue(new ArrayList<>(val));
return (B) this;
}
/**
* Specify default values.
*
* @param val List of values
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValues(V... val) {
super.defaultValue(Arrays.asList(val));
return (B) this;
}
@Override
public PropertyDescriptor<List<V>> build() {
return new GenericMultiValuePropertyDescriptor<>(
getName(),
getDescription(),
getUiOrder(),
getDefaultValue(),
getValidators(),
componentValidators,
parser,
type
);
}
}
} }

View File

@ -1,65 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.properties.newframework;
import java.util.function.Function;
/**
* @author Clément Fournier
* @since 6.7.0
*/
public abstract class AbstractSingleValuePropertyBuilder<B extends AbstractSingleValuePropertyBuilder<B, T>, T> extends AbstractPropertyBuilder<B, T> {
private final Function<String, T> parser;
private final Class<T> type;
private T defaultValue;
AbstractSingleValuePropertyBuilder(String name, Function<String, T> parser, Class<T> type) {
super(name);
this.parser = parser;
this.type = type;
}
@Override
public PropertyDescriptor<T> build() {
return new SingleValuePropertyDescriptor<>(
name,
description,
uiOrder,
defaultValue,
validators,
parser,
type
);
}
/**
* Specify a default value.
*
* @param val Value
*
* @return The same builder
*/
@SuppressWarnings("unchecked")
public B defaultValue(T val) {
this.defaultValue = val;
return (B) this;
}
/**
* Returns the name of the property to be built.
*/
@Override
public String getName() {
return name;
}
}

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.properties.newframework; package net.sourceforge.pmd.properties.newframework;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -15,22 +16,20 @@ import java.util.stream.Collectors;
* @author Clément Fournier * @author Clément Fournier
* @since 6.7.0 * @since 6.7.0
*/ */
final class MultiValuePropertyDescriptor<V> extends AbstractPropertyDescriptor<List<V>> { final class GenericMultiValuePropertyDescriptor<V> extends AbstractGenericPropertyDescriptor<List<V>> {
private final List<V> defaultValue;
private final Set<PropertyValidator<V>> componentValidators; private final Set<PropertyValidator<V>> componentValidators;
private final Function<String, V> parser; private final Function<String, V> parser;
MultiValuePropertyDescriptor(String name, String description, float uiOrder, GenericMultiValuePropertyDescriptor(String name, String description, float uiOrder,
List<V> defaultValue, List<V> defaultValue,
Set<PropertyValidator<List<V>>> listValidators, Set<PropertyValidator<List<V>>> listValidators,
Set<PropertyValidator<V>> componentValidators, Set<PropertyValidator<V>> componentValidators,
Function<String, V> parser, Function<String, V> parser,
Class<V> type) { Class<V> type) {
super(name, description, uiOrder, listValidators, type); super(name, description, uiOrder, defaultValue, listValidators, type);
this.defaultValue = defaultValue;
this.componentValidators = componentValidators; this.componentValidators = componentValidators;
this.parser = parser; this.parser = parser;
} }
@ -44,7 +43,7 @@ final class MultiValuePropertyDescriptor<V> extends AbstractPropertyDescriptor<L
@Override @Override
public List<V> getDefaultValue() { public List<V> getDefaultValue() {
return defaultValue; return Collections.unmodifiableList(defaultValue);
} }

View File

@ -0,0 +1,49 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.properties.newframework;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.properties.ValueParser;
/**
* @author Clément Fournier
* @since 6.7.0
*/
final class GenericPropertyDescriptor<T> extends AbstractGenericPropertyDescriptor<T> {
private final ValueParser<T> parser;
GenericPropertyDescriptor(String name,
String description,
float uiOrder,
T defaultValue,
Set<PropertyValidator<T>> validators,
ValueParser<T> parser,
Class<T> type) {
super(name, description, uiOrder, defaultValue, validators, type);
this.parser = parser;
}
@Override
public boolean isMultiValue() {
return false;
}
@Override
public T valueFrom(List<String> valuesList) throws IllegalArgumentException {
if (valuesList.size() != 1) {
throw new IllegalArgumentException("This property can only handle a single value, but " + valuesList.size() + " was supplied");
}
return parser.valueOf(valuesList.get(0));
}
}

View File

@ -4,10 +4,11 @@
package net.sourceforge.pmd.properties.newframework; package net.sourceforge.pmd.properties.newframework;
import java.util.function.Function;
import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.EnumUtils;
import net.sourceforge.pmd.properties.ValueParser;
import net.sourceforge.pmd.properties.newframework.AbstractPropertyBuilder.GenericPropertyBuilder;
/** /**
* @author Clément Fournier * @author Clément Fournier
@ -20,38 +21,42 @@ public final class PropertyFactory {
} }
private static <T extends Enum<T>> T enumConstantFromEnum(Class<T> enumClass, String name) {
return EnumUtils.getEnumList(enumClass).stream()
.filter(e -> e.name().equalsIgnoreCase(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("The name '" + name + "' doesn't correspond to any constant in the enum '" + enumClass.getName() + "'"));
}
public static NumericPropertyBuilder<Integer> intProperty(String name) { public static NumericPropertyBuilder<Integer> intProperty(String name) {
return new NumericPropertyBuilder<>(name, Integer::valueOf, Integer.class); return new NumericPropertyBuilder<>(name, Integer::valueOf, Integer.class);
} }
// TODO need a way to document the possible values
public static <T extends Enum<T>> GenericPBuilder<T> enumProperty(String name, Class<T> enumClass) { public static <T extends Enum<T>> GenericPBuilder<T> enumProperty(String name, Class<T> enumClass) {
return new GenericPBuilder<>(name, s -> return new GenericPBuilder<>(name, s -> enumConstantFromEnum(enumClass, s), enumClass);
EnumUtils.getEnumList(enumClass).stream()
.filter(e -> e.name().equalsIgnoreCase(s))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("The name '" + s + "' doesn't correspond to any constant in the enum '" + enumClass.getName() + "'"))
, enumClass);
} }
// removes the other type parameter // removes the other type parameter
public static class GenericPBuilder<T> extends AbstractSingleValuePropertyBuilder<GenericPBuilder<T>, T> { public static class GenericPBuilder<T> extends GenericPropertyBuilder<GenericPBuilder<T>, T> {
GenericPBuilder(String name, Function<String, T> parser, Class<T> type) { GenericPBuilder(String name, ValueParser<T> parser, Class<T> type) {
super(name, parser, type); super(name, parser, type);
} }
} }
public static class NumericPropertyBuilder<N extends Number> extends AbstractSingleValuePropertyBuilder<NumericPropertyBuilder<N>, N> { public static class NumericPropertyBuilder<N extends Number> extends GenericPropertyBuilder<NumericPropertyBuilder<N>, N> {
NumericPropertyBuilder(String name, Function<String, N> parser, Class<N> type) { NumericPropertyBuilder(String name, ValueParser<N> parser, Class<N> type) {
super(name, parser, type); super(name, parser, type);
} }
public NumericPropertyBuilder<N> requirePositive() { // TODO rename
return addValidator(n -> n.intValue() > 0, "Expected a positive number"); public NumericPropertyBuilder<N> range(N min, N max) {
return addValidator(Validators.rangeValidator(min, max));
} }
} }

View File

@ -5,22 +5,42 @@
package net.sourceforge.pmd.properties.newframework; package net.sourceforge.pmd.properties.newframework;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
/** /**
* Validates the value of a property.
*
* @param <T> Type of value to handle
*
* @author Clément Fournier * @author Clément Fournier
* @since 6.7.0 * @since 6.7.0
*/ */
@FunctionalInterface
public interface PropertyValidator<T> { public interface PropertyValidator<T> {
/**
* Returns a diagnostic message if the value
* has a problem. Otherwise returns an empty
* optional.
*
* @param value The value to validate
*
* @return An optional diagnostic message
*/
Optional<String> validate(T value); Optional<String> validate(T value);
static <U> PropertyValidator<U> fromPredicate(Predicate<U> pred, String failureMessage) { /**
return u -> pred.test(u) ? Optional.empty() : Optional.of(failureMessage); * Returns a description of the constraint
} * imposed by this validator on the values.
* E.g. "The value should be positive", or
* "The value should be one of A | B | C."
*
* @return A description of the constraint
*/
String getConstraintDescription();
// TODO Java 8 move PropertyFactory#fromPredicate here
} }

View File

@ -1,54 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.properties.newframework;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
/**
* @author Clément Fournier
* @since 6.7.0
*/
final class SingleValuePropertyDescriptor<T> extends AbstractPropertyDescriptor<T> {
private final T defaultValue;
private final Function<String, T> parser;
SingleValuePropertyDescriptor(String name, String description, float uiOrder,
T defaultValue,
Set<PropertyValidator<T>> validators,
Function<String, T> parser,
Class<T> type) {
super(name, description, uiOrder, validators, type);
this.defaultValue = defaultValue;
this.parser = parser;
}
@Override
public boolean isMultiValue() {
return false;
}
@Override
public T getDefaultValue() {
return defaultValue;
}
@Override
public T valueFrom(List<String> valuesList) throws IllegalArgumentException {
if (valuesList.size() != 1) {
throw new IllegalArgumentException("This property can only handle a single value, but " + valuesList.size() + " was supplied");
}
return parser.apply(valuesList.get(0));
}
}

View File

@ -0,0 +1,70 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.properties.newframework;
import java.util.Optional;
/**
* Transitional class to make up for the absence of lambdas until we move to Java 8.
*
* @author Clément Fournier
* @since 6.7.0
*/
final class Validators {
private Validators() {
}
public static <T extends Number> PropertyValidator<T> rangeValidator(T min, T max) {
return fromPredicate(new Predicate<T>() {
@Override
public boolean test(T t) {
return min.doubleValue() < t.doubleValue() && max.doubleValue() > t.doubleValue();
}
},
"Should be between " + min + " and " + max
);
}
/**
* Builds a new validator from a predicate,
* and documentation messages.
*
* @param pred The predicate. If it returns
* false on a value, then the
* value is deemed to have a
* problem and the failureMessage
* will be transmitted.
* @param constraintDescription Description of the constraint,
* see {@link PropertyValidator#getConstraintDescription()}.
* @param <U> Type of value to validate
*
* @return A new validator
*/
private static <U> PropertyValidator<U> fromPredicate(Predicate<U> pred, String constraintDescription) {
return new PropertyValidator<U>() {
@Override
public Optional<String> validate(U value) {
return pred.test(value) ? Optional.empty() : Optional.of("Constraint violated on value '" + value + "' (" + constraintDescription + ")");
}
@Override
public String getConstraintDescription() {
return constraintDescription;
}
};
}
// Until we have Java 8
interface Predicate<T> {
boolean test(T t);
}
}