Update junits for Validation 76/116576/4
authorJim Hahn <jrh3@att.com>
Mon, 4 Jan 2021 17:11:32 +0000 (12:11 -0500)
committerJim Hahn <jrh3@att.com>
Mon, 4 Jan 2021 22:32:12 +0000 (17:32 -0500)
The Validation code was previously refactored.  Added/updated junits
correspondingly.

Issue-ID: POLICY-2648
Change-Id: I570c0ec692ecfcb6e69ada45f7997f6e63735ea0
Signed-off-by: Jim Hahn <jrh3@att.com>
common-parameters/pom.xml
common-parameters/src/main/java/org/onap/policy/common/parameters/BeanValidator.java
common-parameters/src/main/java/org/onap/policy/common/parameters/FieldValidator.java
common-parameters/src/main/java/org/onap/policy/common/parameters/ItemValidator.java
common-parameters/src/main/java/org/onap/policy/common/parameters/ValueValidator.java
common-parameters/src/test/java/org/onap/policy/common/parameters/TestBeanValidator.java
common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java [new file with mode: 0644]
common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java [new file with mode: 0644]
common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java [new file with mode: 0644]
common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java [new file with mode: 0644]
common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java [new file with mode: 0644]

index c0efcf7..02765df 100644 (file)
@@ -1,7 +1,7 @@
 <!--
   ============LICENSE_START=======================================================
    Copyright (C) 2018 Ericsson. All rights reserved.
-   Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+   Modifications Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
index 3f5abcc..51b1140 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,11 +34,14 @@ import org.onap.policy.common.parameters.annotations.NotBlank;
 import org.onap.policy.common.parameters.annotations.NotNull;
 import org.onap.policy.common.parameters.annotations.Pattern;
 import org.onap.policy.common.parameters.annotations.Valid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Bean validator, supporting the parameter annotations.
  */
 public class BeanValidator {
+    public static final Logger logger = LoggerFactory.getLogger(BeanValidator.class);
 
     /**
      * Validates top level fields within an object. For each annotated field, it retrieves
@@ -147,18 +150,18 @@ public class BeanValidator {
      */
     public boolean verRegex(BeanValidationResult result, String fieldName, Pattern annot, Object value) {
         try {
-            if (value instanceof String && !com.google.re2j.Pattern.matches(annot.regexp(), value.toString())) {
-                ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value),
-                                ValidationStatus.INVALID, "does not match regular expression " + annot.regexp());
-                result.addResult(result2);
-                return false;
+            if (value instanceof String && com.google.re2j.Pattern.matches(annot.regexp(), value.toString())) {
+                return true;
             }
+
         } catch (RuntimeException e) {
-            // TODO log at trace level
-            return true;
+            logger.warn("validation error for regular expression: {}", annot.regexp(), e);
         }
 
-        return true;
+        ObjectValidationResult result2 = new ObjectValidationResult(fieldName, xlate(value), ValidationStatus.INVALID,
+                        "does not match regular expression " + annot.regexp());
+        result.addResult(result2);
+        return false;
     }
 
     /**
@@ -352,7 +355,7 @@ public class BeanValidator {
      */
     public boolean verMap(BeanValidationResult result, String fieldName, EntryValidator entryValidator, Object value) {
 
-        if (!(value instanceof Map)) {
+        if (!(value instanceof Map) || entryValidator.isEmpty()) {
             return true;
         }
 
index e762dc0..249185c 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -157,6 +157,7 @@ public class FieldValidator extends ValueValidator {
      * @return the annotation, or {@code null} if neither the field nor the class has the
      *         desired annotation
      */
+    @Override
     public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
 
         // field annotation takes precedence over class annotation
@@ -178,9 +179,9 @@ public class FieldValidator extends ValueValidator {
      */
     private Method getAccessor(Class<?> clazz, String fieldName) {
         String capname = StringUtils.capitalize(fieldName);
-        Method accessor = getMethod(clazz, "get" + capname);
-        if (accessor != null) {
-            return accessor;
+        Method accessor2 = getMethod(clazz, "get" + capname);
+        if (accessor2 != null) {
+            return accessor2;
         }
 
         return getMethod(clazz, "is" + capname);
index d0c027c..07efebb 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@ public class ItemValidator extends ValueValidator {
      * @return the annotation, or {@code null} if the {@link #annotationContainer} does
      *         not contain the desired annotation
      */
+    @Override
     public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
         try {
             for (Method meth : annotationContainer.getClass().getDeclaredMethods()) {
@@ -80,7 +81,10 @@ public class ItemValidator extends ValueValidator {
         return null;
     }
 
-    private <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method)
+    /**
+     * Note: this is only marked "protected" so it can be overridden for junit testing.
+     */
+    protected <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method)
                     throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 
         Class<?> ret = method.getReturnType();
@@ -101,7 +105,9 @@ public class ItemValidator extends ValueValidator {
             return null;
         }
 
-        // TODO log if there's more than one item
+        if (arrobj.length > 1) {
+            throw new IllegalArgumentException("extra item annotations of type: " + annotClass.getName());
+        }
 
         return arrobj[0];
     }
index 9095bfd..6a641a1 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,12 +36,6 @@ import org.onap.policy.common.parameters.annotations.NotNull;
  */
 public class ValueValidator {
 
-    /**
-     * {@code True} if there is a field-level annotation, {@code false} otherwise.
-     */
-    @Setter(AccessLevel.PROTECTED)
-    private boolean fieldIsAnnotated = false;
-
     /**
      * {@code True} if the value is allowed to be {@code null}, {@code false} otherwise.
      * Subclasses are expected to set this, typically based on the validation annotations
index f1e468b..5d53926 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.policy.common.parameters;
 
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
-import lombok.AccessLevel;
 import lombok.Getter;
 import org.junit.Before;
 import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Entries;
+import org.onap.policy.common.parameters.annotations.Items;
 import org.onap.policy.common.parameters.annotations.Max;
 import org.onap.policy.common.parameters.annotations.Min;
 import org.onap.policy.common.parameters.annotations.NotBlank;
 import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.common.parameters.annotations.Pattern;
+import org.onap.policy.common.parameters.annotations.Valid;
 
 public class TestBeanValidator {
-    private static final String GET_MSG = "\"get\"";
-    private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
     private static final String TOP = "top";
     private static final String STR_FIELD = "strValue";
     private static final String INT_FIELD = "intValue";
     private static final String NUM_FIELD = "numValue";
-    private static final String BOOL_FIELD = "boolValue";
     private static final String STRING_VALUE = "string value";
     private static final int INT_VALUE = 20;
 
@@ -113,115 +113,6 @@ public class TestBeanValidator {
         derived.intValue = INT_VALUE;
     }
 
-    @Test
-    public void testValidateField() {
-        /*
-         * Note: nested classes contain fields like "$this", thus the check for "$" in the
-         * variable name is already covered by the other tests.
-         */
-
-        /*
-         * Class with no annotations.
-         */
-        class NoAnnotations {
-            @SuppressWarnings("unused")
-            String strValue;
-        }
-
-        NoAnnotations noAnnot = new NoAnnotations();
-        noAnnot.strValue = null;
-        assertTrue(validator.validateTop(TOP, noAnnot).isValid());
-
-        /*
-         * Class containing a static field with an annotation.
-         */
-        AnnotFieldStatic annotFieldStatic = new AnnotFieldStatic();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, annotFieldStatic))
-                        .withMessageContaining(STR_FIELD).withMessageContaining("static");
-
-        /*
-         * Class containing a static field, with an annotation at the class level.
-         */
-        AnnotClassStatic annotClassStatic = new AnnotClassStatic();
-        assertTrue(validator.validateTop(TOP, annotClassStatic).isValid());
-
-        /*
-         * Class with no getter method, with field-level annotation.
-         */
-        class NoGetter {
-            @NotNull
-            String strValue;
-        }
-
-        NoGetter noGetter = new NoGetter();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, noGetter))
-                        .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG);
-
-        /*
-         * Class with no getter method, with class-level annotation.
-         */
-        @NotNull
-        class ClassNoGetter {
-            @SuppressWarnings("unused")
-            String strValue;
-        }
-
-        ClassNoGetter classNoGetter = new ClassNoGetter();
-        assertTrue(validator.validateTop(TOP, classNoGetter).isValid());
-
-        /*
-         * Class with "blank", but no "null" check. Value is null.
-         */
-        class NoNullCheck {
-            @NotBlank
-            @Getter
-            String strValue;
-        }
-
-        NoNullCheck noNullCheck = new NoNullCheck();
-        assertTrue(validator.validateTop(TOP, noNullCheck).isValid());
-
-        /*
-         * Class with conflicting minimum and maximum, where the value doesn't satisfy
-         * either of them. This should only generate one result, rather than one for each
-         * check. Note: the "max" check occurs before the "min" check, so that's the one
-         * we expect in the result.
-         */
-        class MinAndMax {
-            @Getter
-            @Min(200)
-            @Max(100)
-            Integer intValue;
-        }
-
-        MinAndMax minAndMax = new MinAndMax();
-        minAndMax.intValue = 150;
-        BeanValidationResult result = validator.validateTop(INT_FIELD, minAndMax);
-        assertFalse(result.isValid());
-        assertInvalid("testValidateField", result, INT_FIELD, "maximum");
-        assertFalse(result.getResult().contains("minimum"));
-    }
-
-    @Test
-    public void testGetValue() {
-        /*
-         * Class where the getter throws an exception.
-         */
-        class GetExcept {
-            @NotNull
-            String strValue;
-
-            @SuppressWarnings("unused")
-            public String getStrValue() {
-                throw EXPECTED_EXCEPTION;
-            }
-        }
-
-        GetExcept getExcept = new GetExcept();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, getExcept))
-                        .withMessageContaining(STR_FIELD).withMessageContaining("accessor threw");
-    }
-
     @Test
     public void testVerNotNull() {
         class NotNullCheck {
@@ -280,6 +171,58 @@ public class TestBeanValidator {
         assertTrue(validator.validateTop(TOP, notBlankInt).isValid());
     }
 
+    @Test
+    public void testVerRegex() {
+        class RegexCheck {
+            @Getter
+            @Pattern(regexp = "[a-f]*")
+            String strValue;
+        }
+
+        RegexCheck regexCheck = new RegexCheck();
+
+        // does not match
+        regexCheck.strValue = "xyz";
+        assertInvalid("testVerRegex", validator.validateTop(TOP, regexCheck), STR_FIELD,
+                        "does not match regular expression [a-f]");
+
+        // matches
+        regexCheck.strValue = "abcabc";
+        assertTrue(validator.validateTop(TOP, regexCheck).isValid());
+
+        // invalid regex
+        class InvalidRegexCheck {
+            @Getter
+            @Pattern(regexp = "[a-f")
+            String strValue;
+        }
+
+        InvalidRegexCheck invalidRegexCheck = new InvalidRegexCheck();
+
+        // does not match
+        invalidRegexCheck.strValue = "abc";
+        assertInvalid("testVerRegex", validator.validateTop(TOP, invalidRegexCheck), STR_FIELD,
+                        "does not match regular expression [a-f");
+
+        // matches
+        regexCheck.strValue = "abcabc";
+        assertTrue(validator.validateTop(TOP, regexCheck).isValid());
+
+        /*
+         * Class with "regex" annotation on an integer.
+         */
+        class RegexInt {
+            @Getter
+            @Pattern(regexp = "[a-f]*")
+            int intValue;
+        }
+
+        RegexInt regexInt = new RegexInt();
+        regexInt.intValue = 0;
+        assertInvalid("testVerRegex", validator.validateTop(TOP, regexInt), INT_FIELD,
+                        "does not match regular expression [a-f]");
+    }
+
     @Test
     public void testVerMax() {
         /*
@@ -468,184 +411,132 @@ public class TestBeanValidator {
         assertTrue(validator.validateTop(TOP, atomIntField).isValid());
     }
 
-    private <T> void assertNumeric(String testName, T object, Consumer<Integer> setter, String fieldName,
-                    String expectedText, int inside, int edge, int outside) {
-        setter.accept(inside);
-        assertTrue(validator.validateTop(TOP, object).isValid());
-
-        // on the edge
-        setter.accept(edge);
-        assertTrue(validator.validateTop(TOP, object).isValid());
+    @Test
+    public void testVerCascade() {
+        class Item {
+            @Getter
+            @NotNull
+            Integer intValue;
+        }
 
-        // invalid
-        setter.accept(outside);
-        assertInvalid("testVerNotNull", validator.validateTop(TOP, object), fieldName, expectedText);
-    }
+        @Getter
+        class Container {
+            @Valid
+            Item checked;
 
-    @Test
-    public void testGetAccessor() {
-        /*
-         * Class with "get" method has been tested through-out this junit, so no need to
-         * do more.
-         */
+            Item unchecked;
 
-        /*
-         * Class with "is" method.
-         */
-        class IsField {
-            @NotNull
-            Boolean boolValue;
+            @Valid
+            List<Item> items;
 
-            @SuppressWarnings("unused")
-            public Boolean isBoolValue() {
-                return boolValue;
-            }
+            @Valid
+            Map<String, Item> itemMap;
         }
 
-        // ok value
-        IsField isField = new IsField();
-        isField.boolValue = true;
-        assertTrue(validator.validateTop(TOP, isField).isValid());
+        Container cont = new Container();
+        cont.unchecked = new Item();
+        cont.items = List.of(new Item());
+        cont.itemMap = Map.of(STRING_VALUE, new Item());
 
-        // invalid value
-        isField.boolValue = null;
-        assertInvalid("testGetAccessor", validator.validateTop(TOP, isField), BOOL_FIELD, "null");
-    }
+        cont.checked = null;
+        assertTrue(validator.validateTop(TOP, cont).isValid());
 
-    @Test
-    public void testGetMethod() {
-        /*
-         * Class with some fields annotated and some not.
-         */
-        @Getter
-        class Mixed {
-            Integer intValue;
+        cont.checked = new Item();
 
-            @NotNull
-            String strValue;
-        }
+        assertInvalid("testVerCascade", validator.validateTop(TOP, cont), INT_FIELD, "null");
 
-        // invalid
-        Mixed mixed = new Mixed();
-        BeanValidationResult result = validator.validateTop(TOP, mixed);
-        assertInvalid("testGetMethod", result, STR_FIELD, "null");
-        assertFalse(result.getResult().contains(INT_FIELD));
-
-        // intValue is null, but it isn't annotated so this should be valid
-        mixed.strValue = STRING_VALUE;
-        assertTrue(validator.validateTop(TOP, mixed).isValid());
+        cont.checked.intValue = INT_VALUE;
+        assertTrue(validator.validateTop(TOP, cont).isValid());
     }
 
     @Test
-    public void testValidMethod() {
+    public void testVerCollection() {
+        @Getter
+        class Container {
+            @Items(min = @Min(5))
+            List<Integer> items;
 
-        /*
-         * Plain getter.
-         */
-        class PlainGetter {
-            @NotNull
-            @Getter
+            // not a collection - should not be checked
+            @Items(valid = {@Valid})
             String strValue;
+
+            String noAnnotations;
         }
 
-        // invalid
-        PlainGetter plainGetter = new PlainGetter();
-        assertInvalid("testValidMethod", validator.validateTop(TOP, plainGetter), STR_FIELD, "null");
+        Container cont = new Container();
+        cont.strValue = STRING_VALUE;
+        cont.noAnnotations = STRING_VALUE;
 
-        // valid
-        plainGetter.strValue = STRING_VALUE;
-        assertTrue(validator.validateTop(TOP, plainGetter).isValid());
+        // null collection - always valid
+        assertTrue(validator.validateTop(TOP, cont).isValid());
 
-        /*
-         * Static getter - should throw an exception.
-         */
-        StaticGetter staticGetter = new StaticGetter();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, staticGetter))
-                        .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG);
+        // empty collection - always valid
+        cont.items = List.of();
+        assertTrue(validator.validateTop(TOP, cont).isValid());
 
-        /*
-         * Protected getter - should throw an exception.
-         */
-        class ProtectedGetter {
-            @NotNull
-            @Getter(AccessLevel.PROTECTED)
-            String strValue;
-        }
+        cont.items = List.of(-10, -20);
+        assertThat(validator.validateTop(TOP, cont).getResult()).contains("\"0\"", "-10", "\"1\"", "-20", "minimum");
 
-        ProtectedGetter protectedGetter = new ProtectedGetter();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, protectedGetter))
-                        .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG);
+        cont.items = List.of(10, -30);
+        assertThat(validator.validateTop(TOP, cont).getResult()).contains("\"1\"", "-30", "minimum")
+                        .doesNotContain("\"0\"");
 
