You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by mb...@apache.org on 2013/08/04 00:45:53 UTC

svn commit: r1510072 - in /commons/proper/proxy/branches/version-2.0-work/core/src: main/java/org/apache/commons/proxy2/impl/ test/java/org/apache/commons/proxy2/impl/ test/java/org/apache/commons/proxy2/util/

Author: mbenson
Date: Sat Aug  3 22:45:52 2013
New Revision: 1510072

URL: http://svn.apache.org/r1510072
Log:
use stringified implementation for MethodSignature, make economically serializable

Modified:
    commons/proper/proxy/branches/version-2.0-work/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java
    commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java
    commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/Echo.java
    commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java

Modified: commons/proper/proxy/branches/version-2.0-work/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java
URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java?rev=1510072&r1=1510071&r2=1510072&view=diff
==============================================================================
--- commons/proper/proxy/branches/version-2.0-work/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java (original)
+++ commons/proper/proxy/branches/version-2.0-work/core/src/main/java/org/apache/commons/proxy2/impl/MethodSignature.java Sat Aug  3 22:45:52 2013
@@ -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 @@ import java.util.List;
  * @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 class MethodSignature
      */
     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 @@ public class MethodSignature
             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 class MethodSignature
      */
     public int hashCode()
     {
-        return new HashCodeBuilder().append(name).append(parameterTypes).build();
+        return new HashCodeBuilder().append(internal).build();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        return internal;
     }
 }

Modified: commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java
URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java?rev=1510072&r1=1510071&r2=1510072&view=diff
==============================================================================
--- commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java (original)
+++ commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/impl/MethodSignatureTest.java Sat Aug  3 22:45:52 2013
@@ -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 @@ public class MethodSignatureTest extends
     @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

Modified: commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/Echo.java
URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/Echo.java?rev=1510072&r1=1510071&r2=1510072&view=diff
==============================================================================
--- commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/Echo.java (original)
+++ commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/Echo.java Sat Aug  3 22:45:52 2013
@@ -35,6 +35,10 @@ public interface Echo
 
     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 interface Echo
     public void illegalArgument();
 
     public void ioException() throws IOException;
+
 }

Modified: commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java?rev=1510072&r1=1510071&r2=1510072&view=diff
==============================================================================
--- commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java (original)
+++ commons/proper/proxy/branches/version-2.0-work/core/src/test/java/org/apache/commons/proxy2/util/EchoImpl.java Sat Aug  3 22:45:52 2013
@@ -48,7 +48,7 @@ public class EchoImpl extends AbstractEc
 
     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 @@ public class EchoImpl extends AbstractEc
         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;