/* NSC -- new Scala compiler * Copyright 2007-2009 LAMP/EPFL * @author Sean McDirmid */ // $Id: DefaultDocDriver.scala 18387 2009-07-24 15:28:37Z odersky $ package scala.tools.nsc package doc import scala.collection.mutable import java.util.zip.ZipFile import symtab.Flags._ import scala.xml._ /** * @author Sean McDirmid */ abstract class DefaultDocDriver extends DocDriver with ModelFrames with ModelToXML { import global._ import definitions.{AnyClass, AnyRefClass} lazy val additions = new mutable.LinkedHashSet[Symbol] lazy val additions0 = new ModelAdditions(global) { override def addition(sym: global.Symbol) = { super.addition(sym) sym match { case sym : global.ClassSymbol => additions += sym.asInstanceOf[Symbol] case sym : global.ModuleSymbol => additions += sym.asInstanceOf[Symbol] case sym : global.TypeSymbol => additions += sym.asInstanceOf[Symbol] case _ => } } def init {} } /** Add all top-level entities in ModelAdditions to allClasses */ def addAdditionsToClasses() { additions0.init for (sym <- additions) { val packSym = sym.enclosingPackage if (packSym != NoSymbol) { val pack = Package(packSym) if (!(allClasses contains pack)) { // don't emit an addition unless its package // is already being scaladoced } else { val addition: Option[ClassOrObject] = if (sym.isClass) Some(new TopLevelClass(sym)) else if (sym.isModule) Some(new TopLevelObject(sym)) else if (sym == definitions.AnyRefClass) { // AnyRef is the only top-level type alias, so handle // it specially instead of introducing general support for // top-level aliases Some(new TopLevelClass(sym)) } else None addition match { case None => //println("skipping: " + sym) //DEBUG case Some(addition) => allClasses(pack) += addition } } } else { //println("no package found for: "+sym) //DEBUG } } } def process(units: Iterator[CompilationUnit]) { assert(global.definitions != null) def g(pkg: Package, clazz: ClassOrObject) { if (isAccessible(clazz.sym)) { allClasses(pkg) += clazz clazz.decls.map(_._2).foreach { case clazz : ClassOrObject => g(pkg, clazz) case _ => } } } def f(pkg: Package, tree: Tree) { if (tree != EmptyTree && tree.hasSymbol) { val sym = tree.symbol if (sym != NoSymbol && !sym.hasFlag(symtab.Flags.PRIVATE)) tree match { case tree : PackageDef => val pkg1 = new Package(sym.asInstanceOf[ModuleSymbol]) tree.stats.foreach(stat => f(pkg1, stat)) case tree : ClassDef => assert(pkg != null) g(pkg, new TopLevelClass(sym.asInstanceOf[ClassSymbol])) case tree : ModuleDef => assert(pkg != null) g(pkg, new TopLevelObject(sym.asInstanceOf[ModuleSymbol])) case _ => } } } units.foreach(unit => f(null, unit.body)) addAdditionsToClasses() for (p <- allClasses; d <- p._2) { symbols += d.sym for (pp <- d.sym.tpe.parents) subClasses(pp.typeSymbol) += d } copyResources lazy val packages0 = sort(allClasses.keySet) new AllPackagesFrame with Frame { def packages = packages0 } new PackagesContentFrame with Frame { def packages = packages0 } new NavigationFrame with Frame { } new ListClassFrame with Frame { def classes = for (p <- allClasses; d <- p._2) yield d object organized extends mutable.LinkedHashMap[(List[String],Boolean),List[ClassOrObject]] { override def default(key : (List[String],Boolean)) = Nil; classes.foreach(cls => { val path = cls.path.map(_.name); this((path,cls.isInstanceOf[Clazz])) = cls :: this((path,cls.isInstanceOf[Clazz])); }); } def title = "List of all classes and objects" def path = "all-classes" def navLabel = null // "root-page" // override protected def navSuffix = ".html"; override def optional(cls: ClassOrObject): NodeSeq = { val path = cls.path.map(_.name) val key = (cls.path.map(_.name), cls.isInstanceOf[Clazz]) assert(!organized(key).isEmpty); ((if (!organized(key).tail.isEmpty) Text(" (" +{ //Console.println("CONFLICT: " + path + " " + organized(key)); val str = cls.path(0).sym.owner.fullNameString('.'); val idx = str.lastIndexOf('.'); if (idx == -1) str; else str.substring(idx + 1); }+ ")"); else NodeSeq.Empty) ++ super.optional(cls))(NodeSeq.builderFactory) } } for ((pkg0, classes0) <- allClasses) { new ListClassFrame with Frame { def title = "List of classes and objects in package " + pkg0.fullName('.') def classes = classes0 def path = pkgPath(pkg0.sym) + NAME_SUFFIX_PACKAGE def navLabel = pkg0.fullName('.') } new PackageContentFrame with Frame { def classes = classes0 def pkg = pkg0 } for (clazz0 <- classes0) { new ClassContentFrame with Frame { def clazz = clazz0 def title = clazz0.kind + " " + clazz0.name + " in " + (clazz0.sym.owner.fullNameString('.')); } } } new RootFrame with Frame } override def longList(entity: ClassOrObject, category: Category)(implicit from: Frame) : NodeSeq = category match { case Classes | Objects => NodeSeq.Empty case _ => super.longList(entity, category) } trait Frame extends super.Frame { def longHeader(entity : Entity) = DefaultDocDriver.this.longHeader(entity)(this) def shortHeader(entity : Entity) = DefaultDocDriver.this.shortHeader(entity)(this) } import DocUtil._ override def classBody(entity: ClassOrObject)(implicit from: Frame): NodeSeq = (((subClasses.get(entity.sym) match { case Some(symbols) => (<dl> <dt style="margin:10px 0 0 20px;"><b>Direct Known Subclasses:</b></dt> <dd>{symbols.mkXML("",", ","")(cls => { aref(urlFor(cls.sym), cls.path.map(_.name).mkString("",".","")); })}</dd> </dl><hr/>); case None => NodeSeq.Empty }): NodeSeq)++super.classBody(entity))//(NodeSeq.builderFactory) protected def urlFor(sym: Symbol)(implicit frame: Frame) = frame.urlFor(sym) override protected def decodeTag(tag: String): String = tag match { case "exception" => "Throws" case "ex" => "Examples" case "param" => "Parameters" case "pre" => "Precondition" case "return" => "Returns" case "note" => "Notes" case "see" => "See Also" case tag => super.decodeTag(tag) } override protected def decodeOption(tag: String, option: String): NodeSeq = tag match { case "throws" if additions0.exceptions.contains(option) => val (sym, s) = additions0.exceptions(option) val path = "../" //todo: fix path val href = path + sym.fullNameString('/') + (if (sym.isModule || sym.isModuleClass) NAME_SUFFIX_OBJECT else "") + "#" + s (<a href={href}>{option}</a>) ++ {Text(" - ")}; case _ => super.decodeOption(tag,option) } object roots extends mutable.LinkedHashMap[String,String]; roots("classes") = "http://java.sun.com/j2se/1.5.0/docs/api"; roots("rt") = roots("classes"); private val SCALA_API_ROOT = "http://www.scala-lang.org/docu/files/api/"; roots("scala-library") = SCALA_API_ROOT; private def keyFor(file: ZipFile): String = { var name = file.getName var idx = name.lastIndexOf(java.io.File.pathSeparator) if (idx == -1) idx = name.lastIndexOf('/') if (idx != -1) name = name.substring(idx + 1) if (name endsWith ".jar") name.substring(0, name.length - (".jar").length) else null } // <code>{Text(string + " - ")}</code>; override def hasLink0(sym: Symbol): Boolean = { if (sym == NoSymbol) return false; if (sym == AnyRefClass) { // AnyRefClass is a type alias, so the following logic // does not work. AnyClass should have a link in // the same cases as AnyRefClass, so test it instead. return hasLink(AnyClass) } if (super.hasLink0(sym) && symbols.contains(sym)) return true; if (SyntheticClasses contains sym) return true; if (sym.toplevelClass == NoSymbol) return false; val clazz = sym.toplevelClass.asInstanceOf[ClassSymbol]; import scala.tools.nsc.io._; clazz.classFile match { case file : ZipArchive#FileEntry => val key = keyFor(file.archive); if (key != null && roots.contains(key)) return true; case null => case _ => } false } def aref(href: String, label: String)(implicit frame: Frame) = frame.aref(href, "_self", label) protected def anchor(entity: Symbol)(implicit frame: Frame): NodeSeq = (<a name={Text(frame.docName(entity))}></a>) object symbols extends mutable.LinkedHashSet[Symbol] object allClasses extends mutable.LinkedHashMap[Package, mutable.LinkedHashSet[ClassOrObject]] { override def default(pkg: Package): mutable.LinkedHashSet[ClassOrObject] = { object ret extends mutable.LinkedHashSet[ClassOrObject] this(pkg) = ret ret } } object subClasses extends mutable.LinkedHashMap[Symbol, mutable.LinkedHashSet[ClassOrObject]] { override def default(key: Symbol) = { val ret = new mutable.LinkedHashSet[ClassOrObject] this(key) = ret ret } } override def rootFor(sym: Symbol): String = { assert(sym != NoSymbol) if (sym == definitions.AnyRefClass) { // AnyRefClass is a type alias, so the following logic // does not work. AnyClass should have the same root, // so use it instead. return rootFor(definitions.AnyClass) } if (sym.toplevelClass == NoSymbol) return super.rootFor(sym) if (symbols.contains(sym.toplevelClass)) return super.rootFor(sym) if (SyntheticClasses contains sym) return SCALA_API_ROOT val clazz = sym.toplevelClass.asInstanceOf[ClassSymbol] import scala.tools.nsc.io._; clazz.classFile match { case file : ZipArchive#FileEntry => val key = keyFor(file.archive) if (key != null && roots.contains(key)) { return roots(key) + '/' } case _ => } super.rootFor(sym) } }