- PR -

依存クラスの抽出について

投稿者投稿内容
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2007-04-20 13:42
Javassist等でバイトコードを読み込んで、
定数プールのクラスと文字列を取得すれば、
依存関係は解決できるかなと思います。

定数プールのクラスは当然必要なクラス名が格納されていますが、
文字列を取得するのはClass#forName(String)対策です。

何らかのメソッドによって編集されて作られる文字列に対しては無力ですが。
Class#forName(String)対策はしないのであれば、比較的簡単でしょう。
だっちょ
大ベテラン
会議室デビュー日: 2006/12/05
投稿数: 115
投稿日時: 2007-04-20 16:33
 面白そうなので、思わす作ってしまいました。
Class.forNameに対応する必要はないと思いますので、
javapの出力のmethodと変数だけ解析してみました。
(動作時にJDKのパスは通っている必要があります)
単純なものでとりあえず動いているように見えます。

public class DependClass {
private Process m_process;
private InputStream m_ins;
private BufferedReader m_reader;
private List<String> m_jarClasses;
private Set<String> m_useClasses;
private Pattern m_codePattern;
private Pattern m_localPattern;

private DependClass(String jarfile, List<String> classnames) throws Exception
{
m_codePattern = Pattern.compile("(\\s+\\d*\\x3A\\s)([^\\x3B]*\\x3B)(\\s+//)(\\w+)(\\s)(\\S+)(\\s*)");
m_localPattern = Pattern.compile("(\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\w+\\s+)([A-Z]\\S*)\\s*");

m_jarClasses = classnames;
List<String> params = new Vector<String>();
params.add("javap");
params.add("-c");
params.add("-l");
params.add("-private");
params.add("-classpath");
params.add(jarfile);
for (int i=0;i<classnames.size();i++) {
params.add(classnames.get(i));
}
m_useClasses = new HashSet<String>();
m_process = new ProcessBuilder(params).start();
m_ins = m_process.getInputStream();
m_reader = new BufferedReader(new InputStreamReader(m_ins));
}

public static void main(String[] args)
{
if (0 >= args.length) {
System.out.println("java DependClass [jarfile]");
return;
}
String jarfile = args[0];

try {
List<String> classnames = getClassNames(jarfile);
DependClass obj = new DependClass(jarfile, classnames);
obj.run();
Set<String> names = obj.getClasses();
// 使用packageの抽出
Set<String> packagenames = new HashSet<String>();
for (Iterator<String> iter=names.iterator(); iter.hasNext(); ) {
String name = iter.next();
int n = name.lastIndexOf('.');
if (n!=-1) {
packagenames.add(name.substring(0, n));
}
}
for (Iterator<String> iter=packagenames.iterator(); iter.hasNext(); ) {
System.out.println("use package : " + iter.next());
}
}
catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}
/**
* jarファイルのクラス名一覧取得.
*/
public static List<String> getClassNames(String jarfile) throws IOException
{
List<String> retobj = new Vector<String>();
JarFile jf = new JarFile(jarfile, false);
Enumeration<JarEntry> en = jf.entries();
while (en.hasMoreElements()) {
JarEntry entry = en.nextElement();
String name = entry.getName();
if (name.endsWith(".class")) {
retobj.add(name.substring(0, name.length()-6).replace('/', '.'));
}
}
return(retobj);
}

public void run()
{
try {
while (true) {
if (m_reader.ready()) {
String line = m_reader.readLine();
parseLine(line);
}
else {
try {
m_process.exitValue();
return;
}
catch (IllegalThreadStateException te) {
// まだおわってなければ続ける
}
}
}
}
catch (Exception e) {
e.printStackTrace();
return;
}
}
/**
* javapの出力データの1行を解析.
*/
private final void parseLine(String line)
{
if (line.length()==0) return;
if (line.charAt(0)==' ') {
// 4: invokespecial #6; //Method java/util/Vector."<init>")V
Matcher matcher = m_codePattern.matcher(line);
if (matcher.matches()) {
String step = matcher.group(2);
String func = matcher.group(4);
String expression = matcher.group(6);
//System.out.println("" + func + "<" + expression + "> " + step);
if (func.equals("Method") || func.equals("InterfaceMethod")) {
// 42: invokeinterface #8, 2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
int n = expression.indexOf('.');
if (n==-1) return;

String objclass = expression.substring(0, n);
addClass(objclass);

int m = expression.indexOf(':', n);
if (expression.charAt(m+1)=='(') {
int l = expression.indexOf(')', m+2);
// パラメタクラスの解析は必要ないだろう
// parseParams(expression, m+2, l);
}

}
}
// Codeにmatchしない場合
// LocalVariableTableだけは解析したい気がする
else {
Matcher m2 = m_localPattern.matcher(line);
if (m2.matches()) {
String sig = m2.group(2);
if (sig.charAt(0)=='L') {
int n = sig.indexOf(';');
String name = sig.substring(1, n);
addClass(name);
}
}
}
}
else {
// private javax.persistence.EntityManager m_em;のようなメンバ定義
if ((line.indexOf('(')==-1) && (line.charAt(line.length()-1)==';')) {
int n = line.lastIndexOf(' ');
if (n==-1) return;
int m = line.lastIndexOf(' ', n-1);
if (m==-1) return;
String name = line.substring(m+1, n);
addClass(name);
}
// private DependClass(java.lang.String, java.util.List) throws java.lang.Exception;
// のような関数定義は無視


}
}

/**
* 依存するクラス名を追加.
* javaで始まる名前やjarにあるクラスは無視する
*/
private final void addClass(String classname)
{
if (classname.startsWith("java")) return;
String name = classname.replace('/', '.');
if (m_jarClasses.contains(name)) return;
m_useClasses.add(name);
}
/**
* 依存クラス集合の取得.
*/
public Set<String> getClasses()
{
return(m_useClasses);
}
}

山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2007-04-20 16:46
-verbose:class オプションでロードするクラスをダンプするのも手軽でいいかもしれませんね。

アクセスログを確認するのと同じですが、ファクトリメソッドとかでリフレクションとか使っていても確実に使われているクラスの一覧を取得できます。

[ メッセージ編集済み 編集者: インギ 編集日時 2007-04-20 17:14 ]
コウ
会議室デビュー日: 2007/03/05
投稿数: 8
投稿日時: 2007-04-20 18:30
皆様ありがとうございます。

何か結構いろんな方法がありますね。
今まで、色々やってみたのですがなかなかうまくいかなかったのですが、
皆様の意見を参考に色々試したいと思います。

うまくいったらまた報告させて頂きます。

スキルアップ/キャリアアップ(JOB@IT)