All Downloads are FREE. Search and download functionalities are using the official Maven repository.

apparat.abc.analysis.AbcUMLExport.scala Maven / Gradle / Ivy

/*
 * This file is part of Apparat.
 *
 * Apparat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Apparat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Apparat. If not, see .
 *
 * Copyright (C) 2010 Joa Ebert
 * http://www.joa-ebert.com/
 *
 */
package apparat.abc.analysis

import apparat.graph.{Edge, GraphLike, DOTExport}
import apparat.utils.IndentingPrintWriter
import apparat.utils.IO._
import java.io.{File => JFile, PrintStream => JPrintStream, PrintWriter => JPrintWriter, FileOutputStream => JFileOutputStream}
import scala.collection.mutable.HashMap
import scala.collection.mutable.ListBuffer
import apparat.abc._

/**
 * @author Joa Ebert
 */
class AbcUMLExport(abcs: List[Abc]) {
	private lazy val indices = {
		var map: HashMap[AbcName, Int] = HashMap.empty
		var index = 0
		
		for(abc <- abcs; `type` <- abc.types) {
			map get `type`.inst.name match {
				case Some(_) =>
				case None => {
					map += `type`.inst.name -> index
					index += 1
				}
			}
		}

		map
	}

	private lazy val packages = {
		var map: HashMap[Symbol, List[AbcNominalType]] = HashMap.empty

		for(abc <- abcs; `type` <- abc.types) {
			val `package` = `type`.inst.name.namespace.name
			map(`package`) = `type` :: map.getOrElse(`package`, List.empty)
		}

		map
	}

	private lazy val topLevel = Symbol("TopLevel")

	def to(pathname: String): Unit = to(new JFile(pathname))

	def to(file: JFile): Unit = using(new JFileOutputStream(file)){
		stream => to(new IndentingPrintWriter(new JPrintWriter(stream)))
	}

	def to(stream: JPrintStream): Unit = to(new JPrintWriter(stream))

	def to(writer: JPrintWriter): Unit = {
		val indentingPrintWriter = new IndentingPrintWriter(writer)
		to(indentingPrintWriter)
		indentingPrintWriter.flush()
	}

	def to(writer: IndentingPrintWriter): Unit = {
		val fontName = "Bitstream Vera Sans Mono"
		val fontSize = 8

		writer <= "digraph G {"
		writer withIndent {
			writer <= "fontname=\""+fontName+"\";"
			writer <= "fontsize="+fontSize+";"
			writer <= "ranksep=2;"
			writer <= "ratio=auto;"

			writer <= "graph["
			writer withIndent {
				writer <= "rankdir= \"TB\","
				writer <= "splines= true,"
				writer <= "overlap= false"
			}
			writer <= "];"

			writer <= "node["
			writer withIndent {
				writer <= "fontname=\""+fontName+"\","
				writer <= "fontsize="+fontSize+","
				writer <= "shape=\"record\""
			}
			writer <= "];"

			writer <= "edge["
			writer withIndent {
				writer <= "fontname=\"" + fontName + "\","
				writer <= "fontsize=" + fontSize + ","
				writer <= "arrowhead=\"empty\""
			}
			writer <= "];"

			printPackages(writer)
			printInheritance(writer)

			writer <= "label=\"Powered by Apparat - http://apparat.googlecode.com/\";"
		}
		writer <= "}"
	}

	private def printPackages(implicit writer: IndentingPrintWriter) = {
		var i = 0

		for((name, types) <- packages.elements if name != null && name.name != null) {
			val `package` = name.name.length match {
				case 0 => topLevel
				case _ => name
			}

			writer <= "subgraph clusterP"+i+"{"
			writer withIndent {
				printTypes(types)
				writer <= "label=\""+`package`.name+"\";"
			}
			writer <= "}"

			i += 1
		}
	}

	private def printInheritance(implicit writer: IndentingPrintWriter) = {
		writer <= "edge[style=\"solid\"];"

		for(abc <- abcs; `type` <- abc.types if `type`.inst.base.isDefined) {
			indices get `type`.inst.base.get match {
				case Some(index) => writer <= indices(`type`.inst.name)+" -> "+index+";"
				case None =>
			}
		}

		writer <= "edge[style=\"dashed\"];"

		for(abc <- abcs; `type` <- abc.types; interface <- `type`.inst.interfaces) {
			indices get interface match {
				case Some(index) => writer <= indices(`type`.inst.name)+" -> "+index+";"
				case None =>
			}
		}
	}
	
	private def printTypes(types: List[AbcNominalType])(implicit writer: IndentingPrintWriter) = {
		for(`type` <- types) {
			val buffer = new StringBuilder()
			var properties = ListBuffer.empty[String]
			var methods = ListBuffer.empty[String]
			val inst = `type`.inst

			exportTraits(inst.traits, properties, methods, "")
			exportTraits(`type`.klass.traits, properties, methods, "$ ")

			if(inst.isInterface) {
				 buffer append "\\<\\\\>\\n"
			}

			buffer append inst.name.name.name

			if(inst.isFinal) {
				buffer append "\\n\\{final\\}"
			}

			if(!inst.isInterface)
			{
				buffer append "|"

				for(property <- properties.sorted) {
					buffer append property
					buffer append "\\l"
				}
			}

			buffer append "|"

			for(method <- methods.sorted) {
				buffer append method
				buffer append "\\l"
			}

			writer <= indices(inst.name)+" [label=\"{"+buffer.toString()+"}\"];"
		}
	}

	private def exportTraits(traits: Array[AbcTrait], properties: ListBuffer[String], methods: ListBuffer[String], prefix: String) = {
		var visited = HashMap.empty[AbcName, Boolean]

		//name.name.name!
		for(`trait` <- traits) {
			`trait` match {
				case AbcTraitConst(name, _, typeName, valueType, value, _) => {
					properties += prefix +
						getVisibility(name) + name.name.name + ": " + getType(typeName) + (value match {
							case Some(value) => " = " + getValue(valueType.get, value)
							case None => ""
						}) + " \\{read-only\\}"
				}

				case AbcTraitSlot(name, _, typeName, valueType, value, _) => {
					properties += prefix +
						getVisibility(name) + name.name.name + ": " + getType(typeName) + (value match {
							case Some(value) => " = " + getValue(valueType.get, value)
							case None => ""
						})
				}

				case AbcTraitMethod(name, _, method, isFinal, isOverride, _) => {
					methods += prefix +
						getVisibility(name) + name.name.name + "(" +
						getParameters(method) + "): " + getType(method.returnType) +
						(isOverride match {
							case true => " \\{redefines "+name.name.name+"\\}"
							case false => ""
						}) +
						(isFinal match {
							case true => " \\{final\\}"
							case false => ""
						})
				}

				case AbcTraitGetter(name, _, method, isFinal, isOverride, _) => if(!(visited contains name)) {
					val hasSetter = traits exists {
						case AbcTraitSetter(setterName, _, _, _, _, _) if name == setterName => true
						case _ => false
					}

					visited += name -> true

					properties += prefix +
						getVisibility(name) + name.name.name + ": " + getType(method.returnType) +
						(isOverride match {
							case true => " \\{redefines "+name.name.name+"\\}"
							case false => ""
						}) +
						(isFinal match {
							case true => " \\{final\\}"
							case false => ""
						}) +
						(hasSetter match {
							case true => ""
							case false => " \\{read-only\\}"
						})
				}

				case AbcTraitSetter(name, _, method, isFinal, isOverride, _) => if(!(visited contains name)) {
					val hasGetter = traits exists {
						case AbcTraitGetter(getterName, _, _, _, _, _) if name == getterName => true
						case _ => false
					}

					visited += name -> true
					
					properties += prefix +
						getVisibility(name) + name.name.name + ": " + getType(method.returnType) +
						(isOverride match {
							case true => " \\{redefines "+name.name.name+"\\}"
							case false => ""
						}) +
						(isFinal match {
							case true => " \\{final\\}"
							case false => ""
						}) +
						(hasGetter match {
							case true => ""
							case false => " \\{write-only\\}"
						})
				}

				case _ =>
			}
		}
	}

	private def getVisibility(name: AbcQName) = name.namespace.kind match {
		case AbcNamespaceKind.PackageInternal => "~ "
		case AbcNamespaceKind.Package => "+ "
		case AbcNamespaceKind.Private => "- "
		case AbcNamespaceKind.Protected => "# "
		case _ => ""
	}
	
	private def getType(name: AbcName): String = name match {
		case AbcQName(name, _) => name.name
		case AbcTypename(name, parameters) => name.name.name + ".\\<"+getType(parameters(0))+"\\>"
		case _ => "*"
	}

	private def getValue(valueType: Int, value: Any) = valueType match {
		case AbcConstantType.Double => value.asInstanceOf[Double].toString
		case AbcConstantType.False => "false"
		case AbcConstantType.Int => value.asInstanceOf[Int].toString
		case AbcConstantType.Null => "null"
		case AbcConstantType.True => "true"
		case AbcConstantType.UInt => value.asInstanceOf[Long].toString
		case AbcConstantType.Undefined => "undefined"
		case AbcConstantType.Utf8 => "\\\""+escape(value.asInstanceOf[Symbol].name)+"\\\""
		case _ => "\\"
	}

	private def getParameters(method: AbcMethod) = {
		val buffer = new StringBuilder

		val n = method.parameters.length
		val m = n - 1
		var i = 0

		while(i < n) {
			val parameter = method.parameters(i)

			parameter.name match {
				case Some(Symbol(name)) if name.length != 0 => name
				case _ => buffer append "p"+i
			}

			buffer append ": "
			buffer append getType(parameter.typeName)

			if(parameter.optional) {
				buffer append " = "+getValue(parameter.optionalType.get, parameter.optionalVal.get)
			}

			if(i != m) {
				buffer append ", "
			}

			i += 1
		}

		buffer.toString
	}

	private def escape(value: String) = value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\\\\\n").replaceAll("\"", "\\\\\"")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy