CLOVER🍀

That was when it all began.

Java 8から付属する、依存関係解析ツールjdepsを試してみる

Java 8から、jdepsというクラスの依存関係を解析するツールが増えたそうです。

Java class dependency analyzer.
http://download.java.net/jdk8/docs/technotes/tools/unix/jdeps.html
http://download.java.net/jdk8/docs/technotes/tools/windows/jdeps.html

ちょっと興味があったので、試してみることにしました。

使ってみる

まずは、スケープゴート的なソースを用意してみます。
PrintLoop.java

import java.util.stream.IntStream;

public class PrintLoop {
    public static void main(String... args) {
        IntStream
            .rangeClosed(1, 10)
            .forEach(System.out::println);
    }
}

まあ、初Java 8コード!

とりあえず、コンパイルします。

$ javac PrintLoop.java

この.classファイルを、jdepsコマンドに食わせてみます。

$ jdeps PrintLoop.class 
PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   <unnamed> (PrintLoop.class)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.invoke                                   
      -> java.util.function                                 
      -> java.util.stream

と、依存関係が表示されました。importで書いたもの以上に、使用されているものが表示されている感じですね。

ヘルプを見てみる

先に見ようよ、という気がしますが。

$ jdeps -help
使用方法: jdeps <options> <classes...>
<classes>には、.classファイルのパス名、ディレクトリ、JARファイルまたは完全修飾
クラス名を指定できます。使用できるオプションは次のとおりです:
  -dotoutput <dir>                   DOTファイル出力の宛先ディレクトリ
  -s           -summary              依存性の要約のみ出力します
  -v           -verbose              クラス・レベルの依存性をすべて出力します
  -verbose:package                   パッケージ・レベルの依存性を出力します
                                     (同じアーカイブ内の依存性を除く)
  -verbose:class                     クラス・レベルの依存性を出力します
                                     (同じアーカイブ内の依存性を除く)
  -cp <path>   -classpath <path>     クラス・ファイルを検索する場所を指定します
  -p <pkgname> -package <pkgname>    指定のパッケージ内の依存性を検出します
                                     (複数回指定可能)
  -e <regex>   -regex <regex>        パターンに一致するパッケージ内の依存性を検出します
                                     (-pと-eは排他的)
  -include <regex>                   パターンに一致するクラスに分析を制限します
                                     このオプションを指定すると、分析対象クラスの
                                     リストがフィルタ処理されます。パターンを依存性に
                                     適用する-pおよび-eと一緒に使用できます
  -P           -profile              プロファイル、またはパッケージを含むファイルを表示します
  -apionly                           分析をAPI、つまり、パブリック・クラスの
                                     パブリック・メンバーおよび保護されたメンバーの
                                     署名における依存性(フィールド・タイプ、メソッド・
                                     パラメータ・タイプ、戻されたタイプ、チェックされた
                                     例外タイプなど)に制限します
  -R           -recursive            すべての依存性を反復的に走査します
  -jdkinternals                      Finds class-level dependences on JDK internal APIs.
                                     By default, it analyzes all classes on -classpath
                                     and input files unless -include option is specified.
                                     This option cannot be used with -p, -e and -s options.
                                     WARNING: JDK internal APIs may not be accessible in
                                     the next release.
  -version                           バージョン情報

もう少し踏み込んでみましょう。

依存するクラスまで、詳細に表示してみる

オプション、「-verbose」または「-verbose:class」を付けて実行します。

「-verbose」

$ jdeps -verbose PrintLoop.class 
PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   PrintLoop                                          -> java.io.PrintStream                                
   PrintLoop                                          -> java.lang.Class                                    
   PrintLoop                                          -> java.lang.Object                                   
   PrintLoop                                          -> java.lang.String                                   
   PrintLoop                                          -> java.lang.System                                   
   PrintLoop                                          -> java.lang.invoke.CallSite                          
   PrintLoop                                          -> java.lang.invoke.LambdaMetafactory                 
   PrintLoop                                          -> java.lang.invoke.MethodHandle                      
   PrintLoop                                          -> java.lang.invoke.MethodHandles                     
   PrintLoop                                          -> java.lang.invoke.MethodHandles$Lookup              
   PrintLoop                                          -> java.lang.invoke.MethodType                        
   PrintLoop                                          -> java.util.function.IntConsumer                     
   PrintLoop                                          -> java.util.stream.IntStream

「-verbose」は、「-v」でもOKだそうな。

$ jdeps -v PrintLoop.class

「-verbose:class」

$ jdeps -verbose:class PrintLoop.class 
PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   PrintLoop (PrintLoop.class)
      -> java.io.PrintStream                                
      -> java.lang.Class                                    
      -> java.lang.Object                                   
      -> java.lang.String                                   
      -> java.lang.System                                   
      -> java.lang.invoke.CallSite                          
      -> java.lang.invoke.LambdaMetafactory                 
      -> java.lang.invoke.MethodHandle                      
      -> java.lang.invoke.MethodHandles                     
      -> java.lang.invoke.MethodHandles$Lookup              
      -> java.lang.invoke.MethodType                        
      -> java.util.function.IntConsumer                     
      -> java.util.stream.IntStream

なお、「-verbose:package」というオプションもあるのですが、これがデフォルトみたいですね。

$ jdeps -verbose:package PrintLoop.class 
PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   <unnamed> (PrintLoop.class)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.invoke                                   
      -> java.util.function                                 
      -> java.util.stream

Profileを表示する

「-profile」で。

$ jdeps -profile PrintLoop.class 
PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar (compact1)
   <unnamed> (PrintLoop.class)
      -> java.io                                            compact1
      -> java.lang                                          compact1
      -> java.lang.invoke                                   compact1
      -> java.util.function                                 compact1
      -> java.util.stream                                   compact1

JARファイルに対して、実行してみる

jdepsコマンドは、JARファイルに対しても実行できるようです。

ここはひとつ、Commons Langにスケープゴートになっていただきましょう。

$ jdeps commons-lang3-3.3.1/commons-lang3-3.3.1.jar 
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3 (commons-lang3-3.3.1.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.reflect                                  
      -> java.nio.charset                                   
      -> java.text                                          
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.regex                                    
   org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.reflect                                  
      -> java.util                                          
   org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar)
      -> java.lang                                          
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.concurrent.atomic                        
   org.apache.commons.lang3.event (commons-lang3-3.3.1.jar)

〜以下、省略〜

依存しているパッケージを指定して絞り込む

「-p」または「-package」オプションを指定することで、指定のパッケージに依存したパッケージなどを抽出することができます。

$ jdeps -package java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar 
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3 (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.event (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.exception (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.reflect (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.text (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.time (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.tuple (commons-lang3-3.3.1.jar)
      -> java.util

「-package」に続けて、複数回パッケージを指定可能とのこと。

これは、以下でもOKです。

$ jdeps -p java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar

「-v」と合わせると、それなりに大変なことになります。

$ jdeps -p java.util -v commons-lang3-3.3.1/commons-lang3-3.3.1.jar
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3.AnnotationUtils           -> java.util.Arrays                                   
   org.apache.commons.lang3.AnnotationUtils$1         -> java.util.Iterator                                 
   org.apache.commons.lang3.AnnotationUtils$1         -> java.util.List                                     
   org.apache.commons.lang3.ArrayUtils                -> java.util.Arrays                                   
   org.apache.commons.lang3.ArrayUtils                -> java.util.BitSet                                   
   org.apache.commons.lang3.ArrayUtils                -> java.util.HashMap                                  
   org.apache.commons.lang3.ArrayUtils                -> java.util.Iterator                                 
   org.apache.commons.lang3.ArrayUtils                -> java.util.Map                                      
   org.apache.commons.lang3.ArrayUtils                -> java.util.Map$Entry                                
   org.apache.commons.lang3.ArrayUtils                -> java.util.Set                                      
   org.apache.commons.lang3.CharRange                 -> java.util.Iterator                                 
   org.apache.commons.lang3.CharRange$CharacterIterator -> java.util.Iterator                                 
   org.apache.commons.lang3.CharRange$CharacterIterator -> java.util.NoSuchElementException                   
   org.apache.commons.lang3.CharSet                   -> java.util.Collections                              
   org.apache.commons.lang3.CharSet                   -> java.util.HashMap                                  
   org.apache.commons.lang3.CharSet                   -> java.util.HashSet                                  
   org.apache.commons.lang3.CharSet                   -> java.util.Iterator                                 
   org.apache.commons.lang3.CharSet                   -> java.util.Map                                      
   org.apache.commons.lang3.CharSet                   -> java.util.Set                                      
   org.apache.commons.lang3.ClassUtils                -> java.util.ArrayList 

〜以下、省略〜

指定のパッケージ内の依存関係を解析する

「-include」オプションを使用するようです。引数には、正規表現が使用できます。

$ jdeps -include 'org\.apache\.commons\.lang3\.text.*' commons-lang3-3.3.1/commons-lang3-3.3.1.jar 
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> 見つかりません
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3.text (commons-lang3-3.3.1.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.text                                          
      -> java.util                                          
      -> org.apache.commons.lang3                           見つかりません
      -> org.apache.commons.lang3.builder                   見つかりません
   org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.util                                          
      -> org.apache.commons.lang3                           見つかりません

まあ、通常わざわざ「.」をエスケープしなくてもよいと思いますが。

ところで、指定には癖があるようで、以下は何も引っかかりません。

$ jdeps -include 'org\.apache\.commons\.lang3' commons-lang3-3.3.1/commons-lang3-3.3.1.jar

何かしら、全体でマッチするように指定しなさいということなのでしょうね。

$ jdeps -include 'org\.apache\.commons\.lang3.*' commons-lang3-3.3.1/commons-lang3-3.3.1.jar

依存先のパッケージを指定する

「-e」または「-regex」オプションを指定することで、依存先のパッケージを正規表現で指定することができます。

$ jdeps -e java.util.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar 
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3 (commons-lang3-3.3.1.jar)
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.regex                                    
   org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar)
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.concurrent.atomic                        
   org.apache.commons.lang3.event (commons-lang3-3.3.1.jar)
      -> java.util                                          
      -> java.util.concurrent                               
   org.apache.commons.lang3.exception (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.reflect (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.text (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.time (commons-lang3-3.3.1.jar)
      -> java.util                                          
      -> java.util.concurrent                               
      -> java.util.regex                                    
   org.apache.commons.lang3.tuple (commons-lang3-3.3.1.jar)
      -> java.util

以下でもOK。

$ jdeps -regex java.util.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar 

これも、こういう指定だと何も起こりません…。

$ jdeps -e java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar

「-e」と「-regex」は排他的で、両方指定すると、後で指定した方が有効になるみたいです。

先ほどの「-include」オプションと、併用することもできます。

$ jdeps -regex java.util.* -include org.apache.commons.lang3.text.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar 
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3.text (commons-lang3-3.3.1.jar)
      -> java.util                                          
   org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar)
      -> java.util

クラスパスを設定する

これは、普通に「-cp」または「-classpath」オプションで指定可能です。

$ jdeps -cp commons-lang3-3.3.1/commons-lang3-3.3.1.jar org.apache.commons.lang3.StringUtils
commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar
   org.apache.commons.lang3 (commons-lang3-3.3.1.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.nio.charset                                   
      -> java.text                                          
      -> java.util                                          
      -> java.util.regex

なかなか面白いツールが導入されましたが、関連として時間があれば以下も見てみようかと思います。

こちらは、Java 8には関係ないですけどね…。

forbidden-apis
https://code.google.com/p/forbidden-apis/

http://mail-archives.apache.org/mod_mbox/lucene-dev/201312.mbox/%3C02c401cefb29$b6769a20$2363ce60$@thetaphi.de%3E