Repository: j-easy/easy-random
Issue: InstantiationException when providing a randomizer without default constructor in @Randomizer

The `@Randomizer` annotation requires a randomizer with a default constructor. If no such a constructor is defined, random beans throws a `InstantiationException`. Instead, the library should throw a `ObjectGenerationException` (as declared in the API) with the `InstantiationException` as root cause


Changed files:
random-beans/src/main/java/io/github/benas/randombeans/randomizers/registry/AnnotationRandomizerRegistry.java
random-beans/src/main/java/io/github/benas/randombeans/util/ReflectionUtils.java
random-beans/src/test/java/io/github/benas/randombeans/RandomizerAnnotationTest.java

diff --git a/random-beans/src/main/java/io/github/benas/randombeans/randomizers/registry/AnnotationRandomizerRegistry.java b/random-beans/src/main/java/io/github/benas/randombeans/randomizers/registry/AnnotationRandomizerRegistry.java
index e5c05fac..2ea8f6aa 100644
--- a/random-beans/src/main/java/io/github/benas/randombeans/randomizers/registry/AnnotationRandomizerRegistry.java
+++ b/random-beans/src/main/java/io/github/benas/randombeans/randomizers/registry/AnnotationRandomizerRegistry.java
@@ -31,8 +31,6 @@ import io.github.benas.randombeans.api.RandomizerRegistry;
 import io.github.benas.randombeans.util.ReflectionUtils;
 
 import java.lang.reflect.Field;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 
 /**
  * A {@link RandomizerRegistry} for fields annotated with {@link io.github.benas.randombeans.annotation.Randomizer}.
@@ -42,8 +40,6 @@ import java.util.logging.Logger;
 @Priority(-253)
 public class AnnotationRandomizerRegistry implements RandomizerRegistry {
 
-    private static final Logger LOGGER = Logger.getLogger(AnnotationRandomizerRegistry.class.getName());
-
     /**
      * Set the initial seed for all randomizers of the registry
      *
@@ -61,19 +57,12 @@ public class AnnotationRandomizerRegistry implements RandomizerRegistry {
      * @return the randomizer registered for the given field
      */
     @Override
-    @SuppressWarnings(value = "unchecked")
     public Randomizer<?> getRandomizer(Field field) {
         if (field.isAnnotationPresent(io.github.benas.randombeans.annotation.Randomizer.class)) {
             io.github.benas.randombeans.annotation.Randomizer randomizer = field.getAnnotation(io.github.benas.randombeans.annotation.Randomizer.class);
             Class<?> type = randomizer.value();
             RandomizerArgument[] arguments = randomizer.args();
-
-            try {
-                //return (Randomizer<?>) type.newInstance();
-                return ReflectionUtils.newInstance(type, arguments);
-            } catch (Exception e) {
-                LOGGER.log(Level.WARNING, "Unable to create an instance of " + type.getName(), e);
-            }
+            return ReflectionUtils.newInstance(type, arguments);
         }
         return null;
     }
diff --git a/random-beans/src/main/java/io/github/benas/randombeans/util/ReflectionUtils.java b/random-beans/src/main/java/io/github/benas/randombeans/util/ReflectionUtils.java
index fe2dfa10..184074d4 100644
--- a/random-beans/src/main/java/io/github/benas/randombeans/util/ReflectionUtils.java
+++ b/random-beans/src/main/java/io/github/benas/randombeans/util/ReflectionUtils.java
@@ -25,6 +25,7 @@
 package io.github.benas.randombeans.util;
 
 import io.github.benas.randombeans.annotation.RandomizerArgument;
+import io.github.benas.randombeans.api.ObjectGenerationException;
 import io.github.benas.randombeans.api.Randomizer;
 import lombok.experimental.UtilityClass;
 
@@ -267,29 +268,34 @@ public class ReflectionUtils {
         return actualTypeArguments;
     }
 
-    public static <T> Randomizer<T> newInstance(Class<T> clazz, RandomizerArgument[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
-        if(args != null && args.length > 0) {
-            for(Constructor c : clazz.getConstructors()) {
-                if(c.getParameterCount() > 0 && c.getParameterCount() == args.length) {
-                    Object[] nArgs = new Object[args.length];
-                    Class[] argTypes = c.getParameterTypes();
-                    for(int x=0; x < args.length; x++) {
-                        Class<?> argType = argTypes[x];
-                        RandomizerArgument arg = args[x];
-                        String val = arg.value();
-                        Class type = arg.type();
+    @SuppressWarnings("unchecked")
+    public static <T> Randomizer<T> newInstance(Class<T> clazz, RandomizerArgument[] args) {
+        try {
+            if (args != null && args.length > 0) {
+                for (Constructor<?> c : clazz.getConstructors()) {
+                    if (c.getParameterCount() > 0 && c.getParameterCount() == args.length) {
+                        Object[] nArgs = new Object[args.length];
+                        Class<?>[] argTypes = c.getParameterTypes();
+                        for (int x = 0; x < args.length; x++) {
+                            Class<?> argType = argTypes[x];
+                            RandomizerArgument arg = args[x];
+                            String val = arg.value();
+                            Class<?> type = arg.type();
 
-                        if(argType.isAssignableFrom(arg.type())) {
-                            nArgs[x] = Mapper.INSTANCE.convertValue(val, type);
-                        } else {
-                            // Can't be a valid input for this constructor
-                            break;
+                            if (argType.isAssignableFrom(arg.type())) {
+                                nArgs[x] = Mapper.INSTANCE.convertValue(val, type);
+                            } else {
+                                // Can't be a valid input for this constructor
+                                break;
+                            }
                         }
+                        return (Randomizer<T>) c.newInstance(nArgs);
                     }
-                    return (Randomizer<T>) c.newInstance(nArgs);
                 }
             }
+            return (Randomizer<T>) clazz.newInstance();
+        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+            throw new ObjectGenerationException(String.format("Could not create Randomizer with type: %s and constructor arguments: %s", clazz, Arrays.toString(args)), e);
         }
-        return (Randomizer<T>) clazz.newInstance();
-    }
     }
+}
diff --git a/random-beans/src/test/java/io/github/benas/randombeans/RandomizerAnnotationTest.java b/random-beans/src/test/java/io/github/benas/randombeans/RandomizerAnnotationTest.java
index b02564f2..7dc94289 100644
--- a/random-beans/src/test/java/io/github/benas/randombeans/RandomizerAnnotationTest.java
+++ b/random-beans/src/test/java/io/github/benas/randombeans/RandomizerAnnotationTest.java
@@ -24,9 +24,11 @@
 
 package io.github.benas.randombeans;
 
-import io.github.benas.randombeans.annotation.RandomizerArgument;
 import org.junit.Test;
 
+import io.github.benas.randombeans.api.ObjectGenerationException;
+import io.github.benas.randombeans.api.Randomizer;
+
 import static io.github.benas.randombeans.EnhancedRandomBuilder.aNewEnhancedRandomBuilder;
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -38,6 +40,28 @@ public class RandomizerAnnotationTest {
         assertThat(foo.getName()).isEqualTo("foo");
     }
 
+    @Test(expected=ObjectGenerationException.class)
+    // https://github.com/benas/random-beans/issues/131
+    public void shouldThrowObjectGenerationExceptionWhenRandomizerUsedInRandomizerAnnotationHasNoDefaultConstructor() {
+        aNewEnhancedRandomBuilder().build().nextObject(Bar.class);
+    }
+
+    private class Bar {
+        @io.github.benas.randombeans.annotation.Randomizer(RandomizerWithoutDefaultConstrcutor.class)
+        private String name;
+    }
+
+    public static class RandomizerWithoutDefaultConstrcutor implements Randomizer<String> {
+
+        public RandomizerWithoutDefaultConstrcutor(int d) {
+        }
+
+        @Override
+        public String getRandomValue() {
+            return null;
+        }
+    }
+
     private class Foo {
         @io.github.benas.randombeans.annotation.Randomizer(DummyRandomizer.class)
         private String name;