/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.lang; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.ParameterTypes; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.MetaClassHelper; import java.lang.reflect.Modifier; /** * Represents a Method on a Java object a little like {@link java.lang.reflect.Method} * except without using reflection to invoke the method * * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> * @author Alex Tkachman */ public abstract class MetaMethod extends ParameterTypes implements Cloneable { private String signature; private String mopName; /** * Constructor for a metamethod with an empty parameter list */ public MetaMethod() { } /** *Constructor wit a list of parameter classes * * @param pt A list of parameters types */ public MetaMethod(Class [] pt) { super (pt); } /** *Returns the modifiers for this method * * @return modifiers as an int. */ public abstract int getModifiers(); /** * Returns the name of the method represented by this class * * @return name of this method */ public abstract String getName(); /** * Access the return type for this method * *@return the return type of this method */ public abstract Class getReturnType(); /** * Gets the class where this method is declared * * @return class of this method */ public abstract CachedClass getDeclaringClass(); /** * Invoke this method * * @param object The object this method should be invoked on * @param arguments The arguments for the method if applicable * @return The return value of the invocation */ public abstract Object invoke(Object object, Object[] arguments); /** * Checks that the given parameters are valid to call this method * * @param arguments the arguments to check * @throws IllegalArgumentException if the parameters are not valid */ public void checkParameters(Class[] arguments) { // lets check that the argument types are valid if (!isValidMethod(arguments)) { throw new IllegalArgumentException( "Parameters to method: " + getName() + " do not match types: " + InvokerHelper.toString(getParameterTypes()) + " for arguments: " + InvokerHelper.toString(arguments)); } } /** *Returns true if this this metamethod represents the same method as the argument. * * @param method A metaMethod instance * @return true if method is for the same method as this method, false otherwise. */ public boolean isMethod(MetaMethod method) { return getName().equals(method.getName()) && getModifiers() == method.getModifiers() && getReturnType().equals(method.getReturnType()) && equal(getParameterTypes(), method.getParameterTypes()); } protected static boolean equal(CachedClass[] a, Class[] b) { if (a.length == b.length) { for (int i = 0, size = a.length; i < size; i++) { if (!a[i].getTheClass().equals(b[i])) { return false; } } return true; } return false; } protected static boolean equal(CachedClass[] a, CachedClass[] b) { if (a.length == b.length) { for (int i = 0, size = a.length; i < size; i++) { if (a[i] != b[i]) { return false; } } return true; } return false; } /** * Returns a string representation of this method */ public String toString() { return super.toString() + "[name: " + getName() + " params: " + InvokerHelper.toString(getParameterTypes()) + " returns: " + getReturnType() + " owner: " + getDeclaringClass() + "]"; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new GroovyRuntimeException("This should never happen", e); } } /** * Returns whether or not this method is static. * @return true if this method is static */ public boolean isStatic() { return (getModifiers() & Modifier.STATIC) != 0; } /** * Returns whether or not this method is abstract. * @return true if this method is abstract */ public boolean isAbstract() { return (getModifiers() & Modifier.ABSTRACT) != 0; } /** * Returns whether or not this method is private. * @return true if this method is private */ public final boolean isPrivate() { return (getModifiers() & Modifier.PRIVATE) != 0; } /** * Returns whether or not this method is protected. * @return true if this method is protected */ public final boolean isProtected() { return (getModifiers() & Modifier.PROTECTED) != 0; } /** * Returns whether or not this method is public. * @return true if this method is public */ public final boolean isPublic() { return (getModifiers() & Modifier.PUBLIC) != 0; } /** * @param method the method to compare against * @return true if the given method has the same name, parameters, return type * and modifiers but may be defined on another type */ public final boolean isSame(MetaMethod method) { return getName().equals(method.getName()) && compatibleModifiers(getModifiers(), method.getModifiers()) && getReturnType().equals(method.getReturnType()) && equal(getParameterTypes(), method.getParameterTypes()); } /** * Checks the compatibility between two modifier masks. Checks that they are equal * with regards to access and static modifier. * * @return true if the modifiers are compatible */ private static boolean compatibleModifiers(int modifiersA, int modifiersB) { int mask = Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC | Modifier.STATIC; return (modifiersA & mask) == (modifiersB & mask); } /** * Returns whether this object is cacheable */ public boolean isCacheable() { return true; } /** * Return a descriptor of this method based on the return type and parameters of this method. */ public String getDescriptor() { return BytecodeHelper.getMethodDescriptor(getReturnType(), getNativeParameterTypes()); } /** * Returns the signature of this method * * @return The signature of this method */ public synchronized String getSignature() { if (signature == null) { CachedClass [] parameters = getParameterTypes(); final String name = getName(); StringBuilder buf = new StringBuilder(name.length()+parameters.length*10); buf.append(getReturnType().getName()); buf.append(' '); buf.append(name); buf.append('('); for (int i = 0; i < parameters.length; i++) { if (i > 0) { buf.append(", "); } buf.append(parameters[i].getName()); } buf.append(')'); signature = buf.toString(); } return signature; } public String getMopName() { if (mopName == null) { String name = getName(); CachedClass declaringClass = getDeclaringClass(); if (Modifier.isPrivate(getModifiers())) mopName = new StringBuffer().append("this$").append(declaringClass.getSuperClassDistance()).append("$").append(name).toString(); else mopName = new StringBuffer().append("super$").append(declaringClass.getSuperClassDistance()).append("$").append(name).toString(); } return mopName; } /** * This method is called when an exception occurs while invoking this method. */ public final RuntimeException processDoMethodInvokeException (Exception e, Object object, Object [] argumentArray) { // if (e instanceof IllegalArgumentException) { // //TODO: test if this is OK with new MOP, should be changed! // // we don't want the exception being unwrapped if it is a IllegalArgumentException // // but in the case it is for example a IllegalThreadStateException, we want the unwrapping // // from the runtime // //Note: the reason we want unwrapping sometimes and sometimes not is that the method // // invocation tries to invoke the method with and then reacts with type transformation // // if the invocation failed here. This is OK for IllegalArgumentException, but it is // // possible that a Reflector will be used to execute the call and then an Exception from inside // // the method is not wrapped in a InvocationTargetException and we will end here. // boolean setReason = e.getClass() != IllegalArgumentException.class || this instanceof org.codehaus.groovy.reflection.GeneratedMetaMethod; // return MetaClassHelper.createExceptionText("failed to invoke method: ", this, object, argumentArray, e, setReason); // } if (e instanceof RuntimeException) return (RuntimeException) e; return MetaClassHelper.createExceptionText("failed to invoke method: ", this, object, argumentArray, e, true); } /** * Invokes the method this object represents. This method is not final but it should be overloaded very carefully and only by generated methods * there is no guarantee that it will be called * * @param object The object the method is to be called at. * @param argumentArray Arguments for the method invocation. * @return The return value of the invoked method. */ public Object doMethodInvoke(Object object, Object[] argumentArray) { argumentArray = coerceArgumentsToClasses(argumentArray); try { return invoke(object, argumentArray); } catch (Exception e) { throw processDoMethodInvokeException(e, object, argumentArray); } } }