use stringified implementation for MethodSignature, make economically serializable
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/proxy/branches/version-2.0-work@1510072 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java b/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java
index bfbf268..05cad71 100644
--- a/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java
+++ b/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java
@@ -17,12 +17,22 @@
package org.apache.commons.proxy2.impl;
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-
+import java.io.Serializable;
+import java.lang.reflect.Array;
import java.lang.reflect.Method;
-import java.util.Arrays;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.apache.commons.lang3.tuple.Pair;
/**
* A class for capturing the signature of a method (its name and parameter types).
@@ -30,14 +40,149 @@
* @author James Carman
* @since 1.0
*/
-public class MethodSignature
+public class MethodSignature implements Serializable
{
+ private static final long serialVersionUID = 1L;
+
+ private static final Map<Class<?>, Character> PRIMITIVE_ABBREVIATIONS;
+ private static final Map<Character, Class<?>> REVERSE_ABBREVIATIONS;
+ static
+ {
+ final Map<Class<?>, Character> primitiveAbbreviations = new HashMap<Class<?>, Character>();
+ primitiveAbbreviations.put(Boolean.TYPE, Character.valueOf('Z'));
+ primitiveAbbreviations.put(Byte.TYPE, Character.valueOf('B'));
+ primitiveAbbreviations.put(Short.TYPE, Character.valueOf('S'));
+ primitiveAbbreviations.put(Integer.TYPE, Character.valueOf('I'));
+ primitiveAbbreviations.put(Character.TYPE, Character.valueOf('C'));
+ primitiveAbbreviations.put(Long.TYPE, Character.valueOf('J'));
+ primitiveAbbreviations.put(Float.TYPE, Character.valueOf('F'));
+ primitiveAbbreviations.put(Double.TYPE, Character.valueOf('D'));
+ primitiveAbbreviations.put(Void.TYPE, Character.valueOf('V'));
+ final Map<Character, Class<?>> reverseAbbreviations = new HashMap<Character, Class<?>>();
+ for (Map.Entry<Class<?>, Character> e : primitiveAbbreviations.entrySet())
+ {
+ reverseAbbreviations.put(e.getValue(), e.getKey());
+ }
+ PRIMITIVE_ABBREVIATIONS = Collections.unmodifiableMap(primitiveAbbreviations);
+ REVERSE_ABBREVIATIONS = Collections.unmodifiableMap(reverseAbbreviations);
+ }
+
+ private static void appendTo(StringBuilder buf, Class<?> type)
+ {
+ if (type.isPrimitive())
+ {
+ buf.append(PRIMITIVE_ABBREVIATIONS.get(type));
+ }
+ else if (type.isArray())
+ {
+ buf.append('[');
+ appendTo(buf, type.getComponentType());
+ }
+ else
+ {
+ buf.append('L').append(type.getName().replace('.', '/')).append(';');
+ }
+ }
+
+ private static class SignaturePosition extends ParsePosition
+ {
+ SignaturePosition() {
+ super(0);
+ }
+
+ SignaturePosition next()
+ {
+ return plus(1);
+ }
+
+ SignaturePosition plus(int addend)
+ {
+ setIndex(getIndex() + addend);
+ return this;
+ }
+ }
+
+ private static Pair<String, Class<?>[]> parse(String internal)
+ {
+ Validate.notBlank(internal, "Cannot parse blank method signature");
+ final SignaturePosition pos = new SignaturePosition();
+ int lparen = internal.indexOf('(', pos.getIndex());
+ Validate.isTrue(lparen > 0, "Method signature \"%s\" requires parentheses", internal);
+ final String name = internal.substring(0, lparen).trim();
+ Validate.notBlank(name, "Method signature \"%s\" has blank name", internal);
+
+ pos.setIndex(lparen + 1);
+
+ boolean complete = false;
+ final List<Class<?>> params = new ArrayList<Class<?>>();
+ while (pos.getIndex() < internal.length())
+ {
+ final char c = internal.charAt(pos.getIndex());
+ if (Character.isWhitespace(c)) {
+ pos.next();
+ continue;
+ }
+ final Character k = Character.valueOf(c);
+ if (REVERSE_ABBREVIATIONS.containsKey(k))
+ {
+ params.add(REVERSE_ABBREVIATIONS.get(k));
+ pos.next();
+ continue;
+ }
+ if (')' == c)
+ {
+ complete = true;
+ pos.next();
+ break;
+ }
+ try {
+ params.add(parseType(internal, pos));
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(String.format("Method signature \"%s\" references unknown type",
+ internal), e);
+ }
+ }
+ Validate.isTrue(complete, "Method signature \"%s\" is incomplete", internal);
+ Validate.isTrue(StringUtils.isBlank(internal.substring(pos.getIndex())),
+ "Method signature \"%s\" includes unrecognized content beyond end", internal);
+
+ return Pair.of(name, params.toArray(ArrayUtils.EMPTY_CLASS_ARRAY));
+ }
+
+ private static Class<?> parseType(String internal, SignaturePosition pos) throws ClassNotFoundException {
+ final int here = pos.getIndex();
+ final char c = internal.charAt(here);
+
+ switch (c)
+ {
+ case '[':
+ pos.next();
+ final Class<?> componentType = parseType(internal, pos);
+ return Array.newInstance(componentType, 0).getClass();
+ case 'L':
+ pos.next();
+ final int type = pos.getIndex();
+ final int semi = internal.indexOf(';', type);
+ Validate.isTrue(semi > 0, "Type at index %s of method signature \"%s\" not terminated by semicolon", here,
+ internal);
+ final String className = internal.substring(type, semi).replace('/', '.');
+ Validate.notBlank(className, "Invalid classname at position %s of method signature \"%s\"", type, internal);
+ pos.setIndex(semi + 1);
+ return Class.forName(className);
+ default:
+ throw new IllegalArgumentException(String.format(
+ "Unexpected character at index %s of method signature \"%s\"", here, internal));
+ }
+ }
+
//----------------------------------------------------------------------------------------------------------------------
// Fields
//----------------------------------------------------------------------------------------------------------------------
- private final String name;
- private final List<Class<?>> parameterTypes;
+ /**
+ * Stored as a Java method descriptor minus return type.
+ */
+ private final String internal;
//----------------------------------------------------------------------------------------------------------------------
// Constructors
@@ -50,8 +195,29 @@
*/
public MethodSignature(Method method)
{
- this.name = method.getName();
- this.parameterTypes = Arrays.asList(method.getParameterTypes());
+ final StringBuilder buf = new StringBuilder(method.getName()).append('(');
+ for (Class<?> p : method.getParameterTypes())
+ {
+ appendTo(buf, p);
+ }
+ buf.append(')');
+ this.internal = buf.toString();
+ }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get the corresponding {@link Method} instance
+ * from the specified {@link Class}.
+ * @param type
+ * @return Method
+ */
+ public Method toMethod(Class<?> type)
+ {
+ final Pair<String,Class<?>[]> info = parse(internal);
+ return MethodUtils.getAccessibleMethod(type, info.getLeft(), info.getRight());
}
//----------------------------------------------------------------------------------------------------------------------
@@ -76,10 +242,7 @@
return false;
}
MethodSignature other = (MethodSignature) o;
- return new EqualsBuilder()
- .append(name, other.name)
- .append(parameterTypes, other.parameterTypes)
- .build();
+ return other.internal.equals(internal);
}
/**
@@ -87,6 +250,14 @@
*/
public int hashCode()
{
- return new HashCodeBuilder().append(name).append(parameterTypes).build();
+ return new HashCodeBuilder().append(internal).build();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return internal;
}
}
diff --git a/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java b/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java
index c79b34e..a4471ee 100644
--- a/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java
+++ b/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java
@@ -17,9 +17,14 @@
package org.apache.commons.proxy2.impl;
+import java.lang.reflect.Method;
+
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.commons.proxy2.util.AbstractEcho;
import org.apache.commons.proxy2.util.AbstractTestCase;
import org.apache.commons.proxy2.util.DuplicateEcho;
import org.apache.commons.proxy2.util.Echo;
+import org.apache.commons.proxy2.util.EchoImpl;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -33,12 +38,52 @@
@Test
public void testEquals() throws Exception
{
- final MethodSignature sig = new MethodSignature(Echo.class.getMethod("echoBack", new Class[] {String.class}));
+ final MethodSignature sig = new MethodSignature(Echo.class.getMethod("echoBack", String.class));
assertTrue(sig.equals(sig));
assertFalse(sig.equals("echoBack"));
- assertEquals(sig, new MethodSignature(Echo.class.getMethod("echoBack", new Class[] {String.class})));
- assertEquals(sig, new MethodSignature(DuplicateEcho.class.getMethod("echoBack", new Class[] {String.class})));
- assertFalse(sig.equals(new MethodSignature(Echo.class.getMethod("echoBack", new Class[] {String.class, String.class}))));
- assertFalse(sig.equals(new MethodSignature(Echo.class.getMethod("echo", new Class[] {}))));
+ assertEquals(sig, new MethodSignature(Echo.class.getMethod("echoBack", String.class)));
+ assertEquals(sig, new MethodSignature(DuplicateEcho.class.getMethod("echoBack", String.class)));
+ assertFalse(sig.equals(new MethodSignature(Echo.class.getMethod("echoBack", String.class, String.class))));
+ assertFalse(sig.equals(new MethodSignature(Echo.class.getMethod("echo"))));
+ }
+
+ @Test
+ public void testSerialization() throws Exception
+ {
+ final MethodSignature sig = new MethodSignature(Echo.class.getMethod("echoBack", String.class));
+ assertEquals(sig, SerializationUtils.clone(sig));
+ }
+
+ @Test
+ public void testToString() throws Exception
+ {
+ assertEquals("echo()", new MethodSignature(Echo.class.getMethod("echo")).toString());
+ assertEquals("echoBack(Ljava/lang/String;)", new MethodSignature(Echo.class.getMethod("echoBack", String.class)).toString());
+ assertEquals("echoBack([Ljava/lang/String;)", new MethodSignature(Echo.class.getMethod("echoBack", String[].class)).toString());
+ assertEquals("echoBack([[Ljava/lang/String;)", new MethodSignature(Echo.class.getMethod("echoBack", String[][].class)).toString());
+ assertEquals("echoBack([[[Ljava/lang/String;)", new MethodSignature(Echo.class.getMethod("echoBack", String[][][].class)).toString());
+ assertEquals("echoBack(I)", new MethodSignature(Echo.class.getMethod("echoBack", int.class)).toString());
+ assertEquals("echoBack(Z)", new MethodSignature(Echo.class.getMethod("echoBack", boolean.class)).toString());
+ assertEquals("echoBack(Ljava/lang/String;Ljava/lang/String;)", new MethodSignature(Echo.class.getMethod("echoBack", String.class, String.class)).toString());
+ assertEquals("illegalArgument()", new MethodSignature(Echo.class.getMethod("illegalArgument")).toString());
+ assertEquals("ioException()", new MethodSignature(Echo.class.getMethod("ioException")).toString());
+ }
+
+ @Test
+ public void testToMethod() throws Exception
+ {
+ final MethodSignature sig = new MethodSignature(Echo.class.getMethod("echoBack", String.class));
+
+ assertMethodIs(sig.toMethod(Echo.class), Echo.class, "echoBack", String.class);
+ assertMethodIs(sig.toMethod(AbstractEcho.class), AbstractEcho.class, "echoBack", String.class);
+ assertMethodIs(sig.toMethod(EchoImpl.class), AbstractEcho.class, "echoBack", String.class);
+ assertMethodIs(sig.toMethod(DuplicateEcho.class), DuplicateEcho.class, "echoBack", String.class);
+ }
+
+ private void assertMethodIs(Method method, Class<?> declaredBy, String name, Class<?>... parameterTypes)
+ {
+ assertEquals(declaredBy, method.getDeclaringClass());
+ assertEquals(name, method.getName());
+ assertArrayEquals(parameterTypes, method.getParameterTypes());
}
}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/commons/proxy2/util/Echo.java b/core/src/test/java/org/apache/commons/proxy2/util/Echo.java
index 08749d7..af04e06 100644
--- a/core/src/test/java/org/apache/commons/proxy2/util/Echo.java
+++ b/core/src/test/java/org/apache/commons/proxy2/util/Echo.java
@@ -35,6 +35,10 @@
public String echoBack( String[] messages );
+ public String echoBack( String[][] messages );
+
+ public String echoBack( String[][][] messages );
+
public int echoBack( int i );
public boolean echoBack( boolean b );
@@ -44,4 +48,5 @@
public void illegalArgument();
public void ioException() throws IOException;
+
}
diff --git a/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java b/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java
index 21b0b7b..66169d5 100644
--- a/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java
+++ b/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java
@@ -48,7 +48,7 @@
public String echoBack( String[] messages )
{
- final StringBuffer sb = new StringBuffer();
+ final StringBuilder sb = new StringBuilder();
for( int i = 0; i < messages.length; i++ )
{
String message = messages[i];
@@ -57,6 +57,26 @@
return sb.toString();
}
+ public String echoBack( String[][] messages )
+ {
+ final StringBuilder sb = new StringBuilder();
+ for( int i = 0; i < messages.length; i++ )
+ {
+ sb.append(echoBack(messages[i]));
+ }
+ return sb.toString();
+ }
+
+ public String echoBack( String[][][] messages )
+ {
+ final StringBuilder sb = new StringBuilder();
+ for( int i = 0; i < messages.length; i++ )
+ {
+ sb.append(echoBack(messages[i]));
+ }
+ return sb.toString();
+ }
+
public int echoBack( int i )
{
return i;