-        /*
-         * getter is a "void" function - should throw an exception.
-         */
-        class VoidGetter {
-            @NotNull
+        cont.items = List.of(10, 20);
+        assertTrue(validator.validateTop(TOP, cont).isValid());
+    }
+
+    @Test
+    public void testVerMap() {
+        @Getter
+        class Container {
+            @Entries(key = @Items(), value = @Items(min = {@Min(5)}))
+            Map<String, Integer> items;
+
+            // not a map - should not be checked
+            @Entries(key = @Items(), value = @Items(min = {@Min(5)}))
             String strValue;
 
-            @SuppressWarnings("unused")
-            public void getStrValue() {
-                // do nothing
-            }
+            String noAnnotations;
         }
 
-        VoidGetter voidGetter = new VoidGetter();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, voidGetter))
-                        .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG);
+        Container cont = new Container();
+        cont.strValue = STRING_VALUE;
+        cont.noAnnotations = STRING_VALUE;
 
-        /*
-         * getter takes an argument - should throw an exception.
-         */
-        class ArgGetter {
-            @NotNull
-            String strValue;
+        // null map - always valid
+        assertTrue(validator.validateTop(TOP, cont).isValid());
 
-            @SuppressWarnings("unused")
-            public String getStrValue(String echo) {
-                return echo;
-            }
-        }
+        // empty map - always valid
+        cont.items = Map.of();
+        assertTrue(validator.validateTop(TOP, cont).isValid());
 
