/**
 * Abstract Java Method
 *
 * @author Martin Bravenboer <martin@cs.uu.nl>
 */
module dryad/model/method
imports
  dryad/model/member
  dryad/lib-ext/oo/classes
  
/**
 * Names
 */
strategies

  /**
   * Returns the MethodName of this method.
   *
   * Note that a MethodName not uniquely identifies a method
   * (i.e. a different method can have the same name).
   *
   * @type Method Object -> MethodName
   */
  get-name = instanceof-JavaMethod; ?this;
    where(get-declaring-class => class)
    ; !MethodName(<get-canonical-name> class, Id(<get-simple-name> this))
    
  /**
   * Return name that uniquely identifiers a method.
   *
   * Note that the JLS does not define canonical names for methods.
   * We just use the same terminology for this unique method name, to
   * avoid another kind of name.
   *
   * @todo Include type parameters one way or another?
   */
  get-canonical-name = instanceof-JavaMethod; ?this;
    !MethodName(
       <get-declaring-class; get-canonical-name> this
     , Id(<get-simple-name> this)
     , <get-formal-parameter-types> this
     , <get-return-type> this
     )

/**
 * Access
 */
strategies

  /**
   * Returns the access of this method.
   * Must be implemented by subclasses.
   *
   * @type Method object -> Access Modifier
   */
  get-access =
    fail

/**
 * Formal type parameters
 *
 * @todo Same code as for classes. Make this a superclass?
 */
strategies

  /**
   * Succeeds if this class has a type parameter of the given name.
   *
   * @param  Simple name of the type parameter (String)
   * @type   Method Object -> TypeParam   
   */
  has-formal-type-parameter(|name) =  instanceof-JavaMethod;
    where(get-formal-type-parameter(|name))
    
  /**
   * Returns the type parameter of the given name, or fails
   * if this class thus have a type parameter with this name.
   *
   * @param  Simple name of the type parameter (String)
   * @type   Method Object -> TypeParam
   */
  get-formal-type-parameter(|name) = instanceof-JavaMethod;
    get-formal-type-parameters
    ; fetch-elem(?TypeParam(Id(name), _))

  /**
   * Abstract method. Has to declared in subclasses.
   *
   * @type   Method Object -> List(TypeParam)
   */
  get-formal-type-parameters =
    fail

  /**
   * Succeeds if this is a generic method (i.e. has formal type parameters).
   */
  is-generic = instanceof-JavaMethod;
    where(get-formal-type-parameters => [_ | _])

/**
 * Return Type
 */
strategies

  get-return-type(|inclass) =
    get-return-type-in-class(|inclass)
    
  get-return-type(|intype) =    
    get-return-type-in-type(|intype)

  /**
   * Returns the return type of this method if this method is invoked
   * as a member of the given class.
   *
   * If the method is not actually a member of the specified class, then 
   * this strategy will fail.
   *
   * @param Class Object
   */
  get-return-type-in-class(|inclass) = instanceof-JavaMethod; ?this;
    where(get-simple-name => name);  
    get-member-type-in-class-helper(
        get-declared-methods(|name)
      , get-return-type
      , get-return-type-in-type
      | inclass
      )
    
  /**
   * Returns the return type of this method if this method is invoked
   * as a member of the given type.
   *
   * If the method is not actually a member of the specified type, then 
   * this strategy will fail.
   *
   * @param Type
   */
  get-return-type-in-type(|intype) = instanceof-JavaMethod; ?this;
    where(<is-reference-type> intype)
    ; get-member-type-in-type-helper(get-return-type-in-class | intype)

/**
 * Formal Parameters
 */  
strategies

  /**
   * Succeeds if this method does not take a variable number of arguments.
   */
  is-fixed-arity-method =
    not(is-variable-arity-method)

  get-formal-parameter-types(|inclass) =
    get-formal-parameter-types-in-class(|inclass)
    
  get-formal-parameter-types(|intype) =    
    get-formal-parameter-types-in-type(|intype)

  /**
   * Returns the formal parameter types of this method if this method is 
   * invoked as a member of the given class.
   *
   * If the method is not actually a member of the specified class, then 
   * this strategy will fail.
   *
   * @param Class Object
   * @type Method Object -> List(Type)   
   */
  get-formal-parameter-types-in-class(|inclass) = instanceof-JavaMethod; ?this;
    where(get-simple-name => name);
    get-member-type-in-class-helper(
        get-declared-methods(|name)
      , get-formal-parameter-types
      , get-formal-parameter-types-in-type
      | inclass
      )
      
  /**
   * Returns the formal parameter types of this method if this method is invoked
   * as a member of the given type.
   *
   * If the method is not actually a member of the specified type, then 
   * this strategy will fail.
   *
   * @param Type
   * @type Method Object -> List(Type)
   */
  get-formal-parameter-types-in-type(|intype) = instanceof-JavaMethod; ?this;
    where(<is-reference-type> intype)
    ; get-member-type-in-type-helper(get-formal-parameter-types-in-class | intype)

strategies

  /**
   * @todo Reuse define-substition.
   * @todo Raw types and type erasure.  
   * @todo Check the bound
   * @todo Does zip check that the number of arguments are equal?       
   */
  get-member-type-in-type-helper(getter : ClassObject * MethodObject -> a | intype) = ?this;
    !intype;

    if ?InterfaceType(typename, None()) + ?ClassType(typename, None()) then
      where(<lookup-class> typename => c)
      ; <getter(|c)> this      
      ; if <is-generic> c then
          if is-list then
            map(type-erasure)
          else
            type-erasure
          end
        end
    else
      {| LookupTypeVar :
        ( ?InterfaceType(typename, Some(TypeArgs(t*))) + ?ClassType(typename, Some(TypeArgs(t*))))
        ; where(<lookup-class> typename => c)
        ; <get-formal-type-parameters> c => a*
        ; <zip> (t*, a*)
        ; map({t, a:
            ?(t, TypeParam(Id(a), _))
            ; rules(LookupTypeVar : TypeVar(typename, Id(a)) -> t)
          })
        ; <getter(|c)> this
      |}
    end

  /**
   * OMG!
   */
  get-member-type-in-class-helper(
            declaredgetter : ClassObject -> List(MemberObject)
          , resultgetter : MemberObject -> a
          , typegetter : ClassObject * MethodObject -> a
          | inclass) =
        
    ?this
    ; <instanceof-JavaClass> inclass
    ; (
    
       <declaredgetter> inclass
       ; fetch(?this)
       ; <resultgetter> this
       ; topdown(try(LookupTypeVar))

    <+ <get-superclass-as-type> inclass 
       ; topdown(try(LookupTypeVar)) => super
       ; <typegetter(|super)> this

    <+ get-superinterfaces-as-type
       ; fetch-elem({interface: ?interface; <typegetter(|interface)> this})
    )
    

/**
 * Stratego class support.
 */
strategies

  /**
   * Checks if current term is an instance of JavaClass.
   *
   * Subclasses of method implement this strategy.
   */
  instanceof-JavaMethod =
    fail

  /**
   * Every Java method is a Java member.
   */
  instanceof-JavaMember =
    instanceof-JavaMethod