2011-03-25
scalaとかjavaとかのclass図を表示するサイト作った
scala, clojure, jruby, groovy, GAE, java, svg | |
![]()
このクラス図表示するプログラム自体は、結構まえにつくったもので、いつかGAE上にのせてみたいと思ってたのを、やっとのせたという。とりあえずバグだらけ*1なおったはず。だけど、GAEにのせて、なんとなく動いているので、一応blogで宣伝(?)
http://class-diagrams.appspot.com
たとえば、scalaのListなら、
http://class-diagrams.appspot.com/scala.collection.immutable.$colon$colon
という感じで、class名をそのまま入力するだけです。あとSVGなので、割と新しめのブラウザじゃないとみれませんよ。
一応スクリーンショットの画像も↓
仕組みは、別に特別なことしてるわけではなく、Javaのリフレクション使ってるだけなので、JVMで動いてるものなら、どんな言語だろうが、どんなライブラリだろうが、なんでも表示できるはず。もちろんjavaのclassも表示できます。
とりあえず groovy の1.7.10も突っ込んでみました。一応表示されるっぽい
追記:clojure1.2.0とjruby1.6.0も入れてみた
ちょっとversion 随時変えてるので、正しくないけど、いろいろ入れてあるので、これ見て
http://class-diagrams.appspot.com/clojure.lang.Ref
http://class-diagrams.appspot.com/org.jruby.runtime.scope.DummyDynamicScope
まだ全然未完成で、今後どうやって開発していくかも気分次第なので、突然表示されなくなることもあるかもしれないので、その辺ご了承ください(´・ω・`)
githubにソースコード公開してある
2010-11-03
3ヶ月くらい前に書いたclass図作成プログラム
ただいまハッカソン中(`・ω・´)ハッカソン中にblogを書くという変な行動にでてみるw
だいぶ前に、作成したSVGのものは載せたけど、ソースコード載せてなかったのでとりあえずのせとく。
今日はこれをいろいろいじってみるか => 結局いじらずに違うことやりましたすいません(´・ω・`)
ちなみに、自分でも中の構造がどうなっているのかだいぶ忘れてますが・・・もう汚いまま載せておきます。
あと、ちなみに、ファイルに書き出してるところとか自作の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 } }


