diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java index c7b477ae94..081e86e55b 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java @@ -307,6 +307,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_BLANK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_EMPTY, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NORMALIZED, CharSequence.class ); + registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.ONE_OF, Object.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SCRIPT_ASSERT, Object.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.UNIQUE_ELEMENTS, Collection.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.URL, CharSequence.class ); diff --git a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java index 30d7c2dd4c..2007597bab 100644 --- a/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java +++ b/annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java @@ -85,6 +85,7 @@ public static class HibernateValidatorTypes { public static final String UUID = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".UUID"; public static final String NOT_BLANK = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NotBlank"; public static final String NOT_EMPTY = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".NotEmpty"; + public static final String ONE_OF = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".OneOf"; public static final String SCRIPT_ASSERT = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".ScriptAssert"; public static final String UNIQUE_ELEMENTS = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".UniqueElements"; public static final String URL = ORG_HIBERNATE_VALIDATOR_CONSTRAINTS + ".URL"; diff --git a/documentation/src/main/asciidoc/ch02.asciidoc b/documentation/src/main/asciidoc/ch02.asciidoc index 24f0987056..0ac6e8c8c5 100644 --- a/documentation/src/main/asciidoc/ch02.asciidoc +++ b/documentation/src/main/asciidoc/ch02.asciidoc @@ -711,6 +711,10 @@ With one exception also these constraints apply to the field/property level, onl Supported data types::: `CharSequence` Hibernate metadata impact::: None +`@OneOf`:: Checks that the annotated character sequence or object is one of the allowed values. The allowed values are defined using `allowedValues`, `allowedIntegers`, `allowedLongs`, `allowedFloats`, `allowedDoubles` or by specifying an `enumClass`. The validation occurs after converting the annotated object to a `String`. + Supported data types::: `CharSequence`, `Integer`, `Long`, `Float`, `Double`, `Enum` + Hibernate metadata impact::: None + `@Range(min=, max=)`:: Checks whether the annotated value lies between (inclusive) the specified minimum and maximum Supported data types::: `BigDecimal`, `BigInteger`, `CharSequence`, `byte`, `short`, `int`, `long` and the respective wrappers of the primitive types Hibernate metadata impact::: None diff --git a/engine/src/main/java/org/hibernate/validator/cfg/defs/OneOfDef.java b/engine/src/main/java/org/hibernate/validator/cfg/defs/OneOfDef.java new file mode 100644 index 0000000000..e247e4eacb --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/defs/OneOfDef.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.cfg.defs; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.constraints.OneOf; + +public class OneOfDef extends ConstraintDef { + + public OneOfDef() { + super( OneOf.class ); + } + + public OneOfDef enumClass(Class> enumClass) { + addParameter( "enumClass", enumClass ); + return this; + } + + public OneOfDef allowedIntegers(int[] allowedIntegers) { + addParameter( "allowedIntegers", allowedIntegers ); + return this; + } + + public OneOfDef allowedLongs(long[] allowedLongs) { + addParameter( "allowedLongs", allowedLongs ); + return this; + } + + public OneOfDef allowedFloats(float[] allowedFloats) { + addParameter( "allowedFloats", allowedFloats ); + return this; + } + + public OneOfDef allowedDoubles(double[] allowedDoubles) { + addParameter( "allowedDoubles", allowedDoubles ); + return this; + } + + public OneOfDef allowedValues(String[] allowedValues) { + addParameter( "allowedValues", allowedValues ); + return this; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/constraints/OneOf.java b/engine/src/main/java/org/hibernate/validator/constraints/OneOf.java new file mode 100644 index 0000000000..da9d8000dd --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/constraints/OneOf.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.constraints; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +/** + * Annotation to specify that a field or parameter must be one of a defined set of values. + * This can be enforced using string, integer, float, or double values, or by restricting the values to those + * within an enum type. + * + *

For string values, the annotation supports case-insensitive matching.

+ * + *

Usage example:

+ *
{@code
+ * @OneOf(allowedValues = {"ACTIVE", "INACTIVE"}, ignoreCase = true)
+ * private String status;
+ * }
+ * + *

The message attribute provides a customizable error message when validation fails. The groups and payload + * attributes allow the validation to be applied to specific validation groups or custom payload types.

+ * + *

You can use the following fields in the annotation:

+ * + * + * @author Yusuf Álàmù + * @since 9.0.0 + */ +@Documented +@Constraint(validatedBy = { }) +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +public @interface OneOf { + + String message() default "{org.hibernate.validator.constraints.OneOf.message}"; + + Class[] groups() default { }; + + Class[] payload() default { }; + + int[] allowedIntegers() default { }; + + long[] allowedLongs() default { }; + + float[] allowedFloats() default { }; + + double[] allowedDoubles() default { }; + + String[] allowedValues() default { }; + + Class> enumClass() default DefaultEnum.class; + + boolean ignoreCase() default false; + + enum DefaultEnum { + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/OneOfValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/OneOfValidator.java new file mode 100644 index 0000000000..00ff9096da --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/OneOfValidator.java @@ -0,0 +1,197 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.internal.constraintvalidators.hv; + + +import static java.util.Objects.nonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Stream; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import org.hibernate.validator.constraints.OneOf; + +/** + * Validator that checks if a given {@link CharSequence} matches one of the allowed values specified + * in the {@link OneOf} annotation. + * + *

This class implements the {@link ConstraintValidator} interface to perform the validation logic + * based on the configuration in the {@link OneOf} annotation.

+ * + * @author Yusuf Àlàmù Musa + * @version 1.0 + */ +public class OneOfValidator implements ConstraintValidator { + + private final List acceptedValues = new ArrayList<>(); + private boolean ignoreCase; + + /** + * Initializes the validator with the values specified in the {@link OneOf} annotation. + * + *

This method sets the case sensitivity flag and adds the allowed values (either enum constants or specified values) + * to the list of accepted values for validation.

+ * + * @param constraintAnnotation the {@link OneOf} annotation containing the configuration for validation. + */ + @Override + public void initialize(final OneOf constraintAnnotation) { + ignoreCase = constraintAnnotation.ignoreCase(); + + // If an enum class is specified, initialize accepted values from the enum constants + if ( constraintAnnotation.enumClass() != null ) { + final Enum[] enumConstants = constraintAnnotation.enumClass().getEnumConstants(); + initializeAcceptedValues( enumConstants ); + } + + // If specific allowed values are provided, initialize accepted values from them + if ( constraintAnnotation.allowedValues() != null ) { + initializeAcceptedValues( constraintAnnotation.allowedValues() ); + } + + // If specific allowed values are provided, initialize accepted values from them + if ( constraintAnnotation.allowedIntegers() != null ) { + final String[] acceptedValues = convertIntToStringArray( constraintAnnotation.allowedIntegers() ); + initializeAcceptedValues( acceptedValues ); + } + + // If specific allowed values are provided, initialize accepted values from them + if ( constraintAnnotation.allowedLongs() != null ) { + final String[] acceptedValues = convertLongToStringArray( constraintAnnotation.allowedLongs() ); + initializeAcceptedValues( acceptedValues ); + } + + // If specific allowed values are provided, initialize accepted values from them + if ( constraintAnnotation.allowedFloats() != null ) { + final String[] acceptedValues = convertFloatToStringArray( constraintAnnotation.allowedFloats() ); + initializeAcceptedValues( acceptedValues ); + } + + // If specific allowed values are provided, initialize accepted values from them + if ( constraintAnnotation.allowedDoubles() != null ) { + final String[] acceptedValues = convertDoubleToStringArray( constraintAnnotation.allowedDoubles() ); + initializeAcceptedValues( acceptedValues ); + } + } + + /** + * Validates the given value based on the accepted values. + * + *

If the value is not null, it checks whether the value matches any of the accepted values. + * If the value is null, it is considered valid.

+ * + * @param value the value to validate. + * @param context the validation context. + * @return {@code true} if the value is valid, {@code false} otherwise. + */ + @Override + public boolean isValid(final Object value, final ConstraintValidatorContext context) { + if ( nonNull( value ) ) { + return checkIfValueTheSame( value.toString() ); + } + return true; + } + + /** + * Checks if the provided value matches any of the accepted values. + * + *

If {@code ignoreCase} is false, the comparison is case-sensitive. + * If {@code ignoreCase} is true, the value is compared in lowercase.

+ * + * @param value the value to check. + * @return {@code true} if the value matches an accepted value, {@code false} otherwise. + */ + protected boolean checkIfValueTheSame(final String value) { + if ( !ignoreCase ) { + return acceptedValues.contains( value ); + } + + for ( final String acceptedValue : acceptedValues ) { + if ( acceptedValue.toLowerCase( Locale.ROOT ).equals( value.toLowerCase( Locale.ROOT ) ) ) { + return true; + } + } + + return false; + } + + /** + * Initializes and adds the names of the provided enum constants to the accepted values list. + * + * @param enumConstants the enum constants to be added, ignored if null. + */ + protected void initializeAcceptedValues(final Enum... enumConstants) { + if ( nonNull( enumConstants ) ) { + acceptedValues.addAll( Stream.of( enumConstants ).map( Enum::name ).toList() ); + } + } + + /** + * Initializes and adds the provided values to the accepted values list after trimming them. + * + * @param values the values to be added, ignored if null. + */ + protected void initializeAcceptedValues(final String... values) { + if ( nonNull( values ) ) { + acceptedValues.addAll( Stream.of( values ).map( String::trim ).toList() ); + } + } + + /** + * Converts an array of integers to an array of their corresponding string representations. + * + * @param allowedIntegers The array of integers to be converted. + * @return A new array of strings, where each element is the string representation of the corresponding integer from the input array. + */ + private static String[] convertIntToStringArray(final int[] allowedIntegers) { + return Arrays.stream( allowedIntegers ) + .mapToObj( String::valueOf ) // Convert each int to String + .toArray( String[]::new ); + } + + /** + * Converts an array of longs to an array of their corresponding string representations. + * + * @param allowedLongs The array of longs to be converted. + * @return A new array of strings, where each element is the string representation of the corresponding long from the input array. + */ + private static String[] convertLongToStringArray(final long[] allowedLongs) { + return Arrays.stream( allowedLongs ) + .mapToObj( String::valueOf ) // Convert each long to String + .toArray( String[]::new ); + } + + /** + * Converts an array of doubles to an array of their corresponding string representations. + * + * @param allowedDoubles The array of doubles to be converted. + * @return A new array of strings, where each element is the string representation of the corresponding double from the input array. + */ + private static String[] convertDoubleToStringArray(final double[] allowedDoubles) { + return Arrays.stream( allowedDoubles ) + .mapToObj( String::valueOf ) // Convert each double to String + .toArray( String[]::new ); + } + + /** + * Converts an array of floats to an array of their corresponding string representations. + * + * @param allowedFloats The array of floats to be converted. + * @return A new array of strings, where each element is the string representation of the corresponding float from the input array. + */ + private static String[] convertFloatToStringArray(final float[] allowedFloats) { + final String[] acceptedValues = new String[allowedFloats.length]; + for ( int i = 0; i < allowedFloats.length; i++ ) { + acceptedValues[i] = String.valueOf( allowedFloats[i] ); // Convert each float to String + } + return acceptedValues; + } + +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java index e2b4b17128..d3c681c7c5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/BuiltinConstraint.java @@ -83,7 +83,8 @@ enum BuiltinConstraint { ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_TIME_DURATION_MAX( "org.hibernate.validator.constraints.time.DurationMax" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_TIME_DURATION_MIN( "org.hibernate.validator.constraints.time.DurationMin" ), ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_UUID( "org.hibernate.validator.constraints.UUID" ), - ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_BITCOIN_ADDRESS( "org.hibernate.validator.constraints.BitcoinAddress" ); + ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_BITCOIN_ADDRESS( "org.hibernate.validator.constraints.BitcoinAddress" ), + ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_ONE_OF( "org.hibernate.validator.constraints.OneOf" ); private static final Map> CONSTRAINT_MAPPING; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java index 67c59f17f9..014fa3a813 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java @@ -41,6 +41,7 @@ import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD10_CHECK; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_MOD11_CHECK; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_NORMALIZED; +import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_ONE_OF; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PARAMETER_SCRIPT_ASSERT; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PL_NIP; import static org.hibernate.validator.internal.metadata.core.BuiltinConstraint.ORG_HIBERNATE_VALIDATOR_CONSTRAINTS_PL_PESEL; @@ -112,6 +113,7 @@ import org.hibernate.validator.constraints.Mod10Check; import org.hibernate.validator.constraints.Mod11Check; import org.hibernate.validator.constraints.Normalized; +import org.hibernate.validator.constraints.OneOf; import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.ScriptAssert; @@ -332,6 +334,7 @@ import org.hibernate.validator.internal.constraintvalidators.hv.Mod10CheckValidator; import org.hibernate.validator.internal.constraintvalidators.hv.Mod11CheckValidator; import org.hibernate.validator.internal.constraintvalidators.hv.NormalizedValidator; +import org.hibernate.validator.internal.constraintvalidators.hv.OneOfValidator; import org.hibernate.validator.internal.constraintvalidators.hv.ParameterScriptAssertValidator; import org.hibernate.validator.internal.constraintvalidators.hv.ScriptAssertValidator; import org.hibernate.validator.internal.constraintvalidators.hv.URLValidator; @@ -814,6 +817,9 @@ protected Map, List> violations = validator.validate( dto ); + assertNoViolations( violations, "The DTO should be valid" ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testInvalidDto() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + Validator validator = factory.getValidator(); + + OneOfDto dto = new OneOfDto(); + dto.setOneOfString( "invalid" ); + dto.setOneOfInteger( 400 ); + dto.setOneOfLong( 4000L ); + dto.setOneOfDouble( 4.5 ); + dto.setOneOfFloat( 0.4f ); + dto.setOneOfEnum( TestEnum.valueOf( "TWO" ) ); + dto.setOneOfIgnoreCaseString( "invalid" ); + + Set> violations = validator.validate( dto ); + assertFalse( violations.isEmpty(), "The DTO should be invalid" ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testProgrammaticDefinitionWithString() throws Exception { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( MyClassString.class ) + .field( "myValue" ) + .constraint( new OneOfDef().allowedValues( new String[] { "value1", "value2" } ) ); + + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new MyClassString( "value1" ) ); + assertNoViolations( constraintViolations ); + + constraintViolations = validator.validate( new MyClassString( "invalid" ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( OneOf.class ) + ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testProgrammaticDefinitionWithInt() throws Exception { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( MyClassInt.class ) + .field( "myValue" ) + .constraint( new OneOfDef().allowedIntegers( new int[] { 1, 2, 3 } ) ); + + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new MyClassInt( 1 ) ); + assertNoViolations( constraintViolations ); + + constraintViolations = validator.validate( new MyClassInt( 4 ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( OneOf.class ) + ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testProgrammaticDefinitionWithLong() throws Exception { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( MyClassLong.class ) + .field( "myValue" ) + .constraint( new OneOfDef().allowedLongs( new long[] { 100L, 200L, 300L } ) ); + + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new MyClassLong( 100L ) ); + assertNoViolations( constraintViolations ); + + constraintViolations = validator.validate( new MyClassLong( 400L ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( OneOf.class ) + ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testProgrammaticDefinitionWithFloat() throws Exception { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( MyClassFloat.class ) + .field( "myValue" ) + .constraint( new OneOfDef().allowedFloats( new float[] { 1.1f, 2.2f, 3.3f } ) ); + + + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new MyClassFloat( 1.1f ) ); + assertNoViolations( constraintViolations ); + + + constraintViolations = validator.validate( new MyClassFloat( 4.4f ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( OneOf.class ) + ); + } + + @Test + @TestForIssue(jiraKey = "HV-2073") + public void testProgrammaticDefinitionWithDouble() throws Exception { + HibernateValidatorConfiguration config = getConfiguration( HibernateValidator.class ); + ConstraintMapping mapping = config.createConstraintMapping(); + mapping.type( MyClassDouble.class ) + .field( "myValue" ) + .constraint( new OneOfDef().allowedDoubles( new double[] { 1.11, 2.22, 3.33 } ) ); + + config.addMapping( mapping ); + Validator validator = config.buildValidatorFactory().getValidator(); + + Set> constraintViolations = validator.validate( new MyClassDouble( 1.11 ) ); + assertNoViolations( constraintViolations ); + + + constraintViolations = validator.validate( new MyClassDouble( 4.44 ) ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( OneOf.class ) + ); + } + + private HibernateValidatorConfiguration getConfiguration(Class validatorClass) { + return (HibernateValidatorConfiguration) jakarta.validation.Validation.byProvider( validatorClass ).configure(); + } + + private static class MyClassString { + + @SuppressWarnings("unused") + private String myValue; + + public MyClassString(String myValue) { + this.myValue = myValue; + } + } + + private static class MyClassInt { + + @SuppressWarnings("unused") + private int myValue; + + public MyClassInt(int myValue) { + this.myValue = myValue; + } + } + + private static class MyClassLong { + + @SuppressWarnings("unused") + private long myValue; + + public MyClassLong(long myValue) { + this.myValue = myValue; + } + } + + private static class MyClassFloat { + + @SuppressWarnings("unused") + private float myValue; + + public MyClassFloat(float myValue) { + this.myValue = myValue; + } + } + + private static class MyClassDouble { + + @SuppressWarnings("unused") + private double myValue; + + public MyClassDouble(double myValue) { + this.myValue = myValue; + } + } + + private static class MyClassEnum { + + @SuppressWarnings("unused") + private TestEnum myValue; + + public MyClassEnum(TestEnum myValue) { + this.myValue = myValue; + } + } + + private enum TestEnum { + ONE, TWO, THREE + } + + private OneOf createOneOf(boolean ignoreCase, String[] allowedValues, int[] allowedInts, long[] allowedLongs, float[] allowedFloats, double[] allowDoubles, Class> enumClass) { + return new OneOf() { + @Override + public String[] allowedValues() { + return allowedValues; + } + + @Override + public int[] allowedIntegers() { + return allowedInts; + } + + @Override + public long[] allowedLongs() { + return allowedLongs; + } + + @Override + public float[] allowedFloats() { + return allowedFloats; + } + + @Override + public double[] allowedDoubles() { + return allowDoubles; + } + + @Override + public Class> enumClass() { + return enumClass; + } + + @Override + public boolean ignoreCase() { + return ignoreCase; + } + + @Override + public String message() { + return ""; + } + + @Override + public Class[] groups() { + return new Class[0]; + } + + @Override + public Class[] payload() { + return new Class[0]; + } + + @Override + public Class annotationType() { + return OneOf.class; + } + }; + } + + public static class OneOfDto { + + @OneOf(allowedValues = { "value1", "value2", "value3" }) + private String oneOfString; + + @OneOf(allowedIntegers = { 100, 200, 300 }) + private Integer oneOfInteger; + + @OneOf(allowedLongs = { 1000L, 2000L, 3000L }) + private Long oneOfLong; + + @OneOf(allowedDoubles = { 1.5, 2.5, 3.5 }) + private Double oneOfDouble; + + @OneOf(allowedFloats = { 0.1f, 0.2f, 0.3f }) + private Float oneOfFloat; + + @OneOf(enumClass = TestEnum.class) + private TestEnum oneOfEnum; + + @OneOf(allowedValues = { "enabled", "disabled" }, ignoreCase = true) + private String oneOfIgnoreCaseString; + + public String getOneOfString() { + return oneOfString; + } + + public void setOneOfString(String oneOfString) { + this.oneOfString = oneOfString; + } + + public Integer getOneOfInteger() { + return oneOfInteger; + } + + public void setOneOfInteger(Integer oneOfInteger) { + this.oneOfInteger = oneOfInteger; + } + + public Long getOneOfLong() { + return oneOfLong; + } + + public void setOneOfLong(Long oneOfLong) { + this.oneOfLong = oneOfLong; + } + + public Double getOneOfDouble() { + return oneOfDouble; + } + + public void setOneOfDouble(Double oneOfDouble) { + this.oneOfDouble = oneOfDouble; + } + + + public Float getOneOfFloat() { + return oneOfFloat; + } + + public void setOneOfFloat(Float oneOfFloat) { + this.oneOfFloat = oneOfFloat; + } + + public TestEnum getOneOfEnum() { + return oneOfEnum; + } + + public void setOneOfEnum(TestEnum oneOfEnum) { + this.oneOfEnum = oneOfEnum; + } + + public String getOneOfIgnoreCaseString() { + return oneOfIgnoreCaseString; + } + + public void setOneOfIgnoreCaseString(String oneOfIgnoreCaseString) { + this.oneOfIgnoreCaseString = oneOfIgnoreCaseString; + } + } +}