import scala.tools.partest.BytecodeTest
import scala.collection.JavaConverters._

package p1 {
  package p2 {
    object Singleton {
      object Singleton {
        object Singleton
      }
    }
  }
}

class A1 {
  class B1 {
    class C1
  }
}

class A2 {
  class B2 {
    class C2
  }
  def f: B2#C2 = null
}


object Test extends BytecodeTest {
  import p1.p2._

  def nested(c: Class[_]) = s" ${c.getName}: ${c.getDeclaredClasses.toList}"

  def nprintln(s: String) = println("\n"+s)
  def printInner(cname: String): Unit = {
    val cnode = loadClassNode(cname)
    println(cnode.innerClasses.asScala.toList.map(i => s"className[${i.name}] outerClassName[${i.outerName}] innerName[${i.innerName}] access[${i.access}]").mkString(" ", "\n ", ""))
  }

  def show() {

    println("getClass on module gives module class")
    println(" " + Singleton.Singleton.getClass)

    nprintln("Nested module classes are found through reflection")
    println(nested(Singleton.Singleton.getClass))

    nprintln("Reflection can find direct nested classes (A1-B1-C1)")
    println(nested(classOf[A1]))
    println(nested(classOf[A1#B1]))
    println(nested(classOf[A1#B1#C1]))

    nprintln("Reflection can find direct nested classes (A2-B2-C2)")
    println(nested(classOf[A2]))
    println(nested(classOf[A2#B2]))
    println(nested(classOf[A2#B2#C2]))

    nprintln("The InnerClass attribute of a mirror class contains the members of the module class:")
    printInner("p1.p2.Singleton") // mirror class
    println("The module members are not in the InnerClass table of the module class (unless referenced):")
    printInner("p1.p2.Singleton$")

    nprintln("An outer class has a InnerClass attribute for direct nested classes")
    printInner("A1")
    println("A nested class has an InnerClass attribute for itself (and also for its nested classes)")
    printInner("A1$B1")
    println("C1 is a nested class, so it has an InnerClass attribute for itself.\n"+
            "Because that attribute leads to an entry for B1 in the constant pool, C1 needs an InnerClass attribute for B1.")
    printInner("A1$B1$C1")

    nprintln("Class A2 mentions class C2 in the constant pool (due to method  f), therefore it needs an InnerClass attribute for C1")
    printInner("A2")
    println("B2")
    printInner("A2$B2")
    println("C2")
    printInner("A2$B2$C2")
  }
}