-        ArgGetter argGetter = new ArgGetter();
-        assertThatIllegalArgumentException().isThrownBy(() -> validator.validateTop(TOP, argGetter))
-                        .withMessageContaining(STR_FIELD).withMessageContaining(GET_MSG);
-    }
+        cont.items = Map.of("abc", -10, "def", -20);
+        assertThat(validator.validateTop(TOP, cont).getResult()).contains("abc", "-10", "def", "-20", "minimum");
 
+        cont.items = Map.of("abc", 10, "def", -30);
+        assertThat(validator.validateTop(TOP, cont).getResult()).contains("def", "-30", "minimum")
+                        .doesNotContain("abc");
 
-    private void assertInvalid(String testName, BeanValidationResult result, String fieldName, String message) {
-        String text = result.getResult();
-        assertNotNull(testName, text);
-        assertTrue(testName, text.contains(fieldName));
-        assertTrue(testName, text.contains(message));
+        cont.items = Map.of("abc", 10, "def", 20);
+        assertTrue(validator.validateTop(TOP, cont).isValid());
     }
 
-    /**
-     * Annotated static field.
-     */
-    private static class AnnotFieldStatic {
-        @NotNull
-        static String strValue;
-    }
+    private <T> void assertNumeric(String testName, T object, Consumer<Integer> setter, String fieldName,
+                    String expectedText, int inside, int edge, int outside) {
+        setter.accept(inside);
+        assertTrue(validator.validateTop(TOP, object).isValid());
 
-    /**
-     * Annotated class with a static field.
-     */
-    @NotNull
-    private static class AnnotClassStatic {
-        @SuppressWarnings("unused")
-        static String strValue;
+        // on the edge
+        setter.accept(edge);
+        assertTrue(validator.validateTop(TOP, object).isValid());
+
+        // invalid
+        setter.accept(outside);
+        assertInvalid("testVerNotNull", validator.validateTop(TOP, object), fieldName, expectedText);
     }
 
-    /**
-     * Class with an annotated field, but a static "getter".
-     */
-    private static class StaticGetter {
-        @NotNull
-        String strValue;
 
-        @SuppressWarnings("unused")
-        public static String getStrValue() {
-            return STRING_VALUE;
-        }
+    private void assertInvalid(String testName, BeanValidationResult result, String... text) {
+        assertThat(result.getResult()).describedAs(testName).contains(text);
     }
 }
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestEntryValidator.java
new file mode 100644 (file)
index 0000000..1c93d6c
--- /dev/null
@@ -0,0 +1,108 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Items;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+
+public class TestEntryValidator extends ValidatorUtil {
+
+    // annotations for keys and values
+
+    @Items()
+    private int emptyAnnot;
+
+    @Items(notBlank = {@NotBlank})
+    private int keyAnnot;
+
+    @Items(min = {@Min(5)})
+    private int valueAnnot;
+
+
+    @Before
+    public void setUp() {
+        bean = new BeanValidator();
+    }
+
+    @Test
+    public void testIsEmpty() {
+        // no annotations for key or value
+        assertThat(new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("emptyAnnot")).isEmpty()).isTrue();
+
+        // annotations for key, value, or both
+        assertThat(new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("emptyAnnot")).isEmpty()).isFalse();
+        assertThat(new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("valueAnnot")).isEmpty()).isFalse();
+        assertThat(new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("valueAnnot")).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testValidateEntry() {
+        EntryValidator validator = new EntryValidator(bean, getAnnot("keyAnnot"), getAnnot("valueAnnot"));
+
+        // valid key & value
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        validator.validateEntry(result, makeEntry(HELLO, 10));
+        assertThat(result.getResult()).isNull();
+
+        // invalid key
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateEntry(result, makeEntry("", 20));
+        assertThat(result.getResult()).doesNotContain("\"value\"").contains("\"key\"", "blank");
+
+        // invalid value
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateEntry(result, makeEntry(HELLO, -10));
+        assertThat(result.getResult()).contains(HELLO, "\"value\"", "-10").doesNotContain("\"key\"");
+
+        // both invalid
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateEntry(result, makeEntry("", -100));
+        assertThat(result.getResult()).contains("\"key\"", "blank", "\"value\"", "-100");
+    }
+
+    @Test
+    public void testGetName() {
+        EntryValidator validator = new EntryValidator(bean, getAnnot("emptyAnnot"), getAnnot("emptyAnnot"));
+        assertThat(validator.getName(makeEntry(null, 0))).isEmpty();
+        assertThat(validator.getName(makeEntry("", 0))).isEmpty();
+        assertThat(validator.getName(makeEntry(HELLO, 0))).isEqualTo(HELLO);
+    }
+
+    /**
+     * Makes a Map entry with the given key and value.
+     *
+     * @param key desired key
+     * @param value desired value
+     * @return a new Map entry
+     */
+    Map.Entry<String, Integer> makeEntry(String key, int value) {
+        HashMap<String, Integer> map = new HashMap<>();
+        map.put(key, value);
+        return map.entrySet().iterator().next();
+    }
+}
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestFieldValidator.java
new file mode 100644 (file)
index 0000000..e4432c8
--- /dev/null
@@ -0,0 +1,274 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import lombok.Getter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+public class TestFieldValidator extends ValidatorUtil {
+    private static final String UNANNOTATED_FIELD = "unannotated";
+    private static final String INT_FIELD = "intValue";
+    private static final int VALID_INT = 10;
+    private static final int INVALID_INT = -10;
+
+    @Getter
+    private int unannotated;
+
+    @Min(0)
+    @Getter
+    private int intValue;
+
+    @NotNull
+    @Getter
+    private boolean boolValue;
+
+    @NotNull
+    @Getter
+    private String notNullValue;
+
+    @Min(0)
+    @Getter
+    private static int staticField;
+
+    /**
+     * Has no accessor.
+     */
+    @Min(0)
+    private int noMethod;
+
+    /**
+     * Accessor is {@link #getStaticMethod()}, which is static.
+     */
+    @Min(0)
+    private int staticMethod;
+
+    /**
+     * Accessor is {@link #getVoidMethod()}, which returns a void.
+     */
+    @Min(0)
+    private int voidMethod;
+
+    /**
+     * Accessor is {@link #getParameterizedMethod()}, which requires a parameter.
+     */
+    @Min(0)
+    private int parameterizedMethod;
+
+    /**
+     * Accessor is {@link #getExMethod()}, which throws an exception.
+     */
+    @Min(0)
+    private int exMethod;
+
+
+    @Before
+    public void setUp() {
+        bean = new BeanValidator();
+    }
+
+    @Test
+    public void testGetAnnotation() {
+        // field-level annotation
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse();
+
+        // class-level annotation
+        assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "text")).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testFieldValidator() throws NoSuchFieldException, SecurityException {
+        /*
+         * Note: nested classes contain fields like "$this", thus the check for "$" in the
+         * variable name is already covered by the other tests.
+         */
+
+        /*
+         * Class with no annotations.
+         */
+        @NotNull
+        class NoAnnotations {
+            @SuppressWarnings("unused")
+            String strValue;
+        }
+
+        Field field = NoAnnotations.class.getDeclaredField("this$0");
+
+        assertThat(new FieldValidator(bean, NoAnnotations.class, field).isEmpty()).isTrue();
+
+        // unannotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("unannotated")).isEmpty()).isTrue();
+
+        // these are invalid for various reasons
+
+        Field staticField2 = getField("staticField");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticField2))
+                        .isInstanceOf(IllegalArgumentException.class);
+
+        Field noMethodField = getField("noMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, noMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+
+        // annotated
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testFieldValidator_SetNullAllowed() {
+        // default - null is allowed
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isNullAllowed()).isTrue();
+
+        // field-level NotNull
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("notNullValue")).isNullAllowed())
+                        .isFalse();
+
+        // class-level NotNull
+        assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "noMethod")).isNullAllowed())
+                        .isFalse();
+    }
+
+    @Test
+    public void testValidateField_testGetValue() {
+        // unannotated
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        new FieldValidator(bean, getClass(), getField(UNANNOTATED_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        // valid
+        intValue = VALID_INT;
+        result = new BeanValidationResult(MY_NAME, this);
+        new FieldValidator(bean, getClass(), getField(INT_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).isNull();
+
+        // invalid
+        intValue = INVALID_INT;
+        result = new BeanValidationResult(MY_NAME, this);
+        new FieldValidator(bean, getClass(), getField(INT_FIELD)).validateField(result, this);
+        assertThat(result.getResult()).contains(INT_FIELD);
+
+        // throws an exception
+        FieldValidator validator = new FieldValidator(bean, TestFieldValidator.class, getField("exMethod"));
+        BeanValidationResult result2 = new BeanValidationResult(MY_NAME, this);
+        assertThatThrownBy(() -> validator.validateField(result2, this)).isInstanceOf(IllegalArgumentException.class)
+                        .getCause().isInstanceOf(InvocationTargetException.class).getCause()
+                        .hasMessage("expected exception");
+    }
+
+    @Test
+    public void testClassOnly() {
+        // class-level annotation has no bearing on a static field
+        assertThat(new FieldValidator(bean, ClassAnnot.class, getField(ClassAnnot.class, "staticValue")).isEmpty())
+                        .isTrue();
+
+        // field-level annotation on a static field
+        Field staticField2 = getField("staticField");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticField2))
+                        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testGetAccessor() {
+        // uses "getXxx"
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse();
+
+        // uses "isXxx"
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField("boolValue")).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testGetMethod() {
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse();
+
+        // these are invalid for various reasons
+
+        Field noMethodField = getField("noMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, noMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+
+        Field staticMethodField = getField("staticMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testValidMethod() {
+        assertThat(new FieldValidator(bean, TestFieldValidator.class, getField(INT_FIELD)).isEmpty()).isFalse();
+
+        // these are invalid for various reasons
+
+        Field staticMethodField = getField("staticMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, staticMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+
+        Field voidMethodField = getField("voidMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, voidMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+
+        Field paramMethodField = getField("parameterizedMethod");
+        assertThatThrownBy(() -> new FieldValidator(bean, TestFieldValidator.class, paramMethodField))
+                        .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testIsFieldAnnotated_testSetFieldAnnotated() {
+        // annotated at the field level
+        assertThat(new FieldValidator(bean, getClass(), getField(INT_FIELD)).isFieldAnnotated()).isTrue();
+
+        // unannotated
+        assertThat(new FieldValidator(bean, getClass(), getField(UNANNOTATED_FIELD)).isFieldAnnotated()).isFalse();
+    }
+
+    public static int getStaticMethod() {
+        return -1000;
+    }
+
+    public void getVoidMethod() {
+        // do nothing
+    }
+
+    public int getParameterizedMethod(boolean flag) {
+        return 0;
+    }
+
+    public int getExMethod() {
+        throw new RuntimeException("expected exception");
+    }
+
+    @NotNull
+    public static class ClassAnnot {
+        @Getter
+        private String text;
+
+        // no "get" method
+        @SuppressWarnings("unused")
+        private String noMethod;
+
+        @Getter
+        private static int staticValue;
+    }
+}
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestItemValidator.java
new file mode 100644 (file)
index 0000000..2a0394d
--- /dev/null
@@ -0,0 +1,161 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Items;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+public class TestItemValidator extends ValidatorUtil {
+
+    // annotated fields - each field must have exactly one annotation
+
+    /**
+     * This annotation does not contain a method returning an array.
+     */
+    @Min(value = 0)
+    private int notArray;
+
+    /**
+     * This annotation doesn't contain any annotations that the {@link BeanValidator}
+     * recognizes.
+     */
+    @SimpleItems(simple = {@Simple})
+    private int mismatch;
+
+    /**
+     * Annotation with no sub-annotations.
+     */
+    @Items()
+    private int noAnnotations;
+
+    /**
+     * One matching sub-annotation.
+     */
+    @Items(notNull = {@NotNull})
+    private int match;
+
+    /**
+     * Excess matching sub-annotations of a single type.
+     */
+    @Items(notNull = {@NotNull, @NotNull})
+    private int excess;
+
+    /**
+     * Multiple matching annotations.
+     */
+    @Items(notNull = {@NotNull}, notBlank = {@NotBlank})
+    private String multiMatch;
+
+
+    @Before
+    public void setUp() {
+        bean = new BeanValidator();
+    }
+
+    @Test
+    public void testGetAnnotation() {
+        // no matches
+        assertThat(new ItemValidator(bean, getAnnot("noAnnotations"), true).isEmpty()).isTrue();
+
+        // had a match
+        assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse();
+
+        // with an exception
+        IllegalAccessException ex = new IllegalAccessException("expected exception");
+
+        assertThatThrownBy(() -> new ItemValidator(bean, getAnnot("match"), true) {
+            @Override
+            protected <T extends Annotation> T getAnnotation2(Class<T> annotClass, Method method)
+                            throws IllegalAccessException {
+                throw ex;
+            }
+        }).hasCause(ex);
+
+        // multiple matches
+        ItemValidator validator = new ItemValidator(bean, getAnnot("multiMatch"), true);
+
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, HELLO);
+        assertThat(result.getResult()).isNull();
+
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, null);
+        assertThat(result.getResult()).isNotNull();
+
+        result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, "");
+        assertThat(result.getResult()).isNotNull();
+    }
+
+    @Test
+    public void testItemValidatorBeanValidatorAnnotation() {
+        assertThat(new ItemValidator(bean, getAnnot("match")).isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testItemValidatorBeanValidatorAnnotationBoolean() {
+        assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse();
+
+        assertThat(new ItemValidator(bean, getAnnot("match"), false).isEmpty()).isTrue();
+    }
+
+    @Test
+    public void testGetAnnotation2() {
+        assertThat(new ItemValidator(bean, getAnnot("notArray"), true).isEmpty()).isTrue();
+        assertThat(new ItemValidator(bean, getAnnot("mismatch"), true).isEmpty()).isTrue();
+        assertThat(new ItemValidator(bean, getAnnot("noAnnotations"), true).isEmpty()).isTrue();
+
+        assertThat(new ItemValidator(bean, getAnnot("match"), true).isEmpty()).isFalse();
+
+        Annotation excess = getAnnot("excess");
+        assertThatThrownBy(() -> new ItemValidator(bean, excess, true)).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    // these annotations are not recognized by the BeanValidator
+
+    @Retention(RUNTIME)
+    @Target(FIELD)
+    public @interface Simple {
+
+    }
+
+    @Retention(RUNTIME)
+    @Target(FIELD)
+    public @interface SimpleItems {
+        /**
+         * Validates that it's simple.
+         */
+        Simple[] simple() default {};
+    }
+}
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/TestValueValidator.java
new file mode 100644 (file)
index 0000000..dcf0869
--- /dev/null
@@ -0,0 +1,142 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.annotation.Annotation;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.annotations.Min;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+public class TestValueValidator extends ValidatorUtil {
+
+    private ValueValidator validator;
+
+    // these fields just provide place-holders for annotations
+
+    @NotNull
+    @NotBlank
+    private final int annotField = 1;
+
+
+    @Before
+    public void setUp() {
+        validator = new MyValueValidator();
+    }
+
+    @Test
+    public void testIsEmpty() {
+        assertThat(validator.isEmpty()).isTrue();
+
+        validator.addAnnotation(NotNull.class, (result2, fieldName, value) -> true);
+        assertThat(validator.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testValidateValue_NullValue() {
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+
+        validator.validateValue(result, MY_FIELD, null);
+        assertThat(result.getResult()).isNull();
+
+        validator.addAnnotation(NotNull.class,
+            (result2, fieldName, value) -> result2.validateNotNull(fieldName, value));
+        validator.validateValue(result, MY_FIELD, null);
+        assertThat(result.getResult()).contains(MY_FIELD, "null");
+    }
+
+    @Test
+    public void testValidateValue_NotNullValue() {
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+
+        validator.validateValue(result, MY_FIELD, HELLO);
+        assertThat(result.getResult()).isNull();
+
+        validator.addAnnotation(NotNull.class,
+            (result2, fieldName, value) -> result2.validateNotNull(fieldName, value));
+        validator.validateValue(result, MY_FIELD, HELLO);
+        assertThat(result.getResult()).isNull();
+    }
+
+    @Test
+    public void testAddAnnotationClassOfTChecker() {
+        // the field does not have this annotation
+        validator.addAnnotation(Min.class, (result2, fieldName, value) -> true);
+        assertThat(validator.isEmpty()).isTrue();
+
+        // "null" flag should stay true with this annotation
+        assertThat(validator.isNullAllowed()).isTrue();
+        validator.addAnnotation(NotBlank.class, (result2, fieldName, value) -> true);
+        assertThat(validator.isNullAllowed()).isTrue();
+
+        // "null" flag should become false with this annotation
+        assertThat(validator.isNullAllowed()).isTrue();
+        validator.addAnnotation(NotNull.class, (result2, fieldName, value) -> true);
+        assertThat(validator.isNullAllowed()).isFalse();
+    }
+
+    @Test
+    public void testAddAnnotationClassOfTCheckerWithAnnotOfT() {
+        // the field does not have this annotation
+        validator.addAnnotation(Min.class, (result2, fieldName, annot, value) -> true);
+        assertThat(validator.isEmpty()).isTrue();
+
+        // indicates the annotation value
+        AtomicBoolean wasNull = new AtomicBoolean(false);
+
+        // the field DOES have this annotation
+        validator.addAnnotation(NotNull.class, (result2, fieldName, annot, value) -> {
+            wasNull.set(annot instanceof NotNull);
+            return result2.validateNotNull(fieldName, value);
+        });
+        assertThat(validator.isEmpty()).isFalse();
+
+        // ensure that the checker is invoked
+        BeanValidationResult result = new BeanValidationResult(MY_NAME, this);
+        validator.validateValue(result, MY_FIELD, HELLO);
+        assertThat(result.getResult()).isNull();
+
+        assertThat(wasNull.get()).isTrue();
+    }
+
+    @Test
+    public void testGetAnnotation() {
+        assertThat(new ValueValidator().getAnnotation(NotNull.class)).isNull();
+    }
+
+    /**
+     * Checks for annotations on the "annotField" field.
+     */
+    private static class MyValueValidator extends ValueValidator {
+        @Override
+        public <T extends Annotation> T getAnnotation(Class<T> annotClass) {
+            try {
+                return TestValueValidator.class.getDeclaredField("annotField").getAnnotation(annotClass);
+            } catch (NoSuchFieldException | SecurityException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java b/common-parameters/src/test/java/org/onap/policy/common/parameters/ValidatorUtil.java
new file mode 100644 (file)
index 0000000..4df014f
--- /dev/null
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.parameters;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+
+/**
+ * Utilities for validator tests.
+ */
+public class ValidatorUtil {
+    protected static final String MY_NAME = "My-Name";
+    protected static final String MY_FIELD = "My-Field";
+    protected static final String HELLO = "hello";
+
+    protected BeanValidator bean;
+
+    /**
+     * Gets the single annotation for a given field.
+     *
+     * @param fieldName name of the field having the desired annotation
+     * @return the given field's annotation
+     */
+    protected Annotation getAnnot(String fieldName) {
+        return getField(fieldName).getAnnotations()[0];
+    }
+
+    /**
+     * Gets a field from this object.
+     *
+     * @param fieldName name of the field of interest
+     * @return the given field
+     */
+    protected Field getField(String fieldName) {
+        return getField(getClass(), fieldName);
+    }
+
+    /**
+     * Gets a field from a given class.
+     *
+     * @param clazz class containing the field
+     * @param fieldName name of the field of interest
+     * @return the given field
+     */
+    protected Field getField(Class<?> clazz, String fieldName) {
+        try {
+            return clazz.getDeclaredField(fieldName);
+
+        } catch (NoSuchFieldException | SecurityException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}