View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.proxy2.impl;
19  
20  import java.io.Serializable;
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Method;
23  import java.text.ParsePosition;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.lang3.ArrayUtils;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.Validate;
33  import org.apache.commons.lang3.builder.HashCodeBuilder;
34  import org.apache.commons.lang3.reflect.MethodUtils;
35  import org.apache.commons.lang3.tuple.Pair;
36  
37  /**
38   * A class for capturing the signature of a method (its name and parameter types).
39   * 
40   * @since 2.0
41   */
42  public class MethodSignature implements Serializable
43  {
44      private static final long serialVersionUID = 1L;
45  
46      private static final Map<Class<?>, Character> PRIMITIVE_ABBREVIATIONS;
47      private static final Map<Character, Class<?>> REVERSE_ABBREVIATIONS;
48      static
49      {
50          final Map<Class<?>, Character> primitiveAbbreviations = new HashMap<Class<?>, Character>();
51          primitiveAbbreviations.put(Boolean.TYPE, Character.valueOf('Z'));
52          primitiveAbbreviations.put(Byte.TYPE, Character.valueOf('B'));
53          primitiveAbbreviations.put(Short.TYPE, Character.valueOf('S'));
54          primitiveAbbreviations.put(Integer.TYPE, Character.valueOf('I'));
55          primitiveAbbreviations.put(Character.TYPE, Character.valueOf('C'));
56          primitiveAbbreviations.put(Long.TYPE, Character.valueOf('J'));
57          primitiveAbbreviations.put(Float.TYPE, Character.valueOf('F'));
58          primitiveAbbreviations.put(Double.TYPE, Character.valueOf('D'));
59          primitiveAbbreviations.put(Void.TYPE, Character.valueOf('V'));
60          final Map<Character, Class<?>> reverseAbbreviations = new HashMap<Character, Class<?>>();
61          for (Map.Entry<Class<?>, Character> e : primitiveAbbreviations.entrySet())
62          {
63              reverseAbbreviations.put(e.getValue(), e.getKey());
64          }
65          PRIMITIVE_ABBREVIATIONS = Collections.unmodifiableMap(primitiveAbbreviations);
66          REVERSE_ABBREVIATIONS = Collections.unmodifiableMap(reverseAbbreviations);
67      }
68  
69      private static void appendTo(StringBuilder buf, Class<?> type)
70      {
71          if (type.isPrimitive())
72          {
73              buf.append(PRIMITIVE_ABBREVIATIONS.get(type));
74          }
75          else if (type.isArray())
76          {
77              buf.append('[');
78              appendTo(buf, type.getComponentType());
79          }
80          else
81          {
82              buf.append('L').append(type.getName().replace('.', '/')).append(';');
83          }
84      }
85  
86      private static class SignaturePosition extends ParsePosition
87      {
88          SignaturePosition()
89          {
90              super(0);
91          }
92  
93          SignaturePosition next()
94          {
95              return plus(1);
96          }
97  
98          SignaturePosition plus(int addend)
99          {
100             setIndex(getIndex() + addend);
101             return this;
102         }
103     }
104 
105     private static Pair<String, Class<?>[]> parse(String internal)
106     {
107         Validate.notBlank(internal, "Cannot parse blank method signature");
108         final SignaturePosition pos = new SignaturePosition();
109         int lparen = internal.indexOf('(', pos.getIndex());
110         Validate.isTrue(lparen > 0, "Method signature \"%s\" requires parentheses", internal);
111         final String name = internal.substring(0, lparen).trim();
112         Validate.notBlank(name, "Method signature \"%s\" has blank name", internal);
113 
114         pos.setIndex(lparen + 1);
115 
116         boolean complete = false;
117         final List<Class<?>> params = new ArrayList<Class<?>>();
118         while (pos.getIndex() < internal.length())
119         {
120             final char c = internal.charAt(pos.getIndex());
121             if (Character.isWhitespace(c))
122             {
123                 pos.next();
124                 continue;
125             }
126             final Character k = Character.valueOf(c);
127             if (REVERSE_ABBREVIATIONS.containsKey(k))
128             {
129                 params.add(REVERSE_ABBREVIATIONS.get(k));
130                 pos.next();
131                 continue;
132             }
133             if (')' == c)
134             {
135                 complete = true;
136                 pos.next();
137                 break;
138             }
139             try
140             {
141                 params.add(parseType(internal, pos));
142             }
143             catch (ClassNotFoundException e)
144             {
145                 throw new IllegalArgumentException(String.format("Method signature \"%s\" references unknown type",
146                         internal), e);
147             }
148         }
149         Validate.isTrue(complete, "Method signature \"%s\" is incomplete", internal);
150         Validate.isTrue(StringUtils.isBlank(internal.substring(pos.getIndex())),
151                 "Method signature \"%s\" includes unrecognized content beyond end", internal);
152 
153         return Pair.of(name, params.toArray(ArrayUtils.EMPTY_CLASS_ARRAY));
154     }
155 
156     private static Class<?> parseType(String internal, SignaturePosition pos) throws ClassNotFoundException
157     {
158         final int here = pos.getIndex();
159         final char c = internal.charAt(here);
160 
161         switch (c)
162         {
163         case '[':
164             pos.next();
165             final Class<?> componentType = parseType(internal, pos);
166             return Array.newInstance(componentType, 0).getClass();
167         case 'L':
168             pos.next();
169             final int type = pos.getIndex();
170             final int semi = internal.indexOf(';', type);
171             Validate.isTrue(semi > 0, "Type at index %d of method signature \"%s\" not terminated by semicolon",
172                     Integer.valueOf(here), internal);
173             final String className = internal.substring(type, semi).replace('/', '.');
174             Validate.notBlank(className, "Invalid classname at position %d of method signature \"%s\"",
175                     Integer.valueOf(type), internal);
176             pos.setIndex(semi + 1);
177             return Class.forName(className);
178         default:
179             throw new IllegalArgumentException(String.format(
180                     "Unexpected character at index %d of method signature \"%s\"",
181                     Integer.valueOf(here), internal));
182         }
183     }
184 
185     //******************************************************************************************************************
186     // Fields
187     //******************************************************************************************************************
188 
189     /**
190      * Stored as a Java method descriptor minus return type.
191      */
192     private final String internal;
193 
194     //******************************************************************************************************************
195     // Constructors
196     //******************************************************************************************************************
197 
198     /**
199      * Create a new MethodSignature instance.
200      * 
201      * @param method
202      */
203     public MethodSignature(Method method)
204     {
205         final StringBuilder buf = new StringBuilder(method.getName()).append('(');
206         for (Class<?> p : method.getParameterTypes())
207         {
208             appendTo(buf, p);
209         }
210         buf.append(')');
211         this.internal = buf.toString();
212     }
213 
214     //******************************************************************************************************************
215     // Methods
216     //******************************************************************************************************************
217 
218     /**
219      * Get the corresponding {@link Method} instance from the specified {@link Class}.
220      * 
221      * @param type
222      * @return Method
223      */
224     public Method toMethod(Class<?> type)
225     {
226         final Pair<String, Class<?>[]> info = parse(internal);
227         return MethodUtils.getAccessibleMethod(type, info.getLeft(), info.getRight());
228     }
229 
230     //******************************************************************************************************************
231     // Canonical Methods
232     //******************************************************************************************************************
233 
234     /**
235      * {@inheritDoc}
236      */
237     @Override
238     public boolean equals(Object o)
239     {
240         if (o == null)
241         {
242             return false;
243         }
244         if (o == this)
245         {
246             return true;
247         }
248         if (o.getClass() != getClass())
249         {
250             return false;
251         }
252         MethodSignature other = (MethodSignature) o;
253         return other.internal.equals(internal);
254     }
255 
256     /**
257      * {@inheritDoc}
258      */
259     @Override
260     public int hashCode()
261     {
262         return new HashCodeBuilder().append(internal).build().intValue();
263     }
264 
265     /**
266      * {@inheritDoc}
267      */
268     @Override
269     public String toString()
270     {
271         return internal;
272     }
273 }