1 /*
2 * Copyright 2003-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package groovy.lang;
18
19 import org.codehaus.groovy.classgen.BytecodeHelper;
20 import org.codehaus.groovy.reflection.CachedClass;
21 import org.codehaus.groovy.reflection.GeneratedMetaMethod;
22 import org.codehaus.groovy.reflection.ParameterTypes;
23 import org.codehaus.groovy.runtime.InvokerHelper;
24 import org.codehaus.groovy.runtime.MetaClassHelper;
25
26 import java.lang.reflect.Modifier;
27
28 /**
29 * Represents a Method on a Java object a little like {@link java.lang.reflect.Method}
30 * except without using reflection to invoke the method
31 *
32 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
33 * @author Alex Tkachman
34 * @version $Revision: 15212 $
35 */
36 public abstract class MetaMethod extends ParameterTypes implements Cloneable {
37 private String signature;
38 private String mopName;
39
40 public MetaMethod() {
41 }
42
43 public MetaMethod(Class [] pt) {
44 super (pt);
45 }
46
47 public abstract int getModifiers();
48
49 public abstract String getName();
50
51 public abstract Class getReturnType();
52
53 public abstract CachedClass getDeclaringClass();
54
55 public abstract Object invoke(Object object, Object[] arguments);
56
57 /**
58 * Checks that the given parameters are valid to call this method
59 *
60 * @param arguments the arguments to check
61 * @throws IllegalArgumentException if the parameters are not valid
62 */
63 public void checkParameters(Class[] arguments) {
64 // lets check that the argument types are valid
65 if (!isValidMethod(arguments)) {
66 throw new IllegalArgumentException(
67 "Parameters to method: "
68 + getName()
69 + " do not match types: "
70 + InvokerHelper.toString(getParameterTypes())
71 + " for arguments: "
72 + InvokerHelper.toString(arguments));
73 }
74 }
75
76 public boolean isMethod(MetaMethod method) {
77 return getName().equals(method.getName())
78 && getModifiers() == method.getModifiers()
79 && getReturnType().equals(method.getReturnType())
80 && equal(getParameterTypes(), method.getParameterTypes());
81 }
82
83 protected static boolean equal(CachedClass[] a, Class[] b) {
84 if (a.length == b.length) {
85 for (int i = 0, size = a.length; i < size; i++) {
86 if (!a[i].getTheClass().equals(b[i])) {
87 return false;
88 }
89 }
90 return true;
91 }
92 return false;
93 }
94
95 protected static boolean equal(CachedClass[] a, CachedClass[] b) {
96 if (a.length == b.length) {
97 for (int i = 0, size = a.length; i < size; i++) {
98 if (a[i] != b[i]) {
99 return false;
100 }
101 }
102 return true;
103 }
104 return false;
105 }
106
107 public String toString() {
108 return super.toString()
109 + "[name: "
110 + getName()
111 + " params: "
112 + InvokerHelper.toString(getParameterTypes())
113 + " returns: "
114 + getReturnType()
115 + " owner: "
116 + getDeclaringClass()
117 + "]";
118 }
119
120 public Object clone() {
121 try {
122 return super.clone();
123 }
124 catch (CloneNotSupportedException e) {
125 throw new GroovyRuntimeException("This should never happen", e);
126 }
127 }
128
129 public boolean isStatic() {
130 return (getModifiers() & Modifier.STATIC) != 0;
131 }
132
133 public boolean isAbstract() {
134 return (getModifiers() & Modifier.ABSTRACT) != 0;
135 }
136
137 public final boolean isPrivate() {
138 return (getModifiers() & Modifier.PRIVATE) != 0;
139 }
140
141 public final boolean isProtected() {
142 return (getModifiers() & Modifier.PROTECTED) != 0;
143 }
144
145 public final boolean isPublic() {
146 return (getModifiers() & Modifier.PUBLIC) != 0;
147 }
148
149 /**
150 * @param method the method to compare against
151 * @return true if the given method has the same name, parameters, return type
152 * and modifiers but may be defined on another type
153 */
154 public final boolean isSame(MetaMethod method) {
155 return getName().equals(method.getName())
156 && compatibleModifiers(getModifiers(), method.getModifiers())
157 && getReturnType().equals(method.getReturnType())
158 && equal(getParameterTypes(), method.getParameterTypes());
159 }
160
161 private static boolean compatibleModifiers(int modifiersA, int modifiersB) {
162 int mask = Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC | Modifier.STATIC;
163 return (modifiersA & mask) == (modifiersB & mask);
164 }
165
166 public boolean isCacheable() {
167 return true;
168 }
169
170 public String getDescriptor() {
171 return BytecodeHelper.getMethodDescriptor(getReturnType(), getNativeParameterTypes());
172 }
173
174 public synchronized String getSignature() {
175 if (signature == null) {
176 CachedClass [] parameters = getParameterTypes();
177 final String name = getName();
178 StringBuffer buf = new StringBuffer(name.length()+parameters.length*10);
179 buf.append(getReturnType().getName());
180 //
181 buf.append(' ');
182 buf.append(name);
183 buf.append('(');
184 for (int i = 0; i < parameters.length; i++) {
185 if (i > 0) {
186 buf.append(", ");
187 }
188 buf.append(parameters[i].getName());
189 }
190 buf.append(')');
191 signature = buf.toString();
192 }
193 return signature;
194 }
195
196 public String getMopName() {
197 if (mopName == null) {
198 String name = getName();
199 CachedClass declaringClass = getDeclaringClass();
200 if ((getModifiers() & (Modifier.PUBLIC| Modifier.PROTECTED)) == 0)
201 mopName = new StringBuffer().append("this$").append(declaringClass.getSuperClassDistance()).append("$").append(name).toString();
202 else
203 mopName = new StringBuffer().append("super$").append(declaringClass.getSuperClassDistance()).append("$").append(name).toString();
204 }
205 return mopName;
206 }
207
208 public final RuntimeException processDoMethodInvokeException (Exception e, Object object, Object [] argumentArray) {
209 if (e instanceof IllegalArgumentException) {
210 //TODO: test if this is OK with new MOP, should be changed!
211 // we don't want the exception being unwrapped if it is a IllegalArgumentException
212 // but in the case it is for example a IllegalThreadStateException, we want the unwrapping
213 // from the runtime
214 //Note: the reason we want unwrapping sometimes and sometimes not is that the method
215 // invocation tries to invoke the method with and then reacts with type transformation
216 // if the invocation failed here. This is OK for IllegalArgumentException, but it is
217 // possible that a Reflector will be used to execute the call and then an Exception from inside
218 // the method is not wrapped in a InvocationTargetException and we will end here.
219 boolean setReason = e.getClass() != IllegalArgumentException.class || this instanceof GeneratedMetaMethod;
220 return MetaClassHelper.createExceptionText("failed to invoke method: ", this, object, argumentArray, e, setReason);
221 }
222
223 if (e instanceof RuntimeException)
224 return (RuntimeException) e;
225
226 return MetaClassHelper.createExceptionText("failed to invoke method: ", this, object, argumentArray, e, true);
227 }
228
229 // This method is not final but it should be overloaded very carefully and only by generated methods
230 // there is no guarantee that it will be called
231 public Object doMethodInvoke(Object object, Object[] argumentArray) {
232 argumentArray = coerceArgumentsToClasses(argumentArray);
233 try {
234 return invoke(object, argumentArray);
235 } catch (Exception e) {
236 throw processDoMethodInvokeException(e, object, argumentArray);
237 }
238 }
239 }