001    // Copyright 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.plastic;
016    
017    import java.lang.reflect.Method;
018    import java.lang.reflect.Modifier;
019    import java.util.Arrays;
020    
021    import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
022    
023    /**
024     * Describes a {@link PlasticMethod} in terms of a method name, a set of modifiers
025     * (public, private, static, final, etc.), a return type, types of method arguments,
026     * and types of checked exceptions. Types are represented as Java source names:
027     * either names of primitives ("void", "byte", "long") or fully qualified class names ("java.lang.Object",
028     * "java.lang.Runnable"). ASM refers to this as "class name".
029     * <p>
030     * MethodDescriptions are immutable, and properly implement equals() and hashCode(); they are often used as keys in
031     * Maps.
032     * <p>
033     * The natural sort order for a MethodDescription is ascending order by method name, then descending order by number of
034     * parameters (for the same name). Sort order is not currently specified for overrides of the same method with the same
035     * number of parameters.
036     * <p>
037     * TODO: Handling generic types.
038     */
039    public class MethodDescription implements Comparable<MethodDescription>
040    {
041        /**
042         * The full set of modifier flags for the method.
043         */
044        public final int modifiers;
045    
046        /** The Java source name for the return type, e.g., "void", "short", "java.util.Map", "java.lang.String[]". */
047        public final String returnType;
048    
049        /** The name of the method. */
050        public final String methodName;
051    
052        public final String genericSignature;
053    
054        /**
055         * A non-null array of Java source names for arguments. Do not modify
056         * the contents of this array.
057         */
058        public final String[] argumentTypes;
059    
060        /** A non-null array of Java source names for checked exceptions. Do not modify the contents of this array. */
061        public final String[] checkedExceptionTypes;
062    
063        /**
064         * Convenience constructor for public methods that have no checked exceptions.
065         * 
066         * @param returnType
067         *            return type as type name
068         * @param methodName
069         *            name of method
070         * @param argumentTypes
071         *            type names for arguments
072         */
073        public MethodDescription(String returnType, String methodName, String... argumentTypes)
074        {
075            this(Modifier.PUBLIC, returnType, methodName, argumentTypes, null, null);
076        }
077    
078        /**
079         * @param modifiers
080         * @param returnType
081         *            Java source name for the return type
082         * @param methodName
083         * @param argumentTypes
084         *            may be null
085         * @param genericSignature
086         *            TODO
087         * @param checkedExceptionTypes
088         *            may be null
089         */
090        public MethodDescription(int modifiers, String returnType, String methodName, String[] argumentTypes,
091                String genericSignature, String[] checkedExceptionTypes)
092        {
093            assert PlasticInternalUtils.isNonBlank(returnType);
094            assert PlasticInternalUtils.isNonBlank(methodName);
095    
096            this.modifiers = modifiers;
097            this.returnType = returnType;
098            this.methodName = methodName;
099            this.genericSignature = genericSignature;
100    
101            this.argumentTypes = PlasticInternalUtils.orEmpty(argumentTypes);
102            this.checkedExceptionTypes = PlasticInternalUtils.orEmpty(checkedExceptionTypes);
103        }
104    
105        public MethodDescription withModifiers(int newModifiers)
106        {
107            return new MethodDescription(newModifiers, returnType, methodName, argumentTypes, genericSignature,
108                    checkedExceptionTypes);
109        }
110    
111        /** Creates a MethodDescription from a Java Method. The generic signature will be null. */
112        public MethodDescription(Method method)
113        {
114            this(method.getModifiers(), PlasticUtils.toTypeName(method.getReturnType()), method.getName(), PlasticUtils
115                    .toTypeNames(method.getParameterTypes()), null, PlasticUtils.toTypeNames(method.getExceptionTypes()));
116        }
117    
118        public int compareTo(MethodDescription o)
119        {
120            int result = methodName.compareTo(o.methodName);
121    
122            if (result == 0)
123                result = o.argumentTypes.length - argumentTypes.length;
124    
125            return result;
126        }
127    
128        @Override
129        public int hashCode()
130        {
131            final int prime = 31;
132            int result = 1;
133    
134            result = prime * result + Arrays.hashCode(argumentTypes);
135            result = prime * result + Arrays.hashCode(checkedExceptionTypes);
136            result = prime * result + methodName.hashCode();
137            result = prime * result + modifiers;
138            result = prime * result + (genericSignature == null ? 0 : genericSignature.hashCode());
139    
140            result = prime * result + returnType.hashCode();
141    
142            return result;
143        }
144    
145        @Override
146        public boolean equals(Object obj)
147        {
148            if (this == obj)
149                return true;
150            if (obj == null)
151                return false;
152            if (getClass() != obj.getClass())
153                return false;
154    
155            MethodDescription other = (MethodDescription) obj;
156    
157            if (!methodName.equals(other.methodName))
158                return false;
159    
160            // TODO: I think this tripped me up in Tapestry at some point, as
161            // there were modifiers that cause some problem, such as abstract
162            // or deprecated or something. May need a mask of modifiers we
163            // care about for equals()/hashCode() purposes.
164    
165            if (modifiers != other.modifiers)
166                return false;
167    
168            if (!returnType.equals(other.returnType))
169                return false;
170    
171            if (!Arrays.equals(argumentTypes, other.argumentTypes))
172                return false;
173    
174            if (!PlasticInternalUtils.isEqual(genericSignature, other.genericSignature))
175                return false;
176    
177            if (!Arrays.equals(checkedExceptionTypes, other.checkedExceptionTypes))
178                return false;
179    
180            return true;
181        }
182    
183        @Override
184        public String toString()
185        {
186            StringBuilder builder = new StringBuilder();
187    
188            // TODO: Not 100% sure that methodNode.access is exactly the same
189            // as modifiers. We'll have to see.
190    
191            if (modifiers != 0)
192                builder.append(Modifier.toString(modifiers)).append(" ");
193    
194            builder.append(returnType).append(" ").append(methodName).append("(");
195    
196            String sep = "";
197    
198            for (String name : argumentTypes)
199            {
200                builder.append(sep);
201                builder.append(name);
202    
203                sep = ", ";
204            }
205    
206            builder.append(")");
207    
208            if (genericSignature != null)
209                builder.append(" <").append(genericSignature).append(">");
210    
211            sep = " throws ";
212    
213            for (String name : checkedExceptionTypes)
214            {
215                builder.append(sep);
216                builder.append(name);
217    
218                sep = ", ";
219            }
220    
221            return builder.toString();
222        }
223    
224        /**
225         * A string used to identify the method, containing just the method name and argument types
226         * (but ignoring visibility, return type and thrown exceptions).
227         * 
228         * @return method identifier
229         */
230        public String toShortString()
231        {
232            StringBuilder builder = new StringBuilder(methodName).append("(");
233    
234            String sep = "";
235    
236            for (String name : argumentTypes)
237            {
238                builder.append(sep).append(name);
239    
240                sep = ", ";
241            }
242    
243            return builder.append(")").toString();
244        }
245    }