From b9a15b405a73e2501501dfa4d92e9878f73c66a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 29 Oct 2018 22:49:37 +0100 Subject: [PATCH] Move towards java7 compat --- .../rule/design/CyclomaticComplexityRule.java | 6 +- ...=> AbstractGenericPropertyDescriptor.java} | 20 +- .../AbstractMultiValuePropertyBuilder.java | 76 -------- .../newframework/AbstractPropertyBuilder.java | 176 +++++++++++++++++- .../AbstractSingleValuePropertyBuilder.java | 65 ------- ... GenericMultiValuePropertyDescriptor.java} | 21 +-- .../GenericPropertyDescriptor.java | 49 +++++ .../newframework/PropertyFactory.java | 35 ++-- .../newframework/PropertyValidator.java | 30 ++- .../SingleValuePropertyDescriptor.java | 54 ------ .../properties/newframework/Validators.java | 70 +++++++ 11 files changed, 361 insertions(+), 241 deletions(-) rename pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/{AbstractPropertyDescriptor.java => AbstractGenericPropertyDescriptor.java} (70%) delete mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractMultiValuePropertyBuilder.java delete mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractSingleValuePropertyBuilder.java rename pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/{MultiValuePropertyDescriptor.java => GenericMultiValuePropertyDescriptor.java} (68%) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericPropertyDescriptor.java delete mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/SingleValuePropertyDescriptor.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/Validators.java diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java index 08b6c567c5..e8d6b6a450 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java @@ -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.metrics.ResultOption; 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 { - private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR - = IntegerProperty.named("classReportLevel") + private static final PropertyDescriptor CLASS_LEVEL_DESCRIPTOR + = PropertyFactory.intProperty("classReportLevel") .desc("Total class complexity reporting threshold") .range(1, 200) .defaultValue(40) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractGenericPropertyDescriptor.java similarity index 70% rename from pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyDescriptor.java rename to pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractGenericPropertyDescriptor.java index 96e44ca073..947ad61145 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractGenericPropertyDescriptor.java @@ -14,8 +14,9 @@ import java.util.stream.Collectors; * @author Clément Fournier * @since 6.7.0 */ -public abstract class AbstractPropertyDescriptor implements PropertyDescriptor { +abstract class AbstractGenericPropertyDescriptor implements PropertyDescriptor { + protected final T defaultValue; private final String name; private final String description; private final float uiOrder; @@ -23,13 +24,16 @@ public abstract class AbstractPropertyDescriptor implements PropertyDescripto private final Class type; - protected AbstractPropertyDescriptor(String name, - String description, - float uiOrder, - Set> validators, Class type) { + protected AbstractGenericPropertyDescriptor(String name, + String description, + float uiOrder, + T defaultValue, + Set> validators, + Class type) { this.name = name; this.description = description; this.uiOrder = uiOrder; + this.defaultValue = defaultValue; this.validators = validators; this.type = type; } @@ -53,6 +57,12 @@ public abstract class AbstractPropertyDescriptor implements PropertyDescripto } + @Override + public T getDefaultValue() { + return defaultValue; + } + + @Override public List getErrorMessagesFor(T value) { return validators.stream() diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractMultiValuePropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractMultiValuePropertyBuilder.java deleted file mode 100644 index e94f87b448..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractMultiValuePropertyBuilder.java +++ /dev/null @@ -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>, V> extends AbstractPropertyBuilder> { - private final Set> componentValidators = new LinkedHashSet<>(); - private final Function parser; - private final Class type; - - private List defaultValues; - - - AbstractMultiValuePropertyBuilder(String name, Function parser, Class 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 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> build() { - return new MultiValuePropertyDescriptor<>( - name, - description, - uiOrder, - defaultValues, - validators, - componentValidators, - parser, - type - ); - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyBuilder.java index e31bdb73e3..4010bfaeec 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyBuilder.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractPropertyBuilder.java @@ -4,27 +4,40 @@ 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.Predicate; +import java.util.function.Function; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.properties.ValueParser; + /** + * Base class for generic property builders. + * + * @param Concrete type of this builder instance + * @param Type of values the property handles + * * @author Clément Fournier * @since 6.7.0 */ public abstract class AbstractPropertyBuilder, T> { private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z][\\w-]*"); - protected final Set> validators = new LinkedHashSet<>(); - protected String name; - protected String description; - protected float uiOrder = 0f; + private final Set> validators = new LinkedHashSet<>(); + private String name; + private String description; + private float uiOrder = 0f; + private T defaultValue; - public AbstractPropertyBuilder(String name) { + AbstractPropertyBuilder(String name) { + if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("Name must be provided"); } else if (!NAME_PATTERN.matcher(name).matches()) { @@ -34,6 +47,26 @@ public abstract class AbstractPropertyBuilder> getValidators() { + return validators; + } + + + String getDescription() { + return description; + } + + + float getUiOrder() { + return uiOrder; + } + + + T getDefaultValue() { + return defaultValue; + } + + /** * Specify the description of the property. * @@ -66,8 +99,22 @@ public abstract class AbstractPropertyBuilder pred, String errorMessage) { - validators.add(PropertyValidator.fromPredicate(pred, errorMessage)); + B addValidator(PropertyValidator validator) { + 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; } @@ -88,4 +135,117 @@ public abstract class AbstractPropertyBuilderThis 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 Concrete type of this builder instance + * @param Type of values the property handles + * + * @author Clément Fournier + * @since 6.7.0 + */ + public abstract static class GenericPropertyBuilder, T> extends AbstractPropertyBuilder { + + + private final ValueParser parser; + private final Class type; + + + GenericPropertyBuilder(String name, ValueParser parser, Class type) { + super(name); + this.parser = parser; + this.type = type; + } + + + @Override + public PropertyDescriptor build() { + return new GenericPropertyDescriptor<>( + getName(), + getDescription(), + getUiOrder(), + getDefaultValue(), + getValidators(), + parser, + type + ); + } + + + } + + /** + * Builder for a generic multi-value property. + * + *

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 Concrete type of this builder instance + * @param 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>, V> extends AbstractPropertyBuilder> { + private final Set> componentValidators = new LinkedHashSet<>(); + private final Function parser; + private final Class type; + + + AbstractGenericMultiPropertyBuilder(String name, Function parser, Class 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 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> build() { + return new GenericMultiValuePropertyDescriptor<>( + getName(), + getDescription(), + getUiOrder(), + getDefaultValue(), + getValidators(), + componentValidators, + parser, + type + ); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractSingleValuePropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractSingleValuePropertyBuilder.java deleted file mode 100644 index 5d90a8fbc3..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/AbstractSingleValuePropertyBuilder.java +++ /dev/null @@ -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, T> extends AbstractPropertyBuilder { - - - private final Function parser; - private final Class type; - - private T defaultValue; - - - AbstractSingleValuePropertyBuilder(String name, Function parser, Class type) { - super(name); - this.parser = parser; - this.type = type; - } - - - @Override - public PropertyDescriptor 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; - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/MultiValuePropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericMultiValuePropertyDescriptor.java similarity index 68% rename from pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/MultiValuePropertyDescriptor.java rename to pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericMultiValuePropertyDescriptor.java index 440f7ae762..f90db3cdc3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/MultiValuePropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericMultiValuePropertyDescriptor.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.properties.newframework; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -15,22 +16,20 @@ import java.util.stream.Collectors; * @author Clément Fournier * @since 6.7.0 */ -final class MultiValuePropertyDescriptor extends AbstractPropertyDescriptor> { +final class GenericMultiValuePropertyDescriptor extends AbstractGenericPropertyDescriptor> { - private final List defaultValue; private final Set> componentValidators; private final Function parser; - MultiValuePropertyDescriptor(String name, String description, float uiOrder, - List defaultValue, - Set>> listValidators, - Set> componentValidators, - Function parser, - Class type) { - super(name, description, uiOrder, listValidators, type); - this.defaultValue = defaultValue; + GenericMultiValuePropertyDescriptor(String name, String description, float uiOrder, + List defaultValue, + Set>> listValidators, + Set> componentValidators, + Function parser, + Class type) { + super(name, description, uiOrder, defaultValue, listValidators, type); this.componentValidators = componentValidators; this.parser = parser; } @@ -44,7 +43,7 @@ final class MultiValuePropertyDescriptor extends AbstractPropertyDescriptor getDefaultValue() { - return defaultValue; + return Collections.unmodifiableList(defaultValue); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericPropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericPropertyDescriptor.java new file mode 100644 index 0000000000..6037ccf4ce --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/GenericPropertyDescriptor.java @@ -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 extends AbstractGenericPropertyDescriptor { + + + private final ValueParser parser; + + + GenericPropertyDescriptor(String name, + String description, + float uiOrder, + T defaultValue, + Set> validators, + ValueParser parser, + Class type) { + super(name, description, uiOrder, defaultValue, validators, type); + this.parser = parser; + } + + + @Override + public boolean isMultiValue() { + return false; + } + + + @Override + public T valueFrom(List 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)); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyFactory.java index 0f41476d16..85b70d5fed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyFactory.java @@ -4,10 +4,11 @@ package net.sourceforge.pmd.properties.newframework; -import java.util.function.Function; - import org.apache.commons.lang3.EnumUtils; +import net.sourceforge.pmd.properties.ValueParser; +import net.sourceforge.pmd.properties.newframework.AbstractPropertyBuilder.GenericPropertyBuilder; + /** * @author Clément Fournier @@ -20,38 +21,42 @@ public final class PropertyFactory { } + private static > T enumConstantFromEnum(Class 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 intProperty(String name) { return new NumericPropertyBuilder<>(name, Integer::valueOf, Integer.class); } - // TODO need a way to document the possible values + public static > GenericPBuilder enumProperty(String name, Class enumClass) { - return new GenericPBuilder<>(name, s -> - 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); + return new GenericPBuilder<>(name, s -> enumConstantFromEnum(enumClass, s), enumClass); } // removes the other type parameter - public static class GenericPBuilder extends AbstractSingleValuePropertyBuilder, T> { + public static class GenericPBuilder extends GenericPropertyBuilder, T> { - GenericPBuilder(String name, Function parser, Class type) { + GenericPBuilder(String name, ValueParser parser, Class type) { super(name, parser, type); } } - public static class NumericPropertyBuilder extends AbstractSingleValuePropertyBuilder, N> { + public static class NumericPropertyBuilder extends GenericPropertyBuilder, N> { - NumericPropertyBuilder(String name, Function parser, Class type) { + NumericPropertyBuilder(String name, ValueParser parser, Class type) { super(name, parser, type); } - public NumericPropertyBuilder requirePositive() { - return addValidator(n -> n.intValue() > 0, "Expected a positive number"); + // TODO rename + public NumericPropertyBuilder range(N min, N max) { + return addValidator(Validators.rangeValidator(min, max)); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyValidator.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyValidator.java index f97bb3d8fb..e42349b1d5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyValidator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/PropertyValidator.java @@ -5,22 +5,42 @@ package net.sourceforge.pmd.properties.newframework; import java.util.Optional; -import java.util.function.Predicate; /** + * Validates the value of a property. + * + * @param Type of value to handle + * * @author Clément Fournier * @since 6.7.0 */ -@FunctionalInterface public interface PropertyValidator { + /** + * 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 validate(T value); - static PropertyValidator fromPredicate(Predicate 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 } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/SingleValuePropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/SingleValuePropertyDescriptor.java deleted file mode 100644 index 12eebe7acd..0000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/SingleValuePropertyDescriptor.java +++ /dev/null @@ -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 extends AbstractPropertyDescriptor { - - - private final T defaultValue; - private final Function parser; - - - SingleValuePropertyDescriptor(String name, String description, float uiOrder, - T defaultValue, - Set> validators, - Function parser, - Class 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 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)); - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/Validators.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/Validators.java new file mode 100644 index 0000000000..bfe5848a86 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/newframework/Validators.java @@ -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 PropertyValidator rangeValidator(T min, T max) { + return fromPredicate(new Predicate() { + @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 Type of value to validate + * + * @return A new validator + */ + private static PropertyValidator fromPredicate(Predicate pred, String constraintDescription) { + return new PropertyValidator() { + @Override + public Optional 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 { + boolean test(T t); + } +}