/**
 * Bytecode Class
 *
 * @author Martin Bravenboer <martin@cs.uu.nl>
 */
module dryad/model/bytecode-class
imports
  dryad/lib-ext/oo/classes

  dryad/model/class
  dryad/model/bytecode-constructor
  dryad/model/bytecode-field
  dryad/model/bytecode-method

  dryad/bytecode/Bridge
  dryad/bytecode/Signature
  dryad/bytecode/util

strategies

  /**
   * Construct a new bytecode class object
   *
   * @type  _ -> BytecodeClass Object
   * @param String
   */
  new-bytecode-class(|simple-name) =
    <classes_get-class> JavaBytecodeClass()
    ; classes_new-instance
    ; classes_set-instance-field(|"simple-name", simple-name)

strategies

  set-package(|pkg) = instanceof-JavaBytecodeClass;
    classes_set-instance-field(|"package", pkg)

  get-package = instanceof-JavaBytecodeClass;
    classes_get-instance-field(|"package")

strategies

  /**
   * Returns the super class of this class as a type.
   * Fails if this class has no superclass (i.e. this class is java.lang.Object)
   *
   * @type Class Object -> Source Type
   */
  get-superclass-as-type =
    if-signature(
      ?ClassSignature(_, SuperClass(<id>), _)
    , get-reference-ast
      ; ?ClassFile(_, _, _, _, SuperClass(Some(<id>)), _, _, _, _)
      ; <bytecode-type-to-source-type> ObjectType(<id>)
    )

  /**
   * Returns the interfaces implemented by this class as types.
   *
   * @type Class Object -> List(Source Type)
   */
  get-superinterfaces-as-type =
    if-signature(
      ?ClassSignature(_, _, SuperInterfaces(<id>))
    , get-reference-ast
      ; ?ClassFile(_, _, _, _, _, Interfaces(<id>), _, _, _)
      ; map(<bytecode-type-to-source-type> ObjectType(<id>))
    )
    
strategies

  /**
   * @type BytecodeClass Object -> List(TypeParam)
   */
  get-formal-type-parameters = instanceof-JavaBytecodeClass;
    if get-signature => sig then
      !sig => ClassSignature(TypeParams(<id>), _, _)
    else
      ![]
    end

  /**
   * Returns the generic signature of this class.
   *
   * @type BytecodeClass Object - ClassSignature
   */
  get-signature = instanceof-JavaBytecodeClass;
    ?this; (
       classes_get-instance-field(|GenericSignature())
       
    <+ get-attributes
       ; if fetch(?ClassSignature(_, _, _); ?sig) then
           !Some(sig)
         else
           !None()
         end
       ; ?result
       ; <classes_set-instance-field(|GenericSignature(), result)> this
       ; reclassify-class-signature
       ; classes_get-instance-field(|GenericSignature())
     )
     ; ?Some(<id>)
      
strategies

  /**
   * Returns the attributes of this bytecode class.
   *
   * Note: attributes are a bytecode-level construct and are not
   * directly related to Java source annotations. This strategy returns
   * the *bytecode* attributes.
   *
   * @type BytecodeClass Object -> List(Attribute)
   */
  get-attributes = instanceof-JavaBytecodeClass;
    get-reference-ast
    ; ?ClassFile(_, _, _, _, _, _, _, _, Attributes(<id>))

strategies

  /**
   * Succeeds if this class is an interface.
   *
   * @type BytecodeClass Object -> BytecodeClass Object
   */
  is-interface = instanceof-JavaBytecodeClass;
    where(
      get-access-flags
      ; fetch(?Interface())
    )

  /**
   * @type BytecodeClass Object -> List(AccessFlags)
   */
  get-access-flags = instanceof-JavaBytecodeClass;
    get-reference-ast
    ; ?ClassFile(_, _, AccessFlags(<id>), _, _, _, _, _, _)

  /**
   * @todo Is default access necessary for bytecode classes?
   * @todo Do interfaces have default access set to public in bytecode?
   * @type BytecodeClass Object -> Access Modifier
   */
  get-access = instanceof-JavaBytecodeClass;
    get-access-flags
    ; ( fetch-elem(?Public() + ?Private() + ?Protected())
     <+ !DefaultAccess())

/**
 * Fields
 */
strategies

  /**
   * Initializes the field table of a bytecode class.
   *
   * @type BytecodeClass Object -> List(BytecodeField Object)
   */
  init-get-declared-field-list = instanceof-JavaBytecodeClass;
    get-reference-ast
    ; ?ClassFile(_, _, _, _, _, _, Fields(<id>), _, _)
    ; filter(instanceof-JavaField)

/**
 * Methods
 */
strategies

  /**
   * Initializes the declared method table of a bytecode class.
   *
   * @type BytecodeClass Object -> List(BytecodeMethod Object)
   */
  init-get-declared-method-list = instanceof-JavaBytecodeClass;
    get-reference-ast
    ; ?ClassFile(_, _, _, _, _, _, _, Methods(<id>), _)    
    ; filter(instanceof-JavaMethod)
    
/**
 * Constructors
 */
strategies

  /**
   * Initializes the declared constructors list of a bytecode class.
   *
   * @type BytecodeClass Object -> List(BytecodeConstructor Object)
   */
  init-get-declared-constructor-list = instanceof-JavaBytecodeClass;
    get-reference-ast
    ; ?ClassFile(_, _, _, _, _, _, _, Methods(<id>), _)
    ; filter(instanceof-JavaConstructor)
    
/**
 * Member Classes
 */
strategies

  init-declared-member-type-table = instanceof-JavaBytecodeClass;
    ?this; where(
      new-hashtable => classtbl
    )
    ; classes_set-instance-field(|"declared-member-type-table", classtbl)

/**
 * AST
 *
 * @todo Implement get-ast.
 */
strategies

  set-ast(|ast) = instanceof-JavaBytecodeClass;
    ?this; where(
      <classtree-to-reference-ast(|this)> ast => ref-ast
    )
    ; set-reference-ast(|ref-ast)

/**
 * Reference AST of this bytecode class.
 */
strategies

  /**
   * Returns the Reference AST of this source class.
   *
   * @type BytecodeClass Object -> ReferenceAST(ClassTree)
   */
  get-reference-ast = instanceof-JavaBytecodeClass;
    classes_get-instance-field(read-reference-ast-from-location |"reference-ast")

  /**
   * @param ReferenceAST(ClassTree)
   * @type  BytecodeClass Object -> BytecodeClass Object
   */
  set-reference-ast(|ref-ast) =  instanceof-JavaBytecodeClass;
    classes_set-instance-field(|"reference-ast", ref-ast)
    
  read-reference-ast-from-location = instanceof-JavaBytecodeClass; ?this; (
    get-location
    ; report-reading-class
    ; read-class-file
    ; classtree-to-reference-ast(|this)
    <+ get-simple-name
       ; debug(!"error: could not read class ")
       ; fail
    )

  classtree-to-reference-ast(|this) =
      ?tree
    ; apply-to-methods-of-classtree(
        new-bytecode-method(|<id>)
        ; set-declaring-class(|this)
      )
    ; apply-to-constructors-of-classtree(
        new-bytecode-constructor(|<id>)
        ; set-declaring-class(|this)
      )
    ; apply-to-fields-of-classtree(
        new-bytecode-field(|<id>)
        ; set-declaring-class(|this)
      )
      
  apply-to-constructors-of-classtree(s) =
    ClassFile(id, id, id, id, id, id, id
    , Methods(map(if is-instance-init-method then s end))
    , id
    )

  apply-to-methods-of-classtree(s) =
    ClassFile(id, id, id, id, id, id, id
    , Methods(map(if not(is-class-init-method + is-instance-init-method) then s end))
    , id
    )

  apply-to-fields-of-classtree(s) =
    ClassFile(id, id, id, id, id, id, Fields(map(s)), id, id)

/**
 * Location
 */
strategies
  
  set-location(|loc) = instanceof-JavaBytecodeClass;
    classes_set-instance-field(|"location", loc)

  get-location = instanceof-JavaBytecodeClass;
    classes_get-instance-field(|"location")

strategies

  report-reading-class =
    if-verbose2(
      where(
        ?Jar(jarpath, filepath)
        ; log(|Debug(), ["Reading class ", filepath, " from jar ", jarpath])
      )
    )
    
/**
 * Stratego class support for bytecode classes
 */
signature
  constructors
    /**
     * Identifier for BytecodeClass Objects.
     */
    JavaBytecodeClass : ClassName

strategies

  /**
   * Succeeds if this is a BytecodeClass Object.
   */
  instanceof-JavaBytecodeClass =
    classes_instanceof(|JavaBytecodeClass())

  /**
   * A BytecodeClass Object is a Class Object.
   */
  instanceof-JavaClass =
    classes_instanceof(|JavaBytecodeClass())