BVAL-154: cache unconstrained types by type only and quickly return empty descriptors on subsequent fetches
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
index 7c84f87..f25514c 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
@@ -16,8 +16,10 @@
  */
 package org.apache.bval.jsr.descriptor;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -26,11 +28,13 @@
 import javax.validation.metadata.ContainerDescriptor;
 import javax.validation.metadata.ElementDescriptor;
 import javax.validation.metadata.ExecutableDescriptor;
+import javax.validation.metadata.MethodType;
 
 import org.apache.bval.jsr.ApacheValidatorFactory;
 import org.apache.bval.jsr.metadata.AnnotationBehaviorMergeStrategy;
 import org.apache.bval.jsr.metadata.CompositeBuilder;
 import org.apache.bval.jsr.metadata.DualBuilder;
+import org.apache.bval.jsr.metadata.EmptyBuilder;
 import org.apache.bval.jsr.metadata.HierarchyBuilder;
 import org.apache.bval.jsr.metadata.MetadataBuilder;
 import org.apache.bval.jsr.metadata.ReflectionBuilder;
@@ -55,6 +59,8 @@
 
     private final ApacheValidatorFactory validatorFactory;
     private final ConcurrentMap<Class<?>, BeanD<?>> beanDescriptors = new ConcurrentHashMap<>();
+    // synchronization unnecessary
+    private final Set<Class<?>> knownUnconstrainedTypes = new HashSet<>();
     private final ReflectionBuilder reflectionBuilder;
 
     public DescriptorManager(ApacheValidatorFactory validatorFactory) {
@@ -70,13 +76,19 @@
         if (beanDescriptors.containsKey(beanClass)) {
             return beanDescriptors.get(beanClass);
         }
-        final MetadataReader.ForBean<T> reader =
-            new MetadataReader(validatorFactory, beanClass).forBean(builder(beanClass));
-        final BeanD<T> beanD = new BeanD<>(reader);
-        @SuppressWarnings("unchecked")
-        final BeanD<T> result =
-            Optional.ofNullable((BeanD<T>) beanDescriptors.putIfAbsent(beanClass, beanD)).orElse(beanD);
-        return result;
+        final MetadataBuilder.ForBean<T> builder =
+            knownUnconstrainedTypes.contains(beanClass) ? EmptyBuilder.instance().forBean() : builder(beanClass);
+        final BeanD<T> beanD = new BeanD<>(new MetadataReader(validatorFactory, beanClass).forBean(builder));
+
+        if (beanD.isBeanConstrained() || !(beanD.getConstrainedConstructors().isEmpty()
+            && beanD.getConstrainedMethods(MethodType.GETTER, MethodType.NON_GETTER).isEmpty())) {
+            @SuppressWarnings("unchecked")
+            final BeanD<T> result =
+                Optional.ofNullable((BeanD<T>) beanDescriptors.putIfAbsent(beanClass, beanD)).orElse(beanD);
+            return result;
+        }
+        knownUnconstrainedTypes.add(beanClass);
+        return beanD;
     }
 
     public void clear() {
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
index d81f90a..b18030d 100644
--- a/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/BeanDescriptorTest.java
@@ -23,6 +23,9 @@
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import java.lang.annotation.Documented;
@@ -165,6 +168,19 @@
         assertEquals("Incorrect number of descriptors", 1, constraints.size());
     }
 
+    @Test
+    public void testDescriptorCaching() {
+        // constrained
+        final BeanDescriptor personDescriptor = validator.getConstraintsForClass(Person.class);
+        assertNotNull(personDescriptor);
+        assertSame(personDescriptor, validator.getConstraintsForClass(Person.class));
+
+        // unconstrained
+        final BeanDescriptor objectDescriptor = validator.getConstraintsForClass(Object.class);
+        assertNotNull(objectDescriptor);
+        assertNotSame(objectDescriptor, validator.getConstraintsForClass(Object.class));
+    }
+
     public static class Form {
         @NotNull
         public String name;