diff --git a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java index 10da3ac..e127d22 100644 --- a/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java +++ b/org.eclipse.osgitech.rest/src/main/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyFactory.java @@ -251,6 +251,8 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< .forEach(tv -> { writer.visitFormalTypeParameter(tv.getName()); SignatureVisitor cb = writer.visitClassBound(); + // Class bounds are special and do not require a visitEnd + // even if a new class context is set Arrays.stream(tv.getBounds()).forEach(b -> visitTypeParameter(b, cb, typeInfo, context)); cb.visitEnd(); }); @@ -266,12 +268,14 @@ private static String generateGenericClassSignature(Class delegateClazz, Map< SignatureVisitor iv = writer.visitInterface(); iv.visitClassType(Type.getInternalName(contract)); for(java.lang.reflect.Type t : typeInfo.get(contract.getName()).getActualTypeArguments()) { + SignatureVisitor v; if(TypeVariable.class.isInstance(t)) { - visitTypeParameter(t, iv, typeInfo, context); + v = iv; } else { - SignatureVisitor tav = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(t, tav, typeInfo, context); - tav.visitEnd(); + v = iv.visitTypeArgument(SignatureVisitor.INSTANCEOF); + } + if(visitTypeParameter(t, v, typeInfo, context)) { + v.visitEnd(); } } iv.visitEnd(); @@ -338,6 +342,7 @@ private static Stream> toTypeVariables(java.lang.reflect.Type t) * @param sv - the visitor to update with type information * @param typeInfo - the known type name to type information mapping * @param context - A mapping of type names to the class which defines them + * @return true if a new class type has been established and an additional close is needed */ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVisitor sv, Map typeInfo, Map context) { if(t instanceof Class) { @@ -346,8 +351,9 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis sv.visitBaseType(Type.getDescriptor(clazz).charAt(0)); } else if (clazz.isArray()) { SignatureVisitor av = sv.visitArrayType(); - visitTypeParameter(clazz.getComponentType(), av, typeInfo, context); - // Do not visit the end + if(visitTypeParameter(clazz.getComponentType(), av, typeInfo, context)) { + av.visitEnd(); + } } else { sv.visitClassType(Type.getInternalName(clazz)); return true; @@ -357,10 +363,11 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis sv.visitClassType(Type.getInternalName((Class)pt.getRawType())); Arrays.stream(pt.getActualTypeArguments()).forEach(ta -> { SignatureVisitor tav = sv.visitTypeArgument(SignatureVisitor.INSTANCEOF); - visitTypeParameter(ta, tav, typeInfo, context); - // Here we must visit the end as we created a new class type context - tav.visitEnd(); + if(visitTypeParameter(ta, tav, typeInfo, context)) { + tav.visitEnd(); + } }); + return true; } else if (t instanceof TypeVariable) { TypeVariable tv = (TypeVariable) t; t = getPossibleReifiedTypeFor((TypeVariable)t, typeInfo, context); @@ -383,8 +390,11 @@ private static boolean visitTypeParameter(java.lang.reflect.Type t, SignatureVis tav = sv.visitTypeArgument(SignatureVisitor.EXTENDS); types = wt.getUpperBounds(); } - Arrays.stream(types).forEach(ty -> visitTypeParameter(ty, tav, typeInfo, context)); - // Do not visit the end + Arrays.stream(types).forEach(ty -> { + if(visitTypeParameter(ty, tav, typeInfo, context)) { + tav.visitEnd(); + } + }); } else { throw new IllegalArgumentException("Unhandled generic type " + t.getClass() + " " + t.toString()); } diff --git a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java index 840677b..22cd79f 100644 --- a/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java +++ b/org.eclipse.osgitech.rest/src/test/java/org/eclipse/osgitech/rest/proxy/ExtensionProxyTest.java @@ -496,6 +496,100 @@ public void testMultiInterfaceMapper() throws Exception { } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplex() throws Exception { + + TestMultiInterfaceGenericComplex mi = new TestMultiInterfaceGenericComplex<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplex", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Object.class}, tv.getBounds()); + + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("ignore me", Object.class, null, null, null, null, baos); + + assertArrayEquals(new byte[]{0x42}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals("tada", ((MessageBodyReader) instance).readFrom(Object.class, null, null, null, null, bais)); + + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testMultiInterfaceGenericComplexTwo() throws Exception { + + TestMultiInterfaceGenericComplexTwo mi = new TestMultiInterfaceGenericComplexTwo<>(); + + // Deliberately re-order the interfaces relative to the implements clause + Class proxyClazz = pcl.define("test.MultiInterfaceGenericComplexTwo", mi, + Arrays.asList(MessageBodyWriter.class, MessageBodyReader.class)); + + assertTrue(MessageBodyReader.class.isAssignableFrom(proxyClazz)); + assertTrue(MessageBodyWriter.class.isAssignableFrom(proxyClazz)); + Type[] genericInterfaces = proxyClazz.getGenericInterfaces(); + + assertEquals(2, genericInterfaces.length); + + assertTrue(genericInterfaces[0] instanceof ParameterizedType); + ParameterizedType pt = (ParameterizedType) genericInterfaces[0]; + assertEquals(MessageBodyWriter.class, pt.getRawType()); + TypeVariable tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("W", tv.getName()); + assertArrayEquals(new Type[] {CharSequence.class}, tv.getBounds()); + + assertTrue(genericInterfaces[1] instanceof ParameterizedType); + pt = (ParameterizedType) genericInterfaces[1]; + assertEquals(MessageBodyReader.class, pt.getRawType()); + tv = (TypeVariable) pt.getActualTypeArguments()[0]; + assertEquals("R", tv.getName()); + assertArrayEquals(new Type[] {Number.class}, tv.getBounds()); + + Object instance = proxyClazz.getConstructor(Supplier.class) + .newInstance((Supplier) () -> mi); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ((MessageBodyWriter) instance).writeTo("banana", CharSequence.class, null, null, null, null, baos); + + // 4 characters, "anan" + assertArrayEquals(new byte[]{0,4,97,110,97,110}, baos.toByteArray()); + + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + + + assertEquals(17, ((MessageBodyReader) instance).readFrom(Number.class, null, null, null, null, bais)); + + } + @Test public void testMultiInterfaceMapperJerseyStyle() throws Exception {