ただいまハッカソン中(`・ω・´)ハッカソン中にblogを書くという変な行動にでてみるw
だいぶ前に、作成したSVGのものは載せたけど、ソースコード載せてなかったのでとりあえずのせとく。
今日はこれをいろいろいじってみるか => 結局いじらずに違うことやりましたすいません(´・ω・`)
ちなみに、自分でも中の構造がどうなっているのかだいぶ忘れてますが・・・もう汚いまま載せておきます。
アルゴリズム汚いし、ちょっとバグってるしw
あと、ちなみに、ファイルに書き出してるところとか自作のutility使ってるから、ここに貼ったものだけじゃ動きません(´・ω・`)単なる自分のメモです
はたして3ヶ月前より成長してるだろうか
package test import scala.xml._ /** * scalaのクラス図作成? * @since 2010/07/25 */ object ClassDiagram { import reflectionTest.getAllClassAndTrait /** * @param clazz 自身のclass * @param level 自身のsubclassが多いほど大きくなる * @param parents 直接の親のリスト */ private case class ClassNode( clazz:Class[_],var level:Int,parents:Class[_] * ) extends math.Ordered[ClassNode]{ import ClassNode._ /** 間接的なものも含めた、すべての親 */ lazy val allParents = getAllClassAndTrait(this.clazz) var yoko = 0 @inline private def S(e:Any):String = e.toString /** * 比べた結果を返すというより、内部状態を変化させるため */ override def compare(that: ClassNode): Int = { if( this.parents.contains(that.clazz) ){ if( that.parents.contains(this.clazz) ){ throw new Error("循環参照? "+this.clazz+" "+that.clazz) }else{ if(that.level <= this.level){ that.level = this.level + 1 } 1 } }else{ if( that.parents.contains(this.clazz) ){ if(that.level >= this.level){ this.level = that.level + 1 } -1 }else{ if(this.allParents.contains(that.clazz)){ if(that.level <= this.level){ that.level = this.level + 1 } 1 }else if(that.allParents.contains(this.clazz)){ if(that.level >= this.level){ this.level = that.level + 1 } -1 }else{ 0 } } } } private lazy val fullName = clazz.getName private lazy val url = { val path = fullName.replace(".","/") if( fullName.startsWith("scala") ){ "http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/"+path+".html" // "http://www.scala-lang.org/api/current/"+path+".html"//普通のpath }else if(fullName.startsWith("java") ){ "http://java.sun.com/javase/ja/6/docs/ja/api/"+path+".html" }else{ "" } } def toXml:Node = { val packageFontSize = 10 val maxFontSize = 18 val baseX = yoko * w val baseY = level * h val middleX = baseX + (recW/2) val simpleName = clazz.getSimpleName val tmpSize = (recW*2)/(simpleName.length) val fontSize = if( tmpSize < maxFontSize ){ tmpSize }else{ maxFontSize } <g> <a xlink:href={url} target="_blank" > <rect x={S{ baseX }} y={S{baseY}} width={S{recW}} height ={S{recH}} fill={if(clazz.isInterface)"#799F5A"else"#7996AC"} stroke="black" stroke-width="2"> </rect> <text x={S{ baseX +5}} y={S{baseY + 15}} font-size={S{packageFontSize}} >{ fullName.replace(simpleName,"") }</text> <text x={S{ baseX +5}} y={S{baseY + packageFontSize + 24}} font-size={S{fontSize}} >{ simpleName }</text> </a> {for{ p <- parents if ! exceptList.contains(p) }yield{ val s = allClassNodes.find{ _.clazz == p }.get <line x1={S{ middleX }} y1={S{baseY + recH}} x2={S{ s.yoko * w + (recW/2)}} y2={S{s.level * h }} stroke="black" stroke-width="1"/> }} </g> } } private object ClassNode{ var allClassNodes:List[ClassNode] = Nil private val w = 120//基準位置の横幅 private val h = 200//基準位置の縦幅 private val recW = w - 20//四角形の幅 private val recH = h - 160//四角形の高さ //線をひくのをやめるやつ val exceptList = List("scala.ScalaObject","java.lang.Object").map{ Class.forName } } /** * x > 0 かつ y > 0の引数が渡ってきたときに、 * xについて単射になるように、かつ大きくなりすぎないように返す */ private def rand(x:Int,y:Int):Int = { ( x * ( y%2 + 1 )) + y%3 } /** * 実際のインスタンスから作成 * @param fileName 保存するファイル名 * @param objList 作成元のオブジェクト */ def createClassDiagramByObj(fileName:String)(objList: AnyRef *){ create( fileName , objList.map{_.getClass} ) } /** * クラスの完全修飾名から作成 * @param fileName 保存するファイル名 * @param classNames 作成するクラス名 */ def createClassDiagramByName(fileName:String)(classNames: String * ){ create(fileName, classNames.map{ Class.forName(_) } ) } /** * */ private[this] def create(fileName:String, classes: Traversable[Class[_]] ) = { import collection.{ mutable => mu } val result = sortByInheritance{ classes.foldLeft(List[ClassNode]()){ (list,clazz) => makeClassNodes( clazz ) ::: list }.distinct } //デバック用表示 result.foreach{ case (n,list) => println( n ,list.size,list.map{ _.clazz.getSimpleName } ) } //横の位置計算して決めて、ClassNodeオブジェクトのフィールドに保存 ClassNode.allClassNodes = result.flatMap{ case (_,list) => list.zipWithIndex.map{ case (data,n) => data.yoko = n * (((data.level*1.6).asInstanceOf[Int])%2 + 1 ) } // list.zipWithIndex.map{ case (data,n) => data.yoko = rand(n,data.level) } list } //xmlに変換 val xmlNodeList = ClassNode.allClassNodes.filterNot{x => ClassNode.exceptList.contains(x)}.map{ _.toXml } saveXML(fileName,xmlNodeList) } private def saveXML(fileName:String,xmlNodeList:List[scala.xml.Node]){ val width = 25000 val height = 2000 //ファイル保存 TODO 文字列で書き込むのではなく、XMLのオブジェクトにして、xml用の関数つかう utility.FileUtilO.writeFile(fileName,"Shift-JIS")( """<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body" width="""" + width + """" height=""""+ height +"""" viewBox="-10 -10 """+ width + " " + height + """"> """ + xmlNodeList.mkString(" "," "," ") + "</svg>" ) //saveSVG("""C:\Users\admin\Desktop\hoge2.svg""")(Elem(null,"g",null,xml.TopScope,xmlNodeList : _*)) println("おわり") } /** * (ソートしてない)ClassNodeのList作成 */ private def makeClassNodes(clazz:Class[_]):List[ClassNode] = { getAllClassAndTrait(clazz).map{ x => ClassNode( x ,0, x.getInterfaces : _*) } } /** * ClassNodeのListをソートし、グループ分け * (それぞれのlevelの値を変化させる) */ private def sortByInheritance(classList:List[ClassNode]):List[Pair[Int,List[ClassNode]]] = { /** ローカルのヘルパー関数,再帰 */ @scala.annotation.tailrec def loop(m:List[ClassNode]){ val oldMax = m.foldLeft(0){ (x,y) => x max y.level } for(x <- m;y <- m){//総当りで呼ぶ x.compare(y) } println("loop" ) val newMax = m.foldLeft(0){ (x,y) => x max y.level } if(oldMax != newMax){ loop(m) } } loop(classList) classList.groupBy{ _.level }.toList.sortBy(_._1) } }
/** * @since 2010/07/24 14:40:27 */ package test /** * @since 2010/07/24 14:40:27 * Listとかの階層調べるため */ object reflectionTest { def main(args: Array[String]): Unit = sub1 import java.lang.Class def sub1{ val m = getAllClassAndTrait(List().getClass).map{x => (x,x.getInterfaces) } // val hoge = groupByInterfaseCount(getAllInterfaces(1 to 10)) // hoge.foreach{ case (i,list) => println(i,list.size,list.mkString("[ "," , "," ]")) } } //interfaseの数によって、グループ分けして返す val groupByInterfaseCount = { classList:List[Class[_]] => val classNames = classList.map{c => (c, c.getSimpleName) } //class名が重複しているclassのリスト val overlap = classNames.map{ case (c,name) => (classNames.count{ case (_,y) => name == y},c) }.filterNot{ case (count,name) => count == 1 }.map{ case (_,name) => name } println(overlap.map{_.getSimpleName}.distinct);println classList.groupBy{ c => c.getInterfaces.size }.map{ case (i,list) => (i, list.map{ e => if(overlap.contains(e)){ //クラス名と、そのひとつ外側のパッケージ名 e.toString.split("""\.""").takeRight(2).reduceLeft(_+"."+_) }else{ //クラス名のみ e.getSimpleName } } ) }.toList.sortBy{ case (i,_) => i } } def getSuperClasses(clazz:Class[_]):List[Class[_]] = { @scala.annotation.tailrec def sub( o:Class[_] , result:List[Class[_]] ): List[Class[_]] = { val superClass = o.getSuperclass if(superClass == null){ result }else{ sub(superClass , superClass::result ) } } sub(clazz,List(clazz)) } //階層をたどっていってすべての親インターフェイスを探す def getAllClassAndTrait(clazz:Class[_]):List[Class[_]] = { def sub( c:Class[_] , result:List[Class[_]] ): List[Class[_]] = { val interfaces = c.getInterfaces.toList if( interfaces.size == 0 ){ result }else{ interfaces.flatMap{ i => sub( i , i::result ) } } } getSuperClasses(clazz).flatMap{x => sub(x,List(x)) }.distinct - clazz } }