/* 
 * 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 java.lang.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.VMCommonLibrarySupport;

import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMMember;
import org.jikesrvm.classloader.RVMMethod;

/**
 * This class must be implemented by the VM vendor. This class models a method.
 * Information about the method can be accessed, and the method can be invoked
 * dynamically.
 * 
 */
public final class Method extends AccessibleObject implements GenericDeclaration, Member {
    private final RVMMethod vmMethod;

    /**
     * Constructor
     */
    Method(RVMMethod vmMethod){
      this.vmMethod = vmMethod;
    }
    
    /**
     * Prevent this class from being instantiated
     */
    private Method(){
      vmMethod = null;
    }
    
    public TypeVariable<Method>[] getTypeParameters() {
      throw new Error("TODO");
    }

    /**
     * <p>
     * Returns the String representation of the method's declaration, including
     * the type parameters.
     * </p>
     * 
     * @return An instance of String.
     * @since 1.5
     */
    public String toGenericString() {
      throw new Error("TODO");
    }

    /**
     * <p>
     * Gets the parameter types as an array of {@link Type} instances, in
     * declaration order. If the method has no parameters, then an empty array
     * is returned.
     * </p>
     * 
     * @return An array of {@link Type} instances.
     * @throws GenericSignatureFormatError if the generic method signature is
     *         invalid.
     * @throws TypeNotPresentException if the component type points to a missing
     *         type.
     * @throws MalformedParameterizedTypeException if the component type points
     *         to a type that can't be instantiated for some reason.
     * @since 1.5
     */
    public Type[] getGenericParameterTypes() {
      throw new Error("TODO");
    }

    /**
     * <p>
     * Gets the exception types as an array of {@link Type} instances. If the
     * method has no declared exceptions, then an empty array is returned.
     * </p>
     * 
     * @return An array of {@link Type} instances.
     * @throws GenericSignatureFormatError if the generic method signature is
     *         invalid.
     * @throws TypeNotPresentException if the component type points to a missing
     *         type.
     * @throws MalformedParameterizedTypeException if the component type points
     *         to a type that can't be instantiated for some reason.
     * @since 1.5
     */
    public Type[] getGenericExceptionTypes() {
      throw new Error("TODO");
    }

    /**
     * <p>
     * Gets the return type as a {@link Type} instance.
     * </p>
     * 
     * @return A {@link Type} instance.
     * @throws GenericSignatureFormatError if the generic method signature is
     *         invalid.
     * @throws TypeNotPresentException if the component type points to a missing
     *         type.
     * @throws MalformedParameterizedTypeException if the component type points
     *         to a type that can't be instantiated for some reason.
     * @since 1.5
     */
    public Type getGenericReturnType() {
      throw new Error("TODO");
    }

    /**
     * <p>
     * Gets an array of arrays that represent the annotations of the formal
     * parameters of this method. If there are no parameters on this method,
     * then an empty array is returned. If there are no annotations set, then
     * and array of empty arrays is returned.
     * </p>
     * 
     * @return An array of arrays of {@link Annotation} instances.
     * @since 1.5
     */
    public Annotation[][] getParameterAnnotations() {
      return super.getParameterAnnotations();
    }

    /**
     * <p>
     * Indicates whether or not this method takes a variable number argument.
     * </p>
     * 
     * @return A value of <code>true</code> if a vararg is declare, otherwise
     *         <code>false</code>.
     * @since 1.5
     */
    public boolean isVarArgs() {
      return super.isVarArgs();
    }

    /**
     * <p>
     * Indicates whether or not this method is a bridge.
     * </p>
     * 
     * @return A value of <code>true</code> if this method's a bridge,
     *         otherwise <code>false</code>.
     * @since 1.5
     */
    public boolean isBridge() {
		return (vmMethod.getModifiers() & Modifier.BRIDGE) != 0;
    }

    public boolean isSynthetic() {
		return super.isSynthetic();
    }
    
    /**
     * <p>Gets the default value for the annotation member represented by
     * this method.</p>
     * @return The default value or <code>null</code> if none.
     * @throws TypeNotPresentException if the annotation is of type {@link Class}
     * and no definition can be found.
     * @since 1.5
     */
    public Object getDefaultValue() {
        return vmMethod.getAnnotationDefault();
    }
    
	/**
	 * Compares the specified object to this Method and answer if they are
	 * equal. The object must be an instance of Method with the same defining
	 * class and parameter types.
	 * 
	 * @param object
	 *            the object to compare
	 * @return true if the specified object is equal to this Method, false
	 *         otherwise
	 * @see #hashCode
	 */
	@Override
   public boolean equals(Object object) {
	  if (object instanceof Method) {
		 Method that = (Method)object;
		 return this.vmMethod == that.vmMethod;
	  } else {
		 return false;
	  }
	}

	/**
	 * Return the {@link Class} associated with the class that defined this
	 * method.
	 * 
	 * @return the declaring class
	 */
	public Class<?> getDeclaringClass() {
      return (Class<?>)vmMethod.getDeclaringClass().getClassForType();
	}

	/**
	 * Return an array of the {@link Class} objects associated with the
	 * exceptions declared to be thrown by this method. If the method was not
	 * declared to throw any exceptions, the array returned will be empty.
	 * 
	 * @return the declared exception classes
	 */
	public Class<?>[] getExceptionTypes() {
		return super.getExceptionTypes();
	}

	/**
	 * Return the modifiers for the modeled method. The Modifier class
	 * should be used to decode the result.
	 * 
	 * @return the modifiers
	 * @see java.lang.reflect.Modifier
	 */
    public int getModifiers() {
		return super.getModifiers();
	}

	/**
	 * Return the name of the modeled method.
	 * 
	 * @return the name
	 */
	public String getName() {
		return vmMethod.getName().toString();
	}

	/**
	 * Return an array of the {@link Class} objects associated with the
	 * parameter types of this method. If the method was declared with no
	 * parameters, the array returned will be empty.
	 * 
	 * @return the parameter types
	 */
	public Class<?>[] getParameterTypes() {
      return VMCommonLibrarySupport.typesToClasses(vmMethod.getParameterTypes());
	}

	/**
	 * Return the {@link Class} associated with the return type of this
	 * method.
	 * 
	 * @return the return type
	 */
	public Class<?> getReturnType() {
	  return vmMethod.getReturnType().resolve().getClassForType();
	}

	/**
	 * Answers an integer hash code for the receiver. Objects which are equal
	 * answer the same value for this method. The hash code for a Method is the
	 * hash code of the method's name.
	 * 
	 * @return the receiver's hash
	 * @see #equals
	 */
	@Override
    public int hashCode() {
		return getName().hashCode();
	}

	/**
	 * Return the result of dynamically invoking the modeled method. This
	 * reproduces the effect of
	 * <code>receiver.methodName(arg1, arg2, ... , argN)</code> This method
	 * performs the following:
	 * <ul>
	 * <li>If the modeled method is static, the receiver argument is ignored.
	 * </li>
	 * <li>Otherwise, if the receiver is null, a NullPointerException is
	 * thrown.</li>
	 * If the receiver is not an instance of the declaring class of the method,
	 * an IllegalArgumentException is thrown.
	 * <li>If this Method object is enforcing access control (see
	 * AccessibleObject) and the modeled method is not accessible from the
	 * current context, an IllegalAccessException is thrown.</li>
	 * <li>If the number of arguments passed and the number of parameters do
	 * not match, an IllegalArgumentException is thrown.</li>
	 * <li>For each argument passed:
	 * <ul>
	 * <li>If the corresponding parameter type is a base type, the argument is
	 * unwrapped. If the unwrapping fails, an IllegalArgumentException is
	 * thrown.</li>
	 * <li>If the resulting argument cannot be converted to the parameter type
	 * via a widening conversion, an IllegalArgumentException is thrown.</li>
	 * </ul>
	 * <li>If the modeled method is static, it is invoked directly. If it is
	 * non-static, the modeled method and the receiver are then used to perform
	 * a standard dynamic method lookup. The resulting method is then invoked.
	 * </li>
	 * <li>If an exception is thrown during the invocation it is caught and
	 * wrapped in an InvocationTargetException. This exception is then thrown.
	 * </li>
	 * <li>If the invocation completes normally, the return value is itself
	 * returned. If the method is declared to return a base type, the return
	 * value is first wrapped. If the return type is void, null is returned.
	 * </li>
	 * </ul>
	 * 
	 * @param receiver
	 * 	          The object on which to call the modeled method
	 * @param args
	 *            the arguments to the method
	 * @return the new, initialized, object
	 * @throws java.lang.NullPointerException
	 *                if the receiver is null for a non-static method
	 * @throws java.lang.IllegalAccessException
	 *                if the modeled method is not accessible
	 * @throws java.lang.IllegalArgumentException
	 *                if an incorrect number of arguments are passed, the
	 *                receiver is incompatible with the declaring class, or an
	 *                argument could not be converted by a widening conversion
	 * @throws java.lang.reflect.InvocationTargetException
	 *                if an exception was thrown by the invoked method
	 * @see java.lang.reflect.AccessibleObject
	 */
	public Object invoke(Object receiver, Object... args)
			throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException {
	    return VMCommonLibrarySupport.invoke(receiver, args, vmMethod, this, RVMClass.getClassFromStackFrame(1));
	}

	/**
	 * Answers a string containing a concise, human-readable description of the
	 * receiver. The format of the string is modifiers (if any) return type
	 * declaring class name '.' method name '(' parameter types, separated by
	 * ',' ')' If the method throws exceptions, ' throws ' exception types,
	 * separated by ',' For example:
	 * <code>public native Object java.lang.Method.invoke(Object,Object) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException</code>
	 * 
	 * @return a printable representation for the receiver
	 */
	@Override
   public String toString() {
	  StringBuilder sb = new StringBuilder(80);
	  // append modifiers if any
	  int modifier = getModifiers();
	  if (modifier != 0) {
		 // BRIDGE & VARARGS recognized incorrectly
		 final int MASK = ~(Modifier.BRIDGE | Modifier.VARARGS);
		 sb.append(Modifier.toString(modifier & MASK)).append(' ');            
	  }
	  // append return type
	  appendArrayType(sb, getReturnType());
	  sb.append(' ');
	  // append full method name
	  sb.append(getDeclaringClass().getName()).append('.').append(getName());
	  // append parameters
	  sb.append('(');
	  appendArrayType(sb, getParameterTypes());
	  sb.append(')');
	  // append exeptions if any
	  Class[] exn = getExceptionTypes(); 
	  if (exn.length > 0) {
		 sb.append(" throws ");
		 appendSimpleType(sb, exn);
	  }
	  return sb.toString();
	}

  /* ---- Non-API methods ---- */

  /**
   * Get the VM member implementation. Package protected to stop outside use.
   */
  @Override
  RVMMember getVMMember() {
	 return vmMethod;
  }

  /**
   * Get the VM method implementation, invalid for fields. Package protected to
   * stop outside use.
   */
  @Override
  RVMMethod getVMMethod() {
	 return vmMethod;
  }
}
