BUG-1469: introduce ReflectiveExceptionMapper 09/9609/5
authorRobert Varga <rovarga@cisco.com>
Sat, 2 Aug 2014 10:34:00 +0000 (12:34 +0200)
committerRobert Varga <rovarga@cisco.com>
Sun, 3 Aug 2014 10:47:20 +0000 (12:47 +0200)
Convenience class for use when simple instantiation is acceptable and
the mapper instance can be shared.

Change-Id: I1946155a46b94fda5067ccbffcbf3b72f2ce8e3d
Signed-off-by: Robert Varga <rovarga@cisco.com>
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/ExceptionMapper.java
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapper.java [new file with mode: 0644]
common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapperTest.java [new file with mode: 0644]

index af51032dd27b5b65dedcd96b2a62c9f0c32f8fb7..604ca3692c6f41b3cc74f941a1f16e201ca591a2 100644 (file)
@@ -43,6 +43,15 @@ public abstract class ExceptionMapper<X extends Exception> implements Function<E
         this.opName = Preconditions.checkNotNull(opName);
     }
 
+    /**
+     * Return the exception class produced by this instance.
+     *
+     * @return Exception class.
+     */
+    protected final Class<X> getExceptionType() {
+        return exceptionType;
+    }
+
     /**
      * Invoked to create a new exception instance of the specified type.
      *
diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapper.java b/common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapper.java
new file mode 100644 (file)
index 0000000..3eb8c13
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014 Robert Varga.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util.concurrent;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Convenience {@link ExceptionMapper} which instantiates specified Exception using
+ * reflection. The Exception types are expected to declare an accessible constructor
+ * which takes two arguments: a String and a Throwable.
+ *
+ * @param <X> Exception type
+ */
+public final class ReflectiveExceptionMapper<X extends Exception> extends ExceptionMapper<X> {
+    private final Constructor<X> ctor;
+
+    private ReflectiveExceptionMapper(final String opName, final Constructor<X> ctor) {
+        super(opName, ctor.getDeclaringClass());
+        this.ctor = ctor;
+    }
+
+    @Override
+    protected X newWithCause(final String message, final Throwable cause) {
+        try {
+            return ctor.newInstance(message, cause);
+        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+            throw new IllegalStateException("Failed to instantiate exception " + ctor.getDeclaringClass(), e);
+        }
+    }
+
+    /**
+     * Create a new instance of the reflective exception mapper. This method performs basic
+     * sanity checking on the exception class. This method is potentially very costly, so
+     * users are strongly encouraged to cache the returned mapper for reuse.
+     *
+     * @param opName Operation performed
+     * @param exceptionType Exception type
+     * @return A new mapper instance
+     * @throws IllegalArgumentException when the supplied exception class does not pass sanity checks
+     * @throws SecurityException when the required constructor is not accessible
+     */
+    public static <X extends Exception> ReflectiveExceptionMapper<X> create(final String opName, final Class<X> exceptionType) throws SecurityException {
+        final Constructor<X> c;
+        try {
+            c = exceptionType.getConstructor(String.class, Throwable.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException("Class does not define a String, Throwable constructor", e);
+        }
+
+        try {
+            c.newInstance(opName, new Throwable());
+        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+            throw new IllegalArgumentException("Constructor " + c.getName() + " failed to pass instantiation test", e);
+        }
+
+        return new ReflectiveExceptionMapper<>(opName, c);
+    }
+}
diff --git a/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapperTest.java b/common/util/src/test/java/org/opendaylight/yangtools/util/concurrent/ReflectiveExceptionMapperTest.java
new file mode 100644 (file)
index 0000000..c18ac1f
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014 Robert Varga.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util.concurrent;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.ExecutionException;
+
+import org.junit.Test;
+
+public final class ReflectiveExceptionMapperTest {
+    static final class NoArgumentCtorException extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        public NoArgumentCtorException() {
+            super();
+        }
+    }
+
+    static final class PrivateCtorException extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        private PrivateCtorException(final String message, final Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class FailingCtorException extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        public FailingCtorException(final String message, final Throwable cause) {
+            throw new IllegalArgumentException("just for test");
+        }
+    }
+
+    static final class GoodException extends Exception {
+        private static final long serialVersionUID = 1L;
+
+        public GoodException(final String message, final Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testNoArgumentsContructor() {
+        ReflectiveExceptionMapper.create("no arguments", NoArgumentCtorException.class);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testPrivateContructor() {
+        ReflectiveExceptionMapper.create("private constructor", PrivateCtorException.class);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testFailingContructor() {
+        ReflectiveExceptionMapper.create("failing constructor", FailingCtorException.class);
+    }
+
+    @Test
+    public void testInstantiation() {
+        ReflectiveExceptionMapper<GoodException> mapper = ReflectiveExceptionMapper.create("instantiation", GoodException.class);
+
+        final Throwable cause = new Throwable("some test message");
+
+        GoodException ret = mapper.apply(new ExecutionException("test", cause));
+
+        assertEquals("instantiation execution failed", ret.getMessage());
+        assertEquals(cause, ret.getCause());
+    }
+}