JDTを使ってメソッドの呼び出し元を検索する方法

備忘録として。 eclipseとjdtを使ったメソッド呼び出し元検索のサンプルコード。

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.internal.corext.callhierarchy.CallHierarchy;
import org.eclipse.jdt.internal.corext.callhierarchy.MethodWrapper;

public class Application implements IApplication {

    public Object start(IApplicationContext context) throws Exception {
        String projectName = "sample";
        String fqcn = "pkg.ClassA";
        String methodName = "methodA";

        IType type = findType(projectName, fqcn);
        IMember[] members = findMethod(type, methodName);

        for (IMember m : members) {
            CallTracer tracer = new CallTracer(m);
            traceCaller(tracer);

            tracer.trace();
            // tracer.traceSimple();
        }

        return IApplication.EXIT_OK;
    }

    private void traceCaller(CallTracer tracer) {
        IMember member = tracer.getTarget();

        CallerUtil util = new CallerUtil();
        Set<IMethod> methods = util.getCallers(member);

        for (Iterator<IMethod> ite = methods.iterator(); ite.hasNext();) {
            IMethod next = ite.next();

            CallTracer c = new CallTracer(next);
            tracer.addCaller(c);

            traceCaller(c);
        }
    }

    private IMethod[] findMethod(IType type, String methodName) throws JavaModelException {
        IMethod[] methods = type.getMethods();
        List<IMethod> result = new ArrayList<IMethod>();

        for (IMethod method : methods) {
            if (method.getElementName().equals(methodName)) {
                result.add(method);
            }
        }

        return result.toArray(new IMethod[result.size()]);
    }

    private IType findType(String projectName, String fullName) throws JavaModelException {
        IJavaProject jp = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot().getProject(projectName));

        return jp.findType(fullName);
    }

    @Override
    public void stop() {
        // TODO Auto-generated method stub

    }

    class CallerUtil {

        public HashSet<IMethod> getCallers(IMember member) {
            CallHierarchy callHierarchy = CallHierarchy.getDefault();

            IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
            callHierarchy.setSearchScope(scope);

            IMember[] members = { member };
            MethodWrapper[] methodWrappers = callHierarchy.getCallerRoots(members);
            HashSet<IMethod> result = new HashSet<IMethod>();

            for (MethodWrapper mw : methodWrappers) {
                MethodWrapper[] calls = mw.getCalls(new NullProgressMonitor());
                result.addAll(getMethods(calls));
            }

            return result;
        }

        private HashSet<IMethod> getMethods(MethodWrapper[] methodWrappers) {
            HashSet<IMethod> result = new HashSet<IMethod>();

            for (MethodWrapper mw : methodWrappers) {
                IMethod method = toMethod(mw);

                if (method != null) {
                    result.add(method);
                }
            }

            return result;
        }

        private IMethod toMethod(MethodWrapper methodWrappers) {
            IMember member = methodWrappers.getMember();

            if (member.getElementType() == IJavaElement.METHOD) {
                return (IMethod) methodWrappers.getMember();
            } else {
                System.out.println("IGNORE:" + member.toString());
            }

            return null;
        }
    }

    class CallTracer {
        
        private IMember target;
        private List<CallTracer> callers;

        public CallTracer(IMember member) {
            target = member;
            callers = new ArrayList<CallTracer>();
        }

        public void addCaller(CallTracer c) {
            callers.add(c);
        }

        public IMember getTarget() {
            return target;
        }

        
        public void trace() {
            trace(0);
        }

        private void trace(int idt) {
            printTrace(idt);

            for (CallTracer tracer : callers) {
                idt++;
                tracer.trace(idt);
            }
        }

        public void traceSimple() {
            traceSimple(true, 0);
        }

        public void traceSimple(boolean pltFlg, int idt) {
            if (pltFlg) {
                printTrace(idt);
                idt++;
            }

            if (callers.isEmpty()) {
                printTrace(idt);
            } else {
                for (CallTracer tracer : callers) {
                    tracer.traceSimple(false, idt);
                }
            }
        }

        private void printTrace(int idt) {
            System.out.println(getIdt(idt) + " class:" + target.getParent().getElementName());
            System.out.println(getIdt(idt) + "method:" + target.getElementName());
        }

        private String getIdt(int idt) {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < idt; i++) {
                sb.append("\t");
            }

            return sb.toString();
        }
    }
}

Ruby on Railsの勉強 -RailsTutorial 第2章-

第2章Toyアプリケーション

第2章ではscaffoldというジェネレータの説明と、それを使用したRailsのWebプログラミングの概要が学べる。

Railsアプリケーションの作成

1章と同じようにRailsアプリケーションの作成から。

rails new アプリケーション名(任意)

Gemfile

チュートリアルの通り適当にGemfileを修正してgemのインストールを実施。

bundle install --without production

Gemfileには各環境(開発環境、本番環境等)毎にインストールするライブラリを分けることができ、--withoutオプションで指定したグループのライブラリをスキップすることが可能。

ルーティング
Railsアプリケーションが受け付けるリクエストを定義。また、リクエストに対応するコントローラのアクションに紐づけるための定義ファイル。

“コントローラ名”#“アクション名"のルールで任意のコントローラ、アクションへの紐付けが可能になる。

# config/routes.rb
Rails.application.routes.draw do
  # コンテキストルートへのリクエストは ApplicationController.main が処理を受け付ける
  root 'application#main'

  # '/about'へのリクエストは ApplicationController.about が処理を受け付ける
  get '/about',     to: 'application#about'
end

コントローラ
その他の通りコントローラ。上記ルーティングを例にすると次のようになる。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  def main
  end

  def about
  end
end

Git / Heroku
その後はGit(Bitbucket)へコミットしてから、Herokuへデプロイして準備完了。

# Gitにコミット
git commit -am "add 'hello, world'"

# Herokuに新規アプリケーションを作成してアプリをデプロイ
heroku create
git push heroku master

rails generate
rails generateを使用するとモデルやコントローラ等、様々なテンプレートを自動生成することができる。チュートリアルではrails generate scaffoldを使用した。

scaffoldを使用すると、登録、編集、削除、一覧表示を行うために必要なリソース(コントローラ、モデル、ビュー、マイグレーション等の全部入り)が自動生成される。

# Userテーブルに対して登録、編集、削除、一覧ができるアプリケーションテンプレートを作成
rails generate scaffold User name:String age:integer

rails db:migrate
具体的な仕組みはまだ理解できていないけども、マイグレーションファイルに基づいてDBの構築まで実施してくれる。

# rails generate scaffoldでテンプレート作成実施
rails generate scaffold User name:String age:integer

# データベースのマイグレーションを行う
rails db:migrate

上記の例では'name'と'age'のカラムを持った'User'テーブルの自動生成が行われる。


適当にgenerateするだけですぐに動くものが作れる気軽さにびっくり。DBへのマイグレーションの非常に簡単だった。せこせこDDLとか作ったりする必要ないのかな?


Railsの開発環境

基本構成

基本構成は次の記事通り。

AtomでRailsを爆速開発する環境を作ってみた - Qiita

特に大きな問題は生じなかったが、「terminal-plus」が上手く動かない。 ウィンドウに何も表示されないし、何も入力できない。

terminal-plusが正常に動作しない時

atom 1.12.2 · Issue #386 · jeremyramin/terminal-plus · GitHub

apm経由でインストールすれば良いとのことなので、「terminal-plus」をアンインストールした後に

apm install LarsKumbier/terminal-plus

を実行して、Atomを再起動すれば無事解消。

Atom stylesheet

スタイルシートでテーマのカスタマイズが出来るとのこと。肝心のスタイルシートがどこにあるかわからなかったのでメモ。

「メニュー > Atom > Stylesheet..」

これでstyles.lessが表示されるので好きなようにカスタマイズすればOK。

Ruby on Railsの勉強 -RailsTutorial 第1章-

タイトル通りの話、「Ruby on Railsの勉強」の備忘録として。

私はIT関連の仕事(俗に言うSIerというやつ)をしていますが、Javaをメインに比較的堅苦しい案件ばかりで他言語に触れる機会がなかった。自身の興味の範囲を広げる意味もふまえ、今更ながらRailsの勉強をしようと思う。

まずはチュートリアルから

railstutorial.jp

第1章ゼロからデプロイまで

とりあえず何も考えず、チュートリアルの通りRailsのインストールを実施。

# railsのインストール
gem install rails

適当にワークスペースを作成して、rails new hello_appを実行。 rails newを実行することによってアプリケーションとして必要なファイルセットが自動生成される模様。

次にGemfileをチュートリアル通りに修正する。Gemfileはアプリケーションで使用するライブラリの依存関係を管理するためのファイルらしい。 Gemfileを修正したらbundle installを実行することでGemfileに記載した各種ライブラリのインストールが行われる。

ここまで準備が完了したらrails serverでアプリケーションが起動する。IPとポートの指定が可能だが取りあえずデフォルトのまま実行した。Safari等のブラウザでhttp://localhost:3000/にアクセスすることでデフォルトのRailsページとやらが表示された。

その後、簡単なMVCの説明があり、コントローラの簡単な解説。

# application_controller.rb
def hello
  render html: "hello, world!"
end

上記は"hello, world!“っていうhtmlをレンダリングするってことかな? 次にルーティングの設定。ルーティングファイルに一筆追加した。

# config/routes.rb
root 'application#hello'

これでルートパスへのアクセスを行うことでコントローラに定義したhello関数が呼び出されるのだろう。

その後の流れとして、1.4章ではGItの解説やBitbucketを利用したソースコードの管理、1.5章では仮想本番環境としてHerokuの利用についての解説が入る。


チュートリアルの第1章として"Hello World!“までは想定通りしていたが、GitにPaas(Heroku)と本番開発想定で解説されているおり、非常に良い教材と感じた。1章を写生しながら読み終えるのに1時間強かかったがこのまま継続していきたい。