From e3630f6917fab71ba621b9eca914bf291f336d2e Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Fri, 19 Sep 2014 08:05:21 -0500 Subject: [PATCH] DATACASS-164: dyn proxies now implement Serializable, don't require MapId extension --- .../support/IdInterfaceException.java | 29 +++ .../support/IdInterfaceExceptions.java | 78 ++++++++ .../support/IdInterfaceValidator.java | 100 ++++++++++ .../repository/support/MapIdFactory.java | 50 ++++- .../support/MapIdProxyDelegate.java | 157 ++++++++------- ...lateMapIdProxyDelegateIntegrationTest.java | 4 +- .../unit/mapidfactory/MapIdFactoryTest.java | 183 +++++++++++++++++- 7 files changed, 514 insertions(+), 87 deletions(-) create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceException.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceExceptions.java create mode 100644 spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceValidator.java diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceException.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceException.java new file mode 100644 index 000000000..b0abeb8d2 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceException.java @@ -0,0 +1,29 @@ +package org.springframework.data.cassandra.repository.support; + +import java.lang.reflect.Method; + +public class IdInterfaceException extends RuntimeException { + + private static final long serialVersionUID = -1635695314254522703L; + + String idInterfaceName; + String method; + + public IdInterfaceException(Class idInterface, Method method, String message) { + this(idInterface.getClass().getName(), method == null ? null : method.toString(), message); + } + + public IdInterfaceException(String idInterfaceName, String method, String message) { + super(message); + this.idInterfaceName = idInterfaceName; + this.method = method; + } + + public String getIdInterfaceName() { + return idInterfaceName; + } + + public String getMethod() { + return method; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceExceptions.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceExceptions.java new file mode 100644 index 000000000..6d93d62b1 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceExceptions.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2014 the original author or authors + * + * 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. + */ +package org.springframework.data.cassandra.repository.support; + +import java.util.Collection; +import java.util.LinkedList; + +/** + * Aggregator of multiple violations for convenience when verifying id interfaces. This allows the framework to + * communicate all errors at once, rather than one at a time. + * + * @author Matthew T. Adams + */ +@SuppressWarnings("serial") +public class IdInterfaceExceptions extends RuntimeException { + + Collection exceptions = new LinkedList(); + String idInterfaceName; + + public IdInterfaceExceptions(Class idInterface) { + this.idInterfaceName = idInterface.getClass().getName(); + } + + public void add(IdInterfaceException e) { + exceptions.add(e); + } + + /** + * Returns a list of the {@link IdInterfaceException}s aggregated within. + */ + public Collection getExceptions() { + return exceptions; + } + + /** + * Returns a list of the {@link IdInterfaceException} messages aggregated within. + */ + public Collection getMessages() { + Collection messages = new LinkedList(); + for (IdInterfaceException e : exceptions) { + messages.add(e.getMessage()); + } + return messages; + } + + /** + * Returns the number of exceptions aggregated in this exception. + */ + public int getCount() { + return exceptions.size(); + } + + @Override + public String getMessage() { + StringBuilder builder = new StringBuilder(idInterfaceName).append(":\n"); + for (IdInterfaceException e : exceptions) { + builder.append(e.getMessage()).append("\n"); + } + return builder.toString(); + } + + public String getIdInterfaceName() { + return idInterfaceName; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceValidator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceValidator.java new file mode 100644 index 000000000..0439098c0 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/IdInterfaceValidator.java @@ -0,0 +1,100 @@ +package org.springframework.data.cassandra.repository.support; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.data.cassandra.repository.MapId; + +public class IdInterfaceValidator { + + /** + * Validates the form of the given id interface candidate type. If the interface violates the following restrictions, + * then an {@link IdInterfaceExceptions} is thrown containing all of the violations encountered, which can be obtained + * from {@link IdInterfaceExceptions#getExceptions()} or, as a convenience, + * {@link IdInterfaceExceptions#getMessages()}. + *

+ * Id interfaces are intended to have methods representing setters and getters. Getter methods take the form + *

    + *
  • PropertyType getPropertyName() or
  • + *
  • PropertyType propertyName().
  • + *
+ * Setter methods take the form + *
    + *
  • void|IdType setPropertyName(PropertyType),
  • + *
  • void|IdType withPropertyName(PropertyType), or
  • + *
  • void|IdType propertyName(PropertyType);
  • + *
+ * setter methods may also declare that they return their id interface type to support method chaining. + *

+ * Id interfaces + *

    + *
  • must be an interface, not a class,
  • + *
  • may extend {@link Serializable},
  • + *
  • may extend {@link MapId},
  • + *
  • must have getter methods that only return a {@link Serializable} type,
  • + *
  • must have setter methods that only take a single {@link Serializable} type,
  • + *
  • must have setter methods that only return void or the id interface's type,
  • + *
  • must not define any getter methods with the literal name "get", and
  • + *
  • must not define any setter methods with the literal names "set" or "with".
  • + *
+ * + * @param id The candidate interface type + * @throws IdInterfaceExceptions + * @see IdInterfaceExceptions#getExceptions() + * @see {@link IdInterfaceException} + */ + public static void validate(Class id) { + + IdInterfaceExceptions x = new IdInterfaceExceptions(id); + + if (!id.isInterface()) { + x.add(new IdInterfaceException(id, null, "id type must be an interface")); + } + + Class[] interfaces = id.getInterfaces(); + if (interfaces.length > 2 + || ((interfaces.length == 1 && !(interfaces[0].equals(Serializable.class) || interfaces[0].equals(MapId.class))))) { + x.add(new IdInterfaceException(id, null, "id type may only extend Serializable and/or MapId")); + } + + for (Method m : id.getDeclaredMethods()) { + Class[] args = m.getParameterTypes(); + String name = m.getName(); + Class ret = m.getReturnType(); + switch (args.length) { + case 0: // then getter + if (name.startsWith("get") && name.length() == 3) { + x.add(new IdInterfaceException(id, m, "getter methods must have a property name following 'get' prefix")); + } + if (!Serializable.class.isAssignableFrom(ret)) { + x.add(new IdInterfaceException(id, m, "getter methods must return Serializable types")); + } + break; + case 1: // then setter + if (name.startsWith("set") && name.length() == 3) { + x.add(new IdInterfaceException(id, m, "setter methods must have a property name following 'set' prefix")); + } + if (name.startsWith("with") && name.length() == 4) { + x.add(new IdInterfaceException(id, m, "setter methods must have a property name following 'with' prefix")); + } + if (!void.class.equals(ret) && !id.equals(ret)) { + x.add(new IdInterfaceException(id, m, + "setter methods not returning void may only return the same type as their id interface")); + } + Class arg = args[0]; + if (!Serializable.class.isAssignableFrom(arg)) { + x.add(new IdInterfaceException(id, m, "setter methods must take exactly one Serializable type")); + } + break; + default: + x.add(new IdInterfaceException(id, m, + "id interface methods may only take zero parameters for a getter or one parameter for a setter; found " + + args.length)); + } + } + + if (x.getCount() > 0) { + throw x; + } + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdFactory.java index bb56034ec..9d5626c67 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdFactory.java @@ -1,19 +1,63 @@ package org.springframework.data.cassandra.repository.support; +import java.io.Serializable; import java.lang.reflect.Proxy; import org.springframework.data.cassandra.repository.MapId; import org.springframework.util.Assert; +/** + * Factory class for producing implementations of given id interfaces. For restrictions on id interfaces definitions, + * see {@link IdInterfaceValidator#validate(Class)}. + * + * @see IdInterfaceValidator#validate(Class) + * @author Matthew T. Adams + */ @SuppressWarnings("unchecked") public class MapIdFactory { - public static T id(Class idInterface) { + /** + * Produces an implementation of the given id interface type using the type's class loader. For restrictions on id + * interfaces definitions, see {@link IdInterfaceValidator#validate(Class)}. Returns an implementation of the given + * interface that also implements {@link MapId} and {@link Serializable}, so it can be cast as such if necessary. + * + * @param idInterface The type of the id interface. + * @return An implementation of the given interface that also implements {@link MapId} and {@link Serializable}. + * @see IdInterfaceValidator#validate(Class) + */ + public static T id(Class idInterface) { Assert.notNull(idInterface); return id(idInterface, idInterface.getClassLoader()); } - public static T id(Class idInterface, ClassLoader loader) { - return (T) Proxy.newProxyInstance(loader, new Class[] { idInterface }, new MapIdProxyDelegate(idInterface)); + /** + * Produces an implementation of the given class loader. For restrictions on id interfaces definitions, see + * {@link IdInterfaceValidator#validate(Class)}. Returns an implementation of the given interface that also implements + * {@link MapId} and {@link Serializable}, so it can be cast as such if necessary. + * + * @param idInterface The type of the id interface. + * @return An implementation of the given interface that also implements {@link MapId} and {@link Serializable}. + * @see IdInterfaceValidator#validate(Class) + */ + public static T id(Class idInterface, ClassLoader loader) { + if (MapId.class.equals(idInterface)) { + return (T) new BasicMapId(); + } + + IdInterfaceValidator.validate(idInterface); + + Class[] interfaces = idInterface.getInterfaces(); + switch (interfaces.length) { + case 0: + interfaces = new Class[] { idInterface, MapId.class, Serializable.class }; + break; + case 1: + Class other = interfaces[0].equals(Serializable.class) ? MapId.class : Serializable.class; + interfaces = new Class[] { idInterface, interfaces[0], other }; + break; + default: + interfaces = new Class[] { idInterface, interfaces[0], interfaces[1] }; + } + return (T) Proxy.newProxyInstance(loader, interfaces, new MapIdProxyDelegate(idInterface)); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdProxyDelegate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdProxyDelegate.java index a888d54a3..3a77367c1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdProxyDelegate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/MapIdProxyDelegate.java @@ -10,77 +10,15 @@ import java.util.Map; import org.springframework.data.cassandra.repository.MapId; import org.springframework.util.StringUtils; +/** + * Delegate class for dynamic proxies of id interfaces; delegates to {@link BasicMapId}. + * + * @see MapIdFactory#id(Class) + * @see MapIdFactory#id(Class, ClassLoader) + * @author Matthew T. Adams + */ class MapIdProxyDelegate implements InvocationHandler { - static class Signature { - String name; - Class[] argTypes; - Class returnType; - - Signature(Method method, boolean includeReturnType) { - this(method.getName(), method.getParameterTypes(), includeReturnType ? method.getReturnType() : null); - } - - Signature(String name, Class[] argTypes, Class returnType) { - this.name = name; - this.argTypes = argTypes; - this.returnType = returnType; - } - - @Override - public String toString() { - return String.format("%s %s(%s)", returnType, name, Arrays.toString(argTypes)); - } - - @Override - public boolean equals(Object that) { - if (that == null) { - return false; - } - if (this == that) { - return true; - } - if (!(that instanceof Signature)) { - return false; - } - Signature that_ = (Signature) that; - if (!this.name.equals(that_.name)) { - return false; - } - if ((this.argTypes == null && that_.argTypes != null) || (this.argTypes != null && that_.argTypes == null)) { - return false; - } - if (this.argTypes != null) { - if (this.argTypes.length != that_.argTypes.length) { - return false; - } - for (int i = 0; i < this.argTypes.length; i++) { - if (!this.argTypes[i].equals(that_.argTypes[i])) { - return false; - } - } - } - if (this.returnType == null) { - return that_.returnType == null; - } - return this.returnType.equals(that_.returnType); - } - - @Override - public int hashCode() { - int hash = 37 ^ name.hashCode(); - if (argTypes != null) { - for (Class c : argTypes) { - hash ^= c.hashCode(); - } - } - if (returnType != null) { - hash ^= returnType.hashCode(); - } - return hash; - } - } - private static final Map MAP_ID_SIGNATURES; static { @@ -92,10 +30,10 @@ class MapIdProxyDelegate implements InvocationHandler { } } - MapId delegate = new BasicMapId(); - Class idInterface; + private MapId delegate = new BasicMapId(); + private Class idInterface; - MapIdProxyDelegate(Class idInterface) { + public MapIdProxyDelegate(Class idInterface) { this.idInterface = idInterface; } @@ -119,11 +57,11 @@ class MapIdProxyDelegate implements InvocationHandler { return invokeGetter(method); } - private boolean isMapIdMethod(Method method) { + public boolean isMapIdMethod(Method method) { return MAP_ID_SIGNATURES.containsKey(new Signature(method, true)); } - private Serializable invokeGetter(Method method) { + public Serializable invokeGetter(Method method) { String name = method.getName(); if (name.startsWith("get")) { if (name.length() == 3) { @@ -136,7 +74,7 @@ class MapIdProxyDelegate implements InvocationHandler { return delegate.get(name); } - private void invokeSetter(Method method, Object value) { + public void invokeSetter(Method method, Object value) { String name = method.getName(); int minLength = 1; boolean isSet = name.startsWith("set"); @@ -164,3 +102,72 @@ class MapIdProxyDelegate implements InvocationHandler { delegate.put(name, (Serializable) value); } } + +class Signature { + String name; + Class[] argTypes; + Class returnType; + + Signature(Method method, boolean includeReturnType) { + this(method.getName(), method.getParameterTypes(), includeReturnType ? method.getReturnType() : null); + } + + Signature(String name, Class[] argTypes, Class returnType) { + this.name = name; + this.argTypes = argTypes; + this.returnType = returnType; + } + + @Override + public String toString() { + return String.format("%s %s(%s)", returnType, name, Arrays.toString(argTypes)); + } + + @Override + public boolean equals(Object that) { + if (that == null) { + return false; + } + if (this == that) { + return true; + } + if (!(that instanceof Signature)) { + return false; + } + Signature that_ = (Signature) that; + if (!this.name.equals(that_.name)) { + return false; + } + if ((this.argTypes == null && that_.argTypes != null) || (this.argTypes != null && that_.argTypes == null)) { + return false; + } + if (this.argTypes != null) { + if (this.argTypes.length != that_.argTypes.length) { + return false; + } + for (int i = 0; i < this.argTypes.length; i++) { + if (!this.argTypes[i].equals(that_.argTypes[i])) { + return false; + } + } + } + if (this.returnType == null) { + return that_.returnType == null; + } + return this.returnType.equals(that_.returnType); + } + + @Override + public int hashCode() { + int hash = 37 ^ name.hashCode(); + if (argTypes != null) { + for (Class c : argTypes) { + hash ^= c.hashCode(); + } + } + if (returnType != null) { + hash ^= returnType.hashCode(); + } + return hash; + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTest.java index f59a2c988..db222d64f 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mapping/mapid/proxy/CassandraTemplateMapIdProxyDelegateIntegrationTest.java @@ -75,7 +75,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTest extends assertNull(t.selectOneById(SinglePkc.class, id)); } - public interface SinglePkcId extends MapId { + public interface SinglePkcId { SinglePkcId key(String key); String key(); @@ -149,7 +149,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTest extends assertNull(t.selectOneById(MultiPkc.class, id)); } - public interface MultiPkcId extends MapId { + public interface MultiPkcId { MultiPkcId key0(String key0); String key0(); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/unit/mapidfactory/MapIdFactoryTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/unit/mapidfactory/MapIdFactoryTest.java index 0ef97f44b..0d199fe19 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/unit/mapidfactory/MapIdFactoryTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/unit/mapidfactory/MapIdFactoryTest.java @@ -3,27 +3,32 @@ package org.springframework.data.cassandra.test.unit.mapidfactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.springframework.data.cassandra.repository.support.IdInterfaceValidator.validate; import static org.springframework.data.cassandra.repository.support.MapIdFactory.id; +import java.io.Serializable; import java.util.Random; import org.junit.Test; import org.springframework.data.cassandra.repository.MapId; +import org.springframework.data.cassandra.repository.support.IdInterfaceExceptions; public class MapIdFactoryTest { - static interface MyId extends MapId { - MyId string(String s); + interface HappyExtendingMapIdAndSerializable extends MapId, Serializable { + HappyExtendingMapIdAndSerializable string(String s); void setString(String s); - MyId withString(String s); + HappyExtendingMapIdAndSerializable withString(String s); String string(); String getString(); - MyId number(Integer i); + HappyExtendingMapIdAndSerializable number(Integer i); void setNumber(Integer i); @@ -33,12 +38,12 @@ public class MapIdFactoryTest { } @Test - public void test() { + public void testHappyExtendingMapId() { Random r = new Random(); String s = "" + r.nextInt(); Integer i = new Integer(r.nextInt()); - MyId id = id(MyId.class); + HappyExtendingMapIdAndSerializable id = id(HappyExtendingMapIdAndSerializable.class); assertNull(id.string()); assertNull(id.number()); @@ -50,7 +55,7 @@ public class MapIdFactoryTest { assertEquals(i, id.number()); assertEquals(i, id.get("number")); - MyId returned = null; + HappyExtendingMapIdAndSerializable returned = null; returned = id.number(i = r.nextInt()); assertSame(returned, id); @@ -95,4 +100,168 @@ public class MapIdFactoryTest { assertNull(id.number()); assertNull(id.get("number")); } + + interface HappyExtendingNothing { + HappyExtendingNothing string(String s); + + void setString(String s); + + HappyExtendingNothing withString(String s); + + String string(); + + String getString(); + + HappyExtendingNothing number(Integer i); + + void setNumber(Integer i); + + Integer number(); + + Integer getNumber(); + } + + @Test + public void testHappyExtendingNothing() { + Random r = new Random(); + String s = "" + r.nextInt(); + Integer i = new Integer(r.nextInt()); + + HappyExtendingNothing id = id(HappyExtendingNothing.class); + + assertTrue(id instanceof Serializable); + assertTrue(id instanceof MapId); + MapId mapid = (MapId) id; + + assertNull(id.string()); + assertNull(id.number()); + assertNull(id.getString()); + assertNull(id.getNumber()); + + id.setNumber(i); + assertEquals(i, id.getNumber()); + assertEquals(i, id.number()); + assertEquals(i, mapid.get("number")); + + HappyExtendingNothing returned = null; + + returned = id.number(i = r.nextInt()); + assertSame(returned, id); + assertEquals(i, id.getNumber()); + assertEquals(i, id.number()); + assertEquals(i, mapid.get("number")); + + mapid.put("number", i = r.nextInt()); + assertEquals(i, id.getNumber()); + assertEquals(i, id.number()); + assertEquals(i, mapid.get("number")); + + id.setString(s); + assertEquals(s, id.getString()); + assertEquals(s, id.string()); + assertEquals(s, mapid.get("string")); + + returned = id.string(s = "" + r.nextInt()); + assertSame(returned, id); + assertEquals(s, id.getString()); + assertEquals(s, id.string()); + assertEquals(s, mapid.get("string")); + + returned = id.withString(s = "" + r.nextInt()); + assertSame(returned, id); + assertEquals(s, id.getString()); + assertEquals(s, id.string()); + assertEquals(s, mapid.get("string")); + + mapid.put("string", s = "" + r.nextInt()); + assertEquals(s, id.getString()); + assertEquals(s, id.string()); + assertEquals(s, mapid.get("string")); + + id.setString(null); + assertNull(id.getString()); + assertNull(id.string()); + assertNull(mapid.get("string")); + + id.setNumber(null); + assertNull(id.getNumber()); + assertNull(id.number()); + assertNull(mapid.get("number")); + } + + class IdClass {} + + interface Foo {} + + interface IdExtendingNotMapId extends Foo {} + + interface LiteralGet { + String get(); + } + + interface GetterReturningVoid { + void getString(); + } + + interface GetReturningVoid { + void string(); + } + + interface GetReturningNonSerializable { + Object getFoo(); + } + + interface MethodWithMoreThanOneArgument { + void foo(Object a, Object b); + } + + interface LiteralSet { + void set(String s); + } + + interface LiteralWith { + void with(String s); + } + + interface SetterMethodNotReturningVoidOrThis { + String string(String s); + } + + interface SetMethodNotReturningVoidOrThis { + String setString(String s); + } + + interface WithMethodNotReturningVoidOrThis { + String withString(String s); + } + + interface SetterMethodTakingNonSerializable { + void string(Object o); + } + + interface SetMethodTakingNonSerializable { + void string(Object o); + } + + interface WithMethodTakingNonSerializable { + void string(Object o); + } + + @Test + public void testUnhappies() { + Class[] interfaces = new Class[] { IdClass.class, IdExtendingNotMapId.class, LiteralGet.class, + GetterReturningVoid.class, GetReturningVoid.class, GetReturningNonSerializable.class, + MethodWithMoreThanOneArgument.class, LiteralSet.class, LiteralWith.class, + SetterMethodNotReturningVoidOrThis.class, SetMethodNotReturningVoidOrThis.class, + WithMethodNotReturningVoidOrThis.class, SetterMethodTakingNonSerializable.class, + SetMethodTakingNonSerializable.class, WithMethodTakingNonSerializable.class }; + for (Class i : interfaces) { + try { + validate(i); + fail("should've caught IdInterfaceException validating interface " + i); + } catch (IdInterfaceExceptions e) { + assertEquals(1, e.getCount()); + } + } + } }