/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id: Pickler.scala 18387 2009-07-24 15:28:37Z odersky $

package scala.tools.nsc
package symtab
package classfile

import java.lang.{Float, Double}
import scala.tools.nsc.util.{Position, NoPosition, ShowPickled}
import Flags._
import PickleFormat._

/**
 * Serialize a top-level module and/or class.
 *
 * @see <code>EntryTags.scala</code> for symbol table attribute format.
 *
 * @author Martin Odersky
 * @version 1.0
 */
abstract class Pickler extends SubComponent {
  import global._

  private final val showSig = false

  val phaseName = "pickler"

  def newPhase(prev: Phase): StdPhase = new PicklePhase(prev)

  class PicklePhase(prev: Phase) extends StdPhase(prev) {
    def apply(unit: CompilationUnit) {
      def pickle(tree: Tree) {

        def add(sym: Symbol, pickle: Pickle) = {
          if (currentRun.compiles(sym) && !currentRun.symData.contains(sym)) {
            if (settings.debug.value) log("pickling " + sym)
            pickle.putSymbol(sym)
            currentRun.symData(sym) = pickle
          }
        }

        tree match {
          case PackageDef(_, stats) =>
            stats foreach pickle
          case ClassDef(_, _, _, _) | ModuleDef(_, _, _) =>
            val sym = tree.symbol
            val pickle = new Pickle(sym, sym.name.toTermName, sym.owner)
            add(sym, pickle)
            add(sym.linkedSym, pickle)
            pickle.finish
            // pickleHash is used to track changes in a signature (-> IDE)
            val doPickleHash = global.doPickleHash
            if (doPickleHash) {
              var i = 0
              while (i < pickle.writeIndex) {
                unit.pickleHash += pickle.bytes(i).toLong // toLong needed to work around bug
                i += 1
              }
            }
          case _ =>
        }
      }
      pickle(unit.body)
    }
  }

  private class Pickle(root: Symbol, rootName: Name, rootOwner: Symbol)
        extends PickleBuffer(new Array[Byte](4096), -1, 0) {
    import scala.collection.mutable.LinkedHashMap
    private var entries = new Array[AnyRef](256)
    private var ep = 0
    private val index = new LinkedHashMap[AnyRef, Int]

    /** Is symbol an existentially bound variable with a package as owner? 
     *  Such symbols should be treated as if they were local.
     */
    private def isUnrootedExistential(sym: Symbol) =
      sym.isAbstractType && sym.hasFlag(EXISTENTIAL)

    private def normalizedOwner(sym: Symbol) = 
      if (isUnrootedExistential(sym)) root else sym.owner

    /** Is root in symbol.owner*?
     *
     *  @param sym ...
     *  @return    ...
     */
    private def isLocal(sym: Symbol): Boolean =
      sym.isRefinementClass ||
      sym.name.toTermName == rootName && sym.owner == rootOwner ||
      sym != NoSymbol && isLocal(sym.owner) ||
      isUnrootedExistential(sym)

    private def staticAnnotations(annots: List[AnnotationInfo]) =
      annots filter(ann =>
        ann.atp.typeSymbol isNonBottomSubClass definitions.StaticAnnotationClass)

    // Phase 1 methods: Populate entries/index ------------------------------------

    /** Store entry <code>e</code> in index at next available position unless
     *  it is already there.
     *
     *  @param entry ...
     *  @return      <code>true</code> iff entry is new.
     */
    private def putEntry(entry: AnyRef): Boolean = index.get(entry) match {
      case Some(_) => false
      case None =>
        if (ep == entries.length) {
          val entries1 = new Array[AnyRef](ep * 2)
          System.arraycopy(entries, 0, entries1, 0, ep)
          entries = entries1
        }
        entries(ep) = entry
        index(entry) = ep
        ep = ep + 1
        true
    }

    /** Store symbol in <code>index</code>. If symbol is local, also store
     * everything it refers to.
     *
     *  @param sym ...
     */
    def putSymbol(sym: Symbol) {
      if (putEntry(sym)) {
        if (isLocal(sym)) {
          putEntry(sym.name)
          putSymbol(sym.owner)
          putSymbol(sym.privateWithin)
          putType(sym.info)
          if (sym.thisSym.tpeHK != sym.tpeHK)
            putType(sym.typeOfThis);
          putSymbol(sym.alias)
          putSymbol(sym.defaultGetter)
          if (!sym.children.isEmpty) {
            val (locals, globals) = sym.children.toList.partition(_.isLocalClass)
            val children = 
              if (locals.isEmpty) globals
              else {
                val localChildDummy = sym.newClass(sym.pos, nme.LOCALCHILD)
                localChildDummy.setInfo(ClassInfoType(List(sym.tpe), EmptyScope, localChildDummy))
                localChildDummy :: globals
              }
            putChildren(sym, children.sort((x, y) => x isLess y))
          }
          for (annot <- staticAnnotations(sym.annotations.reverse))
            putAnnotation(sym, annot)
        } else if (sym != NoSymbol) {
          putEntry(if (sym.isModuleClass) sym.name.toTermName else sym.name)
          if (!sym.owner.isRoot) putSymbol(sym.owner)
        }
      }
    }

    private def putSymbols(syms: List[Symbol]) =
      syms foreach putSymbol

    /** Store type and everythig it refers to in map <code>index</code>.
     *
     *  @param tp ...
     */
    private def putType(tp: Type): Unit = if (putEntry(tp)) {
      tp match {
        case NoType | NoPrefix | DeBruijnIndex(_, _) =>
          ;
        case ThisType(sym) =>
          putSymbol(sym)
        case SingleType(pre, sym) =>
          putType(pre); putSymbol(sym)
        case SuperType(thistpe, supertpe) =>
          putType(thistpe)
          putType(supertpe)
        case ConstantType(value) =>
          putConstant(value)
        case TypeRef(pre, sym, args) =>
          putType(pre); putSymbol(sym); putTypes(args)
        case TypeBounds(lo, hi) =>
          putType(lo); putType(hi)
        case RefinedType(parents, decls) =>
          val rclazz = tp.typeSymbol
          for (m <- decls.iterator)
            if (m.owner != rclazz) assert(false, "bad refinement member "+m+" of "+tp+", owner = "+m.owner)
          putSymbol(rclazz); putTypes(parents); putSymbols(decls.toList)
        case ClassInfoType(parents, decls, clazz) =>
          putSymbol(clazz); putTypes(parents); putSymbols(decls.toList)
        case MethodType(params, restpe) =>
          putType(restpe); putSymbols(params)
        case PolyType(tparams, restpe) =>
          putType(restpe); putSymbols(tparams)
        case ExistentialType(tparams, restpe) =>
          putType(restpe); putSymbols(tparams)
        case AnnotatedType(annotations, underlying, selfsym) =>
          putType(underlying)
          if (settings.selfInAnnots.value) putSymbol(selfsym)
          putAnnotations(staticAnnotations(annotations))
        case _ =>
          throw new FatalError("bad type: " + tp + "(" + tp.getClass + ")")
      }
    }
    private def putTypes(tps: List[Type]) { tps foreach putType }

    private def putTree(tree: Tree): Unit = if (putEntry(tree)) {
      if (tree != EmptyTree)
        putType(tree.tpe)
      if (tree.hasSymbol)
        putSymbol(tree.symbol)

      tree match {
        case EmptyTree =>

        case tree@PackageDef(pid, stats) =>
          putTree(pid)
          putTrees(stats)

        case ClassDef(mods, name, tparams, impl) =>
          putMods(mods)
          putEntry(name)
          putTree(impl)
          putTrees(tparams)

        case ModuleDef(mods, name, impl) =>
          putMods(mods)
          putEntry(name)
          putTree(impl)

        case ValDef(mods, name, tpt, rhs) =>
          putMods(mods)
          putEntry(name)
          putTree(tpt)
          putTree(rhs)

        case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
          putMods(mods)
          putEntry(name)
          putTrees(tparams)
          putTreess(vparamss)
          putTree(tpt)
          putTree(rhs)

        case TypeDef(mods, name, tparams, rhs) =>
          putMods(mods)
          putEntry(name)
          putTree(rhs)
          putTrees(tparams)

        case LabelDef(name, params, rhs) =>
          putEntry(name)
          putTree(rhs)
          putTrees(params)

        case Import(expr, selectors) =>
          putTree(expr)
          for ((from,to) <- selectors) {
            putEntry(from)
            putEntry(to)
          }

        case DocDef(comment, definition) =>
          putConstant(Constant(comment))
          putTree(definition)

        case Template(parents, self, body) =>
          writeNat(parents.length)
          putTrees(parents)
          putTree(self)
          putTrees(body)

        case Block(stats, expr) =>
          putTree(expr)
          putTrees(stats)

        case CaseDef(pat, guard, body) =>
          putTree(pat)
          putTree(guard)
          putTree(body)

        case Sequence(trees) =>
          putTrees(trees)

        case Alternative(trees) =>
          putTrees(trees)

        case Star(elem) =>
          putTree(elem)

        case Bind(name, body) =>
          putEntry(name)
          putTree(body)

        case UnApply(fun: Tree, args) =>
          putTree(fun)
          putTrees(args)

        case ArrayValue(elemtpt, trees) =>
          putTree(elemtpt)
          putTrees(trees)


        case Function(vparams, body) =>
          putTree(body)
          putTrees(vparams)

        case Assign(lhs, rhs) =>
          putTree(lhs)
          putTree(rhs)

        case If(cond, thenp, elsep) =>
          putTree(cond)
          putTree(thenp)
          putTree(elsep)

        case Match(selector, cases) =>
          putTree(selector)
          putTrees(cases)

        case Return(expr) =>
          putTree(expr)

        case Try(block, catches, finalizer) =>
          putTree(block)
          putTree(finalizer)
          putTrees(catches)

        case Throw(expr) =>
          putTree(expr)

        case New(tpt) =>
          putTree(tpt)

        case Typed(expr, tpt) =>
          putTree(expr)
          putTree(tpt)

        case TypeApply(fun, args) =>
          putTree(fun)
          putTrees(args)

        case Apply(fun, args) =>
          putTree(fun)
          putTrees(args)

        case ApplyDynamic(qual, args) =>
          putTree(qual)
          putTrees(args)

        case Super(qual, mix) =>
          putEntry(qual:Name)
          putEntry(mix:Name)

        case This(qual) =>
          putEntry(qual)

        case Select(qualifier, selector) =>
          putTree(qualifier)
          putEntry(selector)

        case Ident(name) =>
          putEntry(name)

        case Literal(value) =>
          putEntry(value)

        case TypeTree() =>

        case Annotated(annot, arg) =>
          putTree(annot)
          putTree(arg)

        case SingletonTypeTree(ref) =>
          putTree(ref)

        case SelectFromTypeTree(qualifier, selector) =>
          putTree(qualifier)
          putEntry(selector)

        case CompoundTypeTree(templ: Template) =>
          putTree(templ)

        case AppliedTypeTree(tpt, args) =>
          putTree(tpt)
          putTrees(args)

        case TypeBoundsTree(lo, hi) =>
          putTree(lo)
          putTree(hi)

        case ExistentialTypeTree(tpt, whereClauses) =>
          putTree(tpt)
          putTrees(whereClauses)
      }
    }

    private def putTrees(trees: List[Tree]) =
      trees.foreach(putTree _)

    private def putTreess(treess: List[List[Tree]]) =
      treess.foreach(putTrees _)

    /** only used when pickling trees, i.e. in an
     *  argument of some Annotation */
    private def putMods(mods: Modifiers) = if (putEntry(mods)) {
      // annotations in Modifiers are removed by the typechecker
      val Modifiers(flags, privateWithin, Nil) = mods
      putEntry(privateWithin)
    }

    /** Store a constant in map <code>index</code> along with
     *  anything it references.
     */
    private def putConstant(c: Constant) {
      if (putEntry(c)) {
        if (c.tag == StringTag) putEntry(newTermName(c.stringValue))
        else if (c.tag == ClassTag) putType(c.typeValue)
        else if (c.tag == EnumTag) putSymbol(c.symbolValue)
      }
    }

    private def putChildren(sym: Symbol, children: List[Symbol]) {
      assert(putEntry((sym, children)))
      children foreach putSymbol
    }

    /** used in putSymbol only, i.e. annotations on definitions, not on types */
    private def putAnnotation(sym: Symbol, annot: AnnotationInfo) {
      // if an annotation with the same arguments is applied to the
      // same symbol multiple times, it's only pickled once.
      if (putEntry((sym, annot)))
        putAnnotationBody(annot)
    }

    /** used in AnnotatedType only, i.e. annotations on types */
    private def putAnnotations(annots: List[AnnotationInfo]) {
      annots foreach putAnnotation
    }
    private def putAnnotation(annot: AnnotationInfo) {
      if (putEntry(annot))
        putAnnotationBody(annot)
    }

    /** Puts the members of an AnnotationInfo */
    private def putAnnotationBody(annot: AnnotationInfo) {
      def putAnnotArg(arg: Tree) {
        arg match {
          case Literal(c) => putConstant(c)
          case _ => putTree(arg)
        }
      }
      def putClassfileAnnotArg(carg: ClassfileAnnotArg) {
        carg match {
          case LiteralAnnotArg(const) =>
            putConstant(const)
          case ArrayAnnotArg(args) =>
            if (putEntry(carg))
              args foreach putClassfileAnnotArg
          case NestedAnnotArg(annInfo) =>
            putAnnotation(annInfo)
        }
      }
      val AnnotationInfo(tpe, args, assocs) = annot
      putType(tpe)
      args foreach putAnnotArg
      assocs foreach { asc =>
        putEntry(asc._1)
        putClassfileAnnotArg(asc._2)
      }
    }

    // Phase 2 methods: Write all entries to byte array ------------------------------

    /** Write a reference to object, i.e., the object's number in the map
     *  <code>index</code>.
     *
     *  @param ref ...
     */
    private def writeRef(ref: AnyRef) { writeNat(index(ref)) }
    private def writeRefs(refs: List[AnyRef]) { refs foreach writeRef }

    /** Write name, owner, flags, and info of a symbol.
     */
    private def writeSymInfo(sym: Symbol) {
      writeRef(sym.name)
      writeRef(normalizedOwner(sym))
      writeLongNat((rawFlagsToPickled(sym.flags & PickledFlags)))
      if (sym.privateWithin != NoSymbol) writeRef(sym.privateWithin)
      writeRef(sym.info)
    }

    /** Write a name in UTF8 format. */
    private def writeName(name: Name) {
      ensureCapacity(name.length * 3)
      writeIndex = name.copyUTF8(bytes, writeIndex)
    }

    /** Write an annotation */
    private def writeAnnotation(annot: AnnotationInfo) {
      def writeAnnotArg(arg: Tree) {
        arg match {
          case Literal(c) => writeRef(c)
          case _ => writeRef(arg)
        }
      }

      writeRef(annot.atp)
      annot.args foreach writeAnnotArg
      annot.assocs foreach { asc =>
        writeRef(asc._1)
        writeClassfileAnnotArg(asc._2)
      }
    }

    /** Write a ClassfileAnnotArg (argument to classfile annotation) */
    def writeClassfileAnnotArg(carg: ClassfileAnnotArg) {
      carg match {
        case LiteralAnnotArg(const) =>
          writeRef(const)
        case ArrayAnnotArg(args) =>
          writeRef(carg)
        case NestedAnnotArg(annInfo) =>
          writeRef(annInfo)
      }
    }

    /** Write an entry */
    private def writeEntry(entry: AnyRef) {
      def writeBody(entry: AnyRef): Int = entry match {
        case name: Name =>
          writeName(name)
          if (name.isTermName) TERMname else TYPEname
        case NoSymbol =>
          NONEsym
        case sym: Symbol if !isLocal(sym) =>
          val tag =
            if (sym.isModuleClass) {
              writeRef(sym.name.toTermName); EXTMODCLASSref
            } else {
              writeRef(sym.name); EXTref
            }
          if (!sym.owner.isRoot) writeRef(sym.owner)
          tag
        case sym: ClassSymbol =>
          writeSymInfo(sym)
          if (sym.thisSym.tpe != sym.tpe) writeRef(sym.typeOfThis)
          CLASSsym
        case sym: TypeSymbol =>
          writeSymInfo(sym)
          if (sym.isAbstractType) TYPEsym else ALIASsym
        case sym: TermSymbol =>
          if (!sym.isModule && sym.defaultGetter != NoSymbol)
            writeRef(sym.defaultGetter)
          writeSymInfo(sym)
          if (sym.alias != NoSymbol) writeRef(sym.alias)
          if (sym.isModule) MODULEsym else VALsym
        case NoType =>
          NOtpe
        case NoPrefix =>
          NOPREFIXtpe
        case ThisType(sym) =>
          writeRef(sym); THIStpe
        case SingleType(pre, sym) =>
          writeRef(pre); writeRef(sym); SINGLEtpe
        case SuperType(thistpe, supertpe) =>
          writeRef(thistpe); writeRef(supertpe); SUPERtpe
        case ConstantType(value) =>
          writeRef(value); CONSTANTtpe
        case TypeRef(pre, sym, args) =>
          writeRef(pre); writeRef(sym); writeRefs(args); TYPEREFtpe
        case TypeBounds(lo, hi) =>
          writeRef(lo); writeRef(hi); TYPEBOUNDStpe
        case tp @ RefinedType(parents, decls) =>
          writeRef(tp.typeSymbol); writeRefs(parents); REFINEDtpe
        case ClassInfoType(parents, decls, clazz) =>
          writeRef(clazz); writeRefs(parents); CLASSINFOtpe
        case MethodType(formals, restpe) =>
          writeRef(restpe); writeRefs(formals)
          if (entry.isInstanceOf[ImplicitMethodType]) IMPLICITMETHODtpe
          else METHODtpe
        case PolyType(tparams, restpe) =>
          writeRef(restpe); writeRefs(tparams); POLYtpe
        case ExistentialType(tparams, restpe) =>
          writeRef(restpe); writeRefs(tparams); EXISTENTIALtpe
        case DeBruijnIndex(l, i) =>
          writeNat(l); writeNat(i); DEBRUIJNINDEXtpe
        case c @ Constant(_) =>
          if (c.tag == BooleanTag) writeLong(if (c.booleanValue) 1 else 0)
          else if (ByteTag <= c.tag && c.tag <= LongTag) writeLong(c.longValue)
          else if (c.tag == FloatTag) writeLong(Float.floatToIntBits(c.floatValue))
          else if (c.tag == DoubleTag) writeLong(Double.doubleToLongBits(c.doubleValue))
          else if (c.tag == StringTag) writeRef(newTermName(c.stringValue))
          else if (c.tag == ClassTag) writeRef(c.typeValue)
          else if (c.tag == EnumTag) writeRef(c.symbolValue)
          LITERAL + c.tag // also treats UnitTag, NullTag; no value required
        case AnnotatedType(annotations, tp, selfsym) =>
          val staticAnnots = staticAnnotations(annotations)
          if (staticAnnots isEmpty) {
            writeBody(tp) // write the underlying type if there are no annotations
          } else {
            if (settings.selfInAnnots.value && selfsym != NoSymbol)
              writeRef(selfsym)
            writeRef(tp)
            writeRefs(staticAnnots)
            ANNOTATEDtpe
          }

        // annotations attached to a symbol (i.e. annots on terms)
        case (target: Symbol, annot@AnnotationInfo(_, _, _)) =>
          writeRef(target)
          writeAnnotation(annot)
          SYMANNOT

        case ArrayAnnotArg(args) =>
          args foreach writeClassfileAnnotArg
          ANNOTARGARRAY

        case (target: Symbol, children: List[Symbol]) =>
          writeRef(target)
          for (c <- children) writeRef(c.asInstanceOf[Symbol])
          CHILDREN

        case EmptyTree =>
          writeNat(EMPTYtree)
          TREE

        case tree@PackageDef(pid, stats) =>
          writeNat(PACKAGEtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(tree.mods)
          writeRef(pid)
          writeRefs(stats)
          TREE

        case tree@ClassDef(mods, name, tparams, impl) =>
          writeNat(CLASStree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(mods)
          writeRef(name)
          writeRef(impl)
          writeRefs(tparams)
          TREE

        case tree@ModuleDef(mods, name, impl) =>
          writeNat(MODULEtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(mods)
          writeRef(name)
          writeRef(impl)
          TREE

        case tree@ValDef(mods, name, tpt, rhs) =>
          writeNat(VALDEFtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(mods)
          writeRef(name)
          writeRef(tpt)
          writeRef(rhs)
          TREE

        case tree@DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
          writeNat(DEFDEFtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(mods)
          writeRef(name)
          writeNat(tparams.length)
          writeRefs(tparams)
          writeNat(vparamss.length)
          for(vparams <- vparamss) {
            writeNat(vparams.length)
            writeRefs(vparams)
          }
          writeRef(tpt)
          writeRef(rhs)
          TREE

        case tree@TypeDef(mods, name, tparams, rhs) =>
          writeNat(TYPEDEFtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(mods)
          writeRef(name)
          writeRef(rhs)
          writeRefs(tparams)
          TREE

        case tree@LabelDef(name, params, rhs) =>
          writeNat(LABELtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(name)
          writeRef(rhs)
          writeRefs(params)
          TREE

        case tree@Import(expr, selectors) =>
          writeNat(IMPORTtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(expr)
          for ((from, to) <- selectors) {
            writeRef(from)
            writeRef(to)
          }
          TREE

        case tree@DocDef(comment, definition) =>
          writeNat(DOCDEFtree)
          writeRef(tree.tpe)
          writeRef(Constant(comment))
          writeRef(definition)
          TREE

        case tree@Template(parents, self, body) =>
          writeNat(TEMPLATEtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeNat(parents.length)
          writeRefs(parents)
          writeRef(self)
          writeRefs(body)
          TREE

        case tree@Block(stats, expr) =>
          writeNat(BLOCKtree)
          writeRef(tree.tpe)
          writeRef(expr)
          writeRefs(stats)
          TREE

        case tree@CaseDef(pat, guard, body) =>
          writeNat(CASEtree)
          writeRef(tree.tpe)
          writeRef(pat)
          writeRef(guard)
          writeRef(body)
          TREE

        case tree@Sequence(trees) =>
          writeNat(SEQUENCEtree)
          writeRef(tree.tpe)
          writeRefs(trees)
          TREE

        case tree@Alternative(trees) =>
          writeNat(ALTERNATIVEtree)
          writeRef(tree.tpe)
          writeRefs(trees)
          TREE

        case tree@Star(elem) =>
          writeNat(STARtree)
          writeRef(tree.tpe)
          writeRef(elem)
          TREE

        case tree@Bind(name, body) =>
          writeNat(BINDtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(name)
          writeRef(body)
          TREE

        case tree@UnApply(fun: Tree, args) =>
          writeNat(UNAPPLYtree)
          writeRef(tree.tpe)
          writeRef(fun)
          writeRefs(args)
          TREE

        case tree@ArrayValue(elemtpt, trees) =>
          writeNat(ARRAYVALUEtree)
          writeRef(tree.tpe)
          writeRef(elemtpt)
          writeRefs(trees)
          TREE


        case tree@Function(vparams, body) =>
          writeNat(FUNCTIONtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(body)
          writeRefs(vparams)
          TREE

        case tree@Assign(lhs, rhs) =>
          writeNat(ASSIGNtree)
          writeRef(tree.tpe)
          writeRef(lhs)
          writeRef(rhs)
          TREE

        case tree@If(cond, thenp, elsep) =>
          writeNat(IFtree)
          writeRef(tree.tpe)
          writeRef(cond)
          writeRef(thenp)
          writeRef(elsep)
          TREE

        case tree@Match(selector, cases) =>
          writeNat(MATCHtree)
          writeRef(tree.tpe)
          writeRef(selector)
          writeRefs(cases)
          TREE

        case tree@Return(expr) =>
          writeNat(RETURNtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(expr)
          TREE

        case tree@Try(block, catches, finalizer) =>
          writeNat(TREtree)
          writeRef(tree.tpe)
          writeRef(block)
          writeRef(finalizer)
          writeRefs(catches)
          TREE

        case tree@Throw(expr) =>
          writeNat(THROWtree)
          writeRef(tree.tpe)
          writeRef(expr)
          TREE

        case tree@New(tpt) =>
          writeNat(NEWtree)
          writeRef(tree.tpe)
          writeRef(tpt)
          TREE

        case tree@Typed(expr, tpt) =>
          writeNat(TYPEDtree)
          writeRef(tree.tpe)
          writeRef(expr)
          writeRef(tpt)
          TREE

        case tree@TypeApply(fun, args) =>
          writeNat(TYPEAPPLYtree)
          writeRef(tree.tpe)
          writeRef(fun)
          writeRefs(args)
          TREE

        case tree@Apply(fun, args) =>
          writeNat(APPLYtree)
          writeRef(tree.tpe)
          writeRef(fun)
          writeRefs(args)
          TREE

        case tree@ApplyDynamic(qual, args) =>
          writeNat(APPLYDYNAMICtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(qual)
          writeRefs(args)
          TREE

        case tree@Super(qual, mix) =>
          writeNat(SUPERtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(qual)
          writeRef(mix)
          TREE

        case tree@This(qual) =>
          writeNat(THIStree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(qual)
          TREE

        case tree@Select(qualifier, selector) =>
          writeNat(SELECTtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(qualifier)
          writeRef(selector)
          TREE

        case tree@Ident(name) =>
          writeNat(IDENTtree)
          writeRef(tree.tpe)
          writeRef(tree.symbol)
          writeRef(name)
          TREE

        case tree@Literal(value) =>
          writeNat(LITERALtree)
          writeRef(tree.tpe)
          writeRef(value)
          TREE

        case tree@TypeTree() =>
          writeNat(TYPEtree)
          writeRef(tree.tpe)
          TREE

        case tree@Annotated(annot, arg) =>
          writeNat(ANNOTATEDtree)
          writeRef(tree.tpe)
          writeRef(annot)
          writeRef(arg)
          TREE

        case tree@SingletonTypeTree(ref) =>
          writeNat(SINGLETONTYPEtree)
          writeRef(tree.tpe)
          writeRef(ref)
          TREE

        case tree@SelectFromTypeTree(qualifier, selector) =>
          writeNat(SELECTFROMTYPEtree)
          writeRef(tree.tpe)
          writeRef(qualifier)
          writeRef(selector)
          TREE

        case tree@CompoundTypeTree(templ: Template) =>
          writeNat(COMPOUNDTYPEtree)
          writeRef(tree.tpe)
          writeRef(templ)
          TREE

        case tree@AppliedTypeTree(tpt, args) =>
          writeNat(APPLIEDTYPEtree)
          writeRef(tree.tpe)
          writeRef(tpt)
          writeRefs(args)
          TREE

        case tree@TypeBoundsTree(lo, hi) =>
          writeNat(TYPEBOUNDStree)
          writeRef(tree.tpe)
          writeRef(lo)
          writeRef(hi)
          TREE

        case tree@ExistentialTypeTree(tpt, whereClauses) =>
          writeNat(EXISTENTIALTYPEtree)
          writeRef(tree.tpe)
          writeRef(tpt)
          writeRefs(whereClauses)
          TREE

        case Modifiers(flags, privateWithin, _) =>
          val pflags = rawFlagsToPickled(flags)
          writeNat((pflags >> 32).toInt)
          writeNat((pflags & 0xFFFFFFFF).toInt)
          writeRef(privateWithin)
          MODIFIERS

        // annotations on types (not linked to a symbol)
        case annot@AnnotationInfo(_, _, _) =>
          writeAnnotation(annot)
          ANNOTINFO

        case _ =>
          throw new FatalError("bad entry: " + entry + " " + entry.getClass)
      }

      // begin writeEntry
      val startpos = writeIndex
      // reserve some space so that the patchNat's most likely won't need to shift
      writeByte(0); writeByte(0)
      patchNat(startpos, writeBody(entry))
      patchNat(startpos + 1, writeIndex - (startpos + 2))
    }

    /** Print entry for diagnostics */
    private def printEntry(entry: AnyRef) {
      def printRef(ref: AnyRef) { 
        print(index(ref)+
              (if (ref.isInstanceOf[Name]) "("+ref+") " else " "))
      }
      def printRefs(refs: List[AnyRef]) { refs foreach printRef }
      def printSymInfo(sym: Symbol) {
        var posOffset = 0
        printRef(sym.name)
        printRef(normalizedOwner(sym))
        print(flagsToString(sym.flags & PickledFlags)+" ")
        if (sym.privateWithin != NoSymbol) printRef(sym.privateWithin)
        printRef(sym.info)
      }
      def printBody(entry: AnyRef) = entry match {
        case name: Name =>
          print((if (name.isTermName) "TERMname " else "TYPEname ")+name)
        case NoSymbol =>
          print("NONEsym")
        case sym: Symbol if !isLocal(sym) =>
          if (sym.isModuleClass) {
            print("EXTMODCLASSref "); printRef(sym.name.toTermName)
          } else {
            print("EXTref "); printRef(sym.name)
          }
          if (!sym.owner.isRoot) printRef(sym.owner)
        case sym: ClassSymbol =>
          print("CLASSsym ")
          printSymInfo(sym)
          if (sym.thisSym.tpe != sym.tpe) printRef(sym.typeOfThis)
        case sym: TypeSymbol =>
          print(if (sym.isAbstractType) "TYPEsym " else "ALIASsym ")
          printSymInfo(sym)
        case sym: TermSymbol =>
          print(if (sym.isModule) "MODULEsym " else "VALsym ")
          printSymInfo(sym)
          if (!sym.isModule) printRef(sym.defaultGetter)
          if (sym.alias != NoSymbol) printRef(sym.alias)
        case NoType =>
          print("NOtpe")
        case NoPrefix =>
          print("NOPREFIXtpe")
        case ThisType(sym) =>
          print("THIStpe "); printRef(sym)
        case SingleType(pre, sym) =>
          print("SINGLEtpe "); printRef(pre); printRef(sym); 
        case ConstantType(value) =>
          print("CONSTANTtpe "); printRef(value); 
        case TypeRef(pre, sym, args) =>
          print("TYPEREFtpe "); printRef(pre); printRef(sym); printRefs(args); 
        case TypeBounds(lo, hi) =>
          print("TYPEBOUNDStpe "); printRef(lo); printRef(hi); 
        case tp @ RefinedType(parents, decls) =>
          print("REFINEDtpe "); printRef(tp.typeSymbol); printRefs(parents); 
        case ClassInfoType(parents, decls, clazz) =>
          print("CLASSINFOtpe "); printRef(clazz); printRefs(parents); 
        case MethodType(formals, restpe) =>
          print(if (entry.isInstanceOf[ImplicitMethodType]) "IMPLICITMETHODtpe " else "METHODtpe ");
          printRef(restpe); printRefs(formals)
        case PolyType(tparams, restpe) =>
          print("POLYtpe "); printRef(restpe); printRefs(tparams); 
        case ExistentialType(tparams, restpe) =>
          print("EXISTENTIALtpe "); printRef(restpe); printRefs(tparams); 
        case DeBruijnIndex(l, i) =>
          print("DEBRUIJNINDEXtpe "); print(l+" "+i)
        case c @ Constant(_) =>
          print("LITERAL ")
          if (c.tag == BooleanTag) print("Boolean "+(if (c.booleanValue) 1 else 0))
          else if (c.tag == ByteTag) print("Byte "+c.longValue)
          else if (c.tag == ShortTag) print("Short "+c.longValue)
          else if (c.tag == CharTag) print("Char "+c.longValue)
          else if (c.tag == IntTag) print("Int "+c.longValue)
          else if (c.tag == LongTag) print("Long "+c.longValue)
          else if (c.tag == FloatTag) print("Float "+c.floatValue)
          else if (c.tag == DoubleTag) print("Double "+c.doubleValue)
          else if (c.tag == StringTag) { print("String "); printRef(newTermName(c.stringValue)) }
          else if (c.tag == ClassTag) { print("Class "); printRef(c.typeValue) }
          else if (c.tag == EnumTag) { print("Enum "); printRef(c.symbolValue) }
        case AnnotatedType(annots, tp, selfsym) => 
          if (settings.selfInAnnots.value) {
            print("ANNOTATEDWSELFtpe ")
            printRef(tp)
            printRef(selfsym)
            printRefs(annots)
          } else {
            print("ANNOTATEDtpe ")
            printRef(tp)
            printRefs(annots)
          }
        case (target: Symbol, AnnotationInfo(atp, args, Nil)) =>
          print("SYMANNOT ")
          printRef(target)
          printRef(atp)
          for (c <- args) printRef(c)
        case (target: Symbol, children: List[_]) =>
          print("CHILDREN ")
          printRef(target)
          for (c <- children) printRef(c.asInstanceOf[Symbol])
        case AnnotationInfo(atp, args, Nil) =>
          print("ANNOTINFO")
          printRef(atp)
          for (c <- args) printRef(c)
        case _ =>
          throw new FatalError("bad entry: " + entry + " " + entry.getClass)
      }
      printBody(entry); println()
    }

    /** Write byte array */
    def finish {
      assert(writeIndex == 0)
      writeNat(MajorVersion)
      writeNat(MinorVersion)
      writeNat(ep)
      if (showSig) {
        println("Pickled info for "+rootName+" V"+MajorVersion+"."+MinorVersion)
      }
      for (i <- 0 until ep) {
        if (showSig) {
          print((i formatted "%3d: ")+(writeIndex formatted "%5d: "))
          printEntry(entries(i))
        }
        writeEntry(entries(i))
      }
      if (settings.Xshowcls.value == rootName.toString) {
        readIndex = 0
        ShowPickled.printFile(this, Console.out)
      }
    }

    override def toString() = "" + rootName + " in " + rootOwner
  }
}