001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.proxy2.impl;
019
020import java.io.Serializable;
021import java.lang.reflect.Array;
022import java.lang.reflect.Method;
023import java.text.ParsePosition;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.lang3.ArrayUtils;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.commons.lang3.Validate;
033import org.apache.commons.lang3.builder.HashCodeBuilder;
034import org.apache.commons.lang3.reflect.MethodUtils;
035import org.apache.commons.lang3.tuple.Pair;
036
037/**
038 * A class for capturing the signature of a method (its name and parameter types).
039 * 
040 * @since 2.0
041 */
042public class MethodSignature implements Serializable
043{
044    private static final long serialVersionUID = 1L;
045
046    private static final Map<Class<?>, Character> PRIMITIVE_ABBREVIATIONS;
047    private static final Map<Character, Class<?>> REVERSE_ABBREVIATIONS;
048    static
049    {
050        final Map<Class<?>, Character> primitiveAbbreviations = new HashMap<Class<?>, Character>();
051        primitiveAbbreviations.put(Boolean.TYPE, Character.valueOf('Z'));
052        primitiveAbbreviations.put(Byte.TYPE, Character.valueOf('B'));
053        primitiveAbbreviations.put(Short.TYPE, Character.valueOf('S'));
054        primitiveAbbreviations.put(Integer.TYPE, Character.valueOf('I'));
055        primitiveAbbreviations.put(Character.TYPE, Character.valueOf('C'));
056        primitiveAbbreviations.put(Long.TYPE, Character.valueOf('J'));
057        primitiveAbbreviations.put(Float.TYPE, Character.valueOf('F'));
058        primitiveAbbreviations.put(Double.TYPE, Character.valueOf('D'));
059        primitiveAbbreviations.put(Void.TYPE, Character.valueOf('V'));
060        final Map<Character, Class<?>> reverseAbbreviations = new HashMap<Character, Class<?>>();
061        for (Map.Entry<Class<?>, Character> e : primitiveAbbreviations.entrySet())
062        {
063            reverseAbbreviations.put(e.getValue(), e.getKey());
064        }
065        PRIMITIVE_ABBREVIATIONS = Collections.unmodifiableMap(primitiveAbbreviations);
066        REVERSE_ABBREVIATIONS = Collections.unmodifiableMap(reverseAbbreviations);
067    }
068
069    private static void appendTo(StringBuilder buf, Class<?> type)
070    {
071        if (type.isPrimitive())
072        {
073            buf.append(PRIMITIVE_ABBREVIATIONS.get(type));
074        }
075        else if (type.isArray())
076        {
077            buf.append('[');
078            appendTo(buf, type.getComponentType());
079        }
080        else
081        {
082            buf.append('L').append(type.getName().replace('.', '/')).append(';');
083        }
084    }
085
086    private static class SignaturePosition extends ParsePosition
087    {
088        SignaturePosition()
089        {
090            super(0);
091        }
092
093        SignaturePosition next()
094        {
095            return plus(1);
096        }
097
098        SignaturePosition plus(int addend)
099        {
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}