/* NSC -- new Scala compiler * Copyright 2005-2009 LAMP/EPFL * @author Sean McDirmid */ // $Id: ModelFrames.scala 18387 2009-07-24 15:28:37Z odersky $ package scala.tools.nsc package doc import java.io.{File, FileWriter} import scala.util.NameTransformer import scala.collection.mutable import scala.compat.Platform.{EOL => LINE_SEPARATOR} import scala.xml.{NodeSeq, Text, Unparsed, Utility} /** This class provides HTML document framing functionality. * * @author Sean McDirmid, Stephane Micheloud */ trait ModelFrames extends ModelExtractor { import DocUtil._ def settings: doc.Settings import global.definitions.{AnyClass, AnyRefClass} val SyntheticClasses = new scala.collection.mutable.HashSet[global.Symbol]; { import global.definitions._ global.definitions.init SyntheticClasses ++= List( NothingClass, NullClass, AnyClass, AnyRefClass, AnyValClass, //value classes BooleanClass, ByteClass, CharClass, IntClass, LongClass, ShortClass, FloatClass, DoubleClass, UnitClass) } val outdir = settings.outdir.value val windowTitle = settings.windowtitle.value val docTitle = load(settings.doctitle.value) val stylesheetSetting = settings.stylesheetfile def pageHeader = load(settings.pageheader.value) def pageFooter = load(settings.pagefooter.value) def pageTop = load(settings.pagetop.value) def pageBottom = load(settings.pagebottom.value) def contentFrame = "contentFrame" def classesFrame = "classesFrame" def modulesFrame = "modulesFrame" protected val FILE_EXTENSION_HTML = ".html" protected val NAME_SUFFIX_OBJECT = "$object" protected val NAME_SUFFIX_PACKAGE = "$package" def rootTitle = (<div class="page-title">{docTitle}</div>); def rootDesc = (<p>{load("This document is the API specification for " + windowTitle)}</p>); final def hasLink(sym: global.Symbol): Boolean = if (sym == global.NoSymbol) false else if (hasLink0(sym)) true else hasLink(decode(sym.owner)) def hasLink0(sym: global.Symbol): Boolean = true abstract class Frame extends UrlContext { { // just save. save(page(title, body, hasBody)); } def path: String // relative to outdir def relative: String = { if (path eq null) return "foo" assert(path ne null) var idx = 0 var ct = new StringBuilder while (idx != -1) { idx = path.indexOf('/', idx) //System.err.println(path + " idx=" + idx) ct.append(if (idx != -1) "../" else "") idx += (if (idx == -1) 0 else 1) } ct.toString } def save(nodes: NodeSeq) = { val path = this.path if (path.startsWith("http://")) throw new Error("frame: " + this) val path0 = outdir + File.separator + path + FILE_EXTENSION_HTML //if (settings.debug.value) inform("Writing XML nodes to " + path0) val file = new File(path0) val parent = file.getParentFile() if (!parent.exists()) parent.mkdirs() val writer = new FileWriter(file) val str = dtype + LINE_SEPARATOR + nodes.toString() writer.write(str, 0, str.length()) writer.close() } protected def body: NodeSeq protected def title: String protected def hasBody = true //def urlFor(entity: Entity, target: String): NodeSeq def urlFor(entity: Entity): String = { val ret = this.urlFor(entity.sym) assert(ret != null); ret } def link(entity: Entity, target: String) = aref(urlFor(entity), target, entity.name) protected def shortHeader(entity: Entity): NodeSeq protected def longHeader(entity: Entity): NodeSeq import global._ import symtab.Flags def urlFor(sym: Symbol): String = sym match { case psym : ModuleSymbol if psym.isPackage => urlFor0(sym, sym) + FILE_EXTENSION_HTML case sym if !hasLink(sym) => null case sym if sym == AnyRefClass => urlFor0(sym, sym) + FILE_EXTENSION_HTML case msym: ModuleSymbol => urlFor0(sym, sym) + FILE_EXTENSION_HTML case csym: ClassSymbol => urlFor0(sym, sym) + FILE_EXTENSION_HTML case _ => val cnt = urlFor(decode(sym.owner)) if (cnt == null) null else cnt + "#" + docName(sym) } def docName(sym: Symbol): String = { def javaParams(paramTypes: List[Type]): String = { def javaName(pt: Type): String = { val s = pt.toString val matVal = patVal.matcher(s) if (matVal.matches) matVal.group(1).toLowerCase else s.replaceAll("\\$", ".") } paramTypes.map(pt => javaName(pt)).mkString("(", ",", ")") } def scalaParams(paramTypes: List[Type]): String = { def scalaName(pt: Type): String = pt.toString.replaceAll(" ", "") paramTypes.map(pt => scalaName(pt)).mkString("(", ",", ")") } java.net.URLEncoder.encode(sym.nameString + (sym.tpe match { case MethodType(params, _) => val paramTypes = params map (_.tpe) if (sym hasFlag Flags.JAVA) javaParams(paramTypes) else scalaParams(paramTypes) case PolyType(_, MethodType(params, _)) => val paramTypes = params map (_.tpe) if (sym hasFlag Flags.JAVA) javaParams(paramTypes) else scalaParams(paramTypes) case _ => "" }), encoding) } def urlFor0(sym: Symbol, orig: Symbol): String = (if (sym == NoSymbol) "XXX" else if (sym.owner.isPackageClass) rootFor(sym) + pkgPath(sym) else urlFor0(decode(sym.owner), orig) + "." + NameTransformer.encode(Utility.escape(sym.nameString)) ) + (sym match { case msym: ModuleSymbol => if (msym hasFlag Flags.PACKAGE) NAME_SUFFIX_PACKAGE else NAME_SUFFIX_OBJECT case csym: ClassSymbol if csym.isModuleClass => if (csym hasFlag Flags.PACKAGE) NAME_SUFFIX_PACKAGE else NAME_SUFFIX_OBJECT case _ => "" }) } def pkgPath(sym : global.Symbol) = sym.fullNameString('/') match { case "<empty>" => "_empty_" case path => path } protected def rootFor(sym: global.Symbol) = "" abstract class AllPackagesFrame extends Frame { override lazy val path = "modules" override lazy val title = "List of all packages" def packages: Iterable[Package] override def body: NodeSeq = (<div> <div class="doctitle-larger">{windowTitle}</div> <a href="all-classes.html" target={classesFrame} onclick="resetKind();">{"All objects and classes"}</a> </div> <div class="kinds">Packages</div> <ul class="list">{sort(packages).mkXML("","\n","")(pkg => { (<li><a href={urlFor(pkg)} target={classesFrame} onclick="resetKind();"> {pkg.fullName('.')}</a></li>) })} </ul>); } abstract class PackagesContentFrame extends Frame { lazy val path = "root-content" lazy val title = "All Packages" def packages : Iterable[Package] //def modules: TreeMap[String, ModuleClassSymbol] def body: NodeSeq = {rootTitle} ++ {rootDesc} ++ (<hr/>) ++ (<table cellpadding="3" class="member" summary=""> <tr><td colspan="2" class="title">Package Summary</td></tr> {sort(packages).mkXML("","\n","")(pkg => (<tr><td class="signature"> <code>package {aref(pkgPath(pkg.sym) + "$content.html", "_self", pkg.fullName('.'))} </code> </td></tr>))} </table>); } val classFrameKinds = Classes :: Objects :: Nil; abstract class ListClassFrame extends Frame { def classes: Iterable[ClassOrObject] def navLabel: String private def navPath = { val p = path; (if (p endsWith NAME_SUFFIX_PACKAGE) p.substring(0, p.length() - NAME_SUFFIX_PACKAGE.length()); else p) + navSuffix; } protected def navSuffix = "$content.html" def body: NodeSeq = { val nav = if (navLabel == null) NodeSeq.Empty else (<table class="navigation" summary=""> <tr><td valign="top" class="navigation-links"> {aref(navPath, contentFrame, navLabel)} </td></tr> </table>); val ids = new mutable.LinkedHashSet[String] def idFor(kind: Category, t: Entity)(seq : NodeSeq): NodeSeq = { val ch = t.listName.charAt(0); val id = kind.plural + "_" + ch; if (ids contains id) (<li>{seq}</li>); else { ids += id; (<li id={id}>{seq}</li>) }; } val body = (<div>{classFrameKinds.mkXML("","\n","")(kind => { val classes = sort(this.classes.filter(e => kind.f(e.sym))); if (classes.isEmpty) NodeSeq.Empty; else (<div id={kind.plural} class="kinds">{Text(kind.plural)}</div> <ul class="list"> {classes.mkXML("","\n","")(cls => { idFor(kind, cls)( aref(urlFor(cls), contentFrame, cls.listName) ++ optional(cls) ); })} </ul>); })}</div>); nav ++ body } def optional(cls: ClassOrObject): NodeSeq = NodeSeq.Empty } abstract class PackageContentFrame extends Frame { override def path = pkgPath(pkg.sym) + "$content" override def title = "All classes and objects in " + pkg.fullName('.') protected def pkg: Package protected def classes: Iterable[ClassOrObject] def body: NodeSeq = {rootTitle} ++ {rootDesc} ++ {classFrameKinds.mkXML("","\n","")(kind => { val classes = sort(this.classes.filter(e => kind.f(e.sym) && e.isInstanceOf[TopLevel])); if (classes.isEmpty) NodeSeq.Empty else (<table cellpadding="3" class="member" summary=""> <tr><td colspan="2" class="title">{kind.label} Summary</td></tr> {classes.mkXML("","\n","")(shortHeader)} </table>) })}; } abstract class ClassContentFrame extends Frame { def clazz: ClassOrObject def body: NodeSeq = (<xml:group> {pageHeader}{navigation}{pageTop} {header0}{longHeader(clazz)} {pageBottom}{navigation}{pageFooter} </xml:group>); final def path = urlFor0(clazz.sym, clazz.sym) private def navigation: NodeSeq = (<table class="navigation" summary=""> <tr> <td valign="top" class="navigation-links"> <!-- <table><tr></tr></table> --> </td> <td align="right" valign="top" style="white-space:nowrap;" rowspan="2"> <div class="doctitle-larger">{windowTitle}</div> </td> </tr> <tr><td></td></tr> </table>); private def header0: NodeSeq = { val owner = decode(clazz.sym.owner) (<xml:group> <div class="entity"> {aref(urlFor(owner), "_self", owner.fullNameString('.'))} <br/> <span class="entity">{Text(clazz.kind)} {Text(clazz.name)}</span> </div><hr/> <div class="source"> { if (SyntheticClasses contains clazz.sym) Text("[Source: none]") else { val name = owner.fullNameString('/') + (if (owner.isPackage) "/" + clazz.name else "") Text("[source: ") ++ (<a class={name} href=""><code>{name + ".scala"}</code></a>) ++ Text("]") } } </div><hr/> </xml:group>) } } val index = (<frameset cols="25%, 75%"> <frameset rows="50%, 28, 50%"> <frame src="modules.html" name={modulesFrame}></frame> <frame src="nav-classes.html" name="navigationFrame"></frame> <frame src="all-classes.html" name={classesFrame}></frame> </frameset> <frame src="root-content.html" name={contentFrame}></frame> </frameset>); val root = (<b></b>); abstract class RootFrame extends Frame { def title = windowTitle def body = index def path = "index" override def hasBody = false } val indexChars = 'A' :: 'B' :: 'C' :: 'D' :: 'E' :: 'G' :: 'I' :: 'L' :: 'M' :: 'P' :: 'R' :: 'T' :: 'V' :: 'X' :: Nil; abstract class NavigationFrame extends Frame { def title="navigation" def path="nav-classes" override def body0(hasBody: Boolean, nodes: NodeSeq): NodeSeq = if (!hasBody) nodes else (<body style="margin:1px 0 0 1px; padding:1px 0 0 1px;">{nodes}</body>); def body = (<form> <select id="kinds" onchange="gotoKind()"> <option value="#Classes" selected="selected">Classes</option> <option value="#Objects">Objects</option> </select> <span id="alphabet" style="font-family:Courier;word-spacing:-8px;">{ indexChars.mkXML("","\n","")(c => { (<a href={Unparsed("javascript:gotoName(\'" + c + "\')")}>{c}</a>) }); } </span> </form>) } def copyResources = { import java.io._ val loader = this.getClass().getClassLoader() def basename(path: String): String = { val pos = path lastIndexOf System.getProperty("file.separator", "/") if (pos != -1) path.substring(pos + 1) else path } def copyResource(name: String, isFile: Boolean) = try { val (in, outfile) = if (isFile) (new FileInputStream(name), basename(name)) else { // The name of a resource is a '/'-separated path name that identifies the resource. (loader.getResourceAsStream("scala/tools/nsc/doc/" + name), name) } val out = new FileOutputStream(new File(outdir + File.separator + outfile)) val buf = new Array[Byte](1024) var len = 0 while (len != -1) { out.write(buf, 0, len) len = in.read(buf) } in.close() out.close() } catch { case _ => System.err.println("Resource file '" + name + "' not found") } copyResource(stylesheetSetting.value, !stylesheetSetting.isDefault) copyResource("script.js", false) } private val patVal = java.util.regex.Pattern.compile( "scala\\.(Byte|Boolean|Char|Double|Float|Int|Long|Short)") }