マニアック JDK コマンド

この記事は「[エムスリー Advent Calendar 2016](http://qiita.com/advent-calendar/2016/m3)」の 22日目の記事です。

ant や maven、その他様々なエコシステムがある今、JDK コマンドを直で使う機会というのはそうそう無いのではないかと思います。
（maven 無しで、`javac` でプロジェクトのパッケージングをやれと言われたら結構大変そうです:sweat_smile:）

`jvisualvm`, `jstack`, `jmap` などある程度メジャーなコマンドは、解析やトラブル対応などで使ったことがありますが、それ以外にも JDK には沢山の付属コマンドがあります。
今回は、${JAVA_HOME}/bin の下にあるマニアックなコマンドについて調べてみたので、それをいくつか紹介します。

ちなみに、${JAVA_HOME}/bin の下には以下のようなコマンドがありました。

```
appletviewer   jarsigner      javafxpackager jcmd           jhat           jmc            jstack         keytool        policytool     schemagen      unpack200
extcheck       java           javah          jconsole       jinfo          jps            jstat          native2ascii   rmic           serialver      wsgen
idlj           javac          javap          jdb            jjs            jrunscript     jstatd         orbd           rmid           servertool     wsimport
jar            javadoc        javapackager   jdeps          jmap           jsadebugd      jvisualvm      pack200        rmiregistry    tnameserv      xjc
```

※ 今回は Oracle JDK で調べてみましたが、OpenJDK 等、他の実装では違いがあるかもしれません。

## serialver

> serialverコマンドは、1つ以上のクラスのserialVersionUIDを、展開しているクラスにコピーするのに適した形式で返します。引数を指定しないでserialverコマンドを呼び出すと、使用法を示す行が出力されます。
> https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/serialver.html

`serialVersionUID` を返すコマンドのようです。実際に使ってみます。

```java:TestModel.java
package hoge;

public class TestModel {

    private int hoge;

    public int getHoge() {
        return hoge;
    }

    public void setHoge(int hoge) {
        this.hoge = hoge;
    }
}
```

```shell-session
$ ${JAVA_HOME}/bin/serialver hoge.TestModel
クラスhoge.TestModelは直列化できません。
```

おっと、Serializable を implements してないのでエラーになってしまいました。
気を取り直して、Serializable を implements してみます。

```java:TestModel.java
public class TestModel implements Serializable {

    private int hoge;

    public int getHoge() {
        return hoge;
    }

    public void setHoge(int hoge) {
        this.hoge = hoge;
    }
}
```

```shell-session
$ ${JAVA_HOME}/bin/serialver hoge.TestModel
hoge.TestModel:    private static final long serialVersionUID = -2243945887951876522L;
```

クラスには `serialVersionUID` の定義を書いてないですが、自動計算されたっぽい値が返ってきました。これはどういうことなのでしょうか。
答えは Javadoc にありました。

> 直列化可能クラスがserialVersionUIDを明示的に宣言しない場合、直列化ランタイムは「Java(TM)オブジェクト直列化仕様」で説明されているように、クラスのさまざまな側面に基づいて、クラスのserialVersionUIDのデフォルト値を計算します。
> https://docs.oracle.com/javase/jp/8/docs/api/java/io/Serializable.html

最後に、自分で定義した値が返ってくるか確認します。

```java:TestModel.java
public class TestModel implements Serializable {

    private static final long serialVersionUID = 1L;

    private int hoge;

    public int getHoge() {
        return hoge;
    }

    public void setHoge(int hoge) {
        this.hoge = hoge;
    }
}
```

```shell-session
$ ${JAVA_HOME}/bin/serialver hoge.TestModel
hoge.TestModel:    private static final long serialVersionUID = 1L;
```

よさそうです。

ところで、リファクタリングなどでクラス構造が変わった場合、`serialVersionUID` も追随して変えないといけないと思いますが、これをちゃんとやってるのを見たことないですね。まぁそういうことが必要になるシステム（オブジェクトをシリアライズして、NW 転送して、別 VM で使うとか）が世の中にどれだけあるのか？という話ですね。

しかし、このコマンドはどういう場面で使うんでしょうかね？

## jrunscript

> jrunscriptコマンドは、言語に依存しないコマンド行スクリプト・シェルです。jrunscriptコマンドでは、対話型(読取り-評価-出力)モードとバッチ(-fオプション)モードの両方のスクリプト実行がサポートされています。デフォルトの使用言語はJavaScriptですが、-lオプションを使えば他の言語も指定できます。Javaとスクリプト言語との通信を使用することで、jrunscriptコマンドで探求的なプログラミング・スタイルがサポートされます。
> https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/jrunscript.html

JVM 上で他の言語（デフォルトでは JavaScript）を実行できるようになるとのことです。

試してみます。

```shell-session
$ ${JAVA_HOME}/bin/jrunscript -e "print('hello world')"
hello world
```

Java の API を使ったり、組み込み関数もいくつかサポートされているようです。

```js:sample.js
java.lang.System.out.println(new java.util.Date())
date()
pwd()
```

```shell-session
$ ${JAVA_HOME}/bin/jrunscript -f sample.js
Mon Dec 12 12:40:49 JST 2016
Mon Dec 12 2016 12:40:49 GMT+0900 (JST)
/Users/sinsengumi/Documents/work/20161212/.
```

これを使えば、クロスプラットフォームなスクリプトが書けたりするのでしょうか・・・。

## schemagen

> スキーマ・ジェネレータは、Javaクラス内で参照されている名前空間ごとに1つのスキーマ・ファイルを作成します。
> https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/schemagen.html

これは Java クラスから XML Schema を生成する時に使用するコマンドですね。
ただし、XML Schema では多彩な定義情報を持つことができますが、それをプレーンな Java だけでは表現できないので、アノテーションを駆使して表現してあげる必要があります。

```java:TestXml.java
package hoge;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class TestXml {

    @XmlElement(name = "id")
    private int id;

    @XmlAttribute(name = "attr")
    private String attr;
}
```

```shell-session
$ schemagen hoge/TestXml.java
```

```xml:schema1.xsd
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="testXml" type="testXml"/>

  <xs:complexType name="testXml">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
    </xs:sequence>
    <xs:attribute name="attr" type="xs:string"/>
  </xs:complexType>
</xs:schema>
```

これとは逆に XML Schema から Java クラスを生成する `xjc` コマンドというのもあります。

JSON 全盛の今の時代にこいつらを使う日が来るのだろうか・・・

## jdeps

> jdepsコマンドは、Javaクラス・ファイルの依存関係をパッケージ・レベルまたはクラス・レベルで表示します。入力クラスには、.classファイル、ディレクトリ、JARファイルへのパス名、またはすべてのクラス・ファイルが分析されるクラスの完全修飾名を指定できます。出力は、オプションによって決まります。デフォルトでは、jdepsはシステム出力に依存関係を出力します。依存関係はDOT言語で生成できます(-dotoutputオプションを参照)。
> https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/jdeps.html

これは Java8 から入ったコマンドで、クラスや jar の依存関係を出力してくれるとのことです。

使ってみます（サンプルは皆大好き seasar2 だよ）

```shell-session
$ $JAVA_HOME/bin/jdeps s2-framework-2.4.48.jar
s2-framework-2.4.48.jar -> 見つかりません
s2-framework-2.4.48.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_11.jdk/Contents/Home/jre/lib/rt.jar
   org.seasar.framework.aop (s2-framework-2.4.48.jar)
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> javassist                                          見つかりません
      -> org.aopalliance.intercept                          見つかりません
   org.seasar.framework.aop.impl (s2-framework-2.4.48.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.util                                          
      -> java.util.regex                                    
      -> org.aopalliance.intercept                          見つかりません
   org.seasar.framework.aop.interceptors (s2-framework-2.4.48.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.text                                          
      -> java.util                                          
      -> javax.servlet.http                                 見つかりません
      -> org.aopalliance.intercept                          見つかりません

... 省略 ...

   org.seasar.framework.unit (s2-framework-2.4.48.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.util                                          
      -> javax.servlet                                      見つかりません
      -> javax.servlet.http                                 見つかりません
      -> junit.framework                                    見つかりません
   org.seasar.framework.util (s2-framework-2.4.48.jar)
      -> java.beans                                         
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.math                                          
      -> java.net                                           
      -> java.security                                      
      -> java.sql                                           
      -> java.text                                          
      -> java.util                                          
      -> java.util.jar                                      
      -> java.util.zip                                      
      -> javassist                                          見つかりません
      -> javax.naming                                       
      -> javax.transaction                                  見つかりません
      -> javax.transaction.xa                               
      -> javax.xml.parsers                                  
      -> ognl                                               見つかりません
      -> org.w3c.dom                                        
      -> org.xml.sax                                        
      -> org.xml.sax.helpers                                
   org.seasar.framework.xml (s2-framework-2.4.48.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.net                                           
      -> java.util                                          
      -> javax.xml.parsers                                  
      -> org.xml.sax                                        
      -> org.xml.sax.helpers                                
```

「見つかりません」というのは、依存している jar をクラスパスに追加することで依存 jar のファイル名が表示されます。

また、dot 言語での出力もサポートされており、[Graphviz](http://www.graphviz.org/) というツールを使えばグラフ構造の画像にすることができます。

```shell-session
$ $JAVA_HOME/bin/jdeps -s -dotoutput dots/ -cp lib/* s2-framework-2.4.48.jar
```

![summary.png](https://qiita-image-store.s3.amazonaws.com/0/2046/b57eb544-ba9d-95c9-de74-46c638884e53.png)

今回は jar に対して使ってみましたが、class ファイルに対して実行すれば、このクラスを修正したらどこに影響するかを調べるのに使えるかもしれませんね。