かとじゅんの技術日誌

技術の話をするところ

Effective Javaの第3章 項目10のtoStringを常にオーバーライドするをirenkaでやってみる件

開発業務の品質改善の一環で、FindBugs, CheckStyleの導入、カスタマイズを行っているのですが、次はirenkaの出番です。
Effective Javaでは、端的にいうとtoStringは当該オブジェクトの情報をすべて返すべきというレコメンドがあります。詳しくは本みてください。

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

たとえば、以下のようにtoStringメソッドをオーバーライドしてcommons-langのToStringBuilderで文字列を合成して返すようなことをします。

public class Employee {
	private String name;

	private String deptName;

	public String getDeptName() {
		return deptName;
	}

	public String getName() {
		return name;
	}

	public void setDeptName(String deptName) {
		this.deptName = deptName;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return new org.apache.commons.lang.builder.ToStringBuilder(this)
				.toString();
	}
}

Hackのコードは以下のようになりました。ちょっと余計なクラスやインターフェイスが登場していますが、rewriteメソッドの方をみてください。
ここでは、toStringがオーバーライドされていない場合に、自動的に上記のようなtoStringメソッドが作られます。toStringメソッドの内部のコードが適切でない場合は書き換えてもirenkaによって上書きされません。クラスを新規で作成した場合も自動的に作られるので、もう忘れることはないですね。

public class ToStringRewriter extends AbstractHack implements ClassRewriter {

	private static final String METHOD_NAME_TO_STRING = "toString";

	/**
	 * @when event.phase = {@link HackPhase#INITIALIZE}
	 */
	@Override
	public void initialize(HackEvent event,
		DeclarationFactory declarationFactory, ElementFactory elementFactory,
		LiteralFactory literalFactory, DocFactory docFactory, Filer filer,
		Messager messager) {
		super.initialize(event, declarationFactory, elementFactory,
			literalFactory, docFactory, filer, messager);
	}

	/**
	 * ソースコードをリライトします。
	 * 
	 * @when
	 * @param clazz
	 */
	public void rewrite(CtClass<?> clazz) {
		CtMethod<?> myToStringMethod =
			clazz.getDeclaredMethod(METHOD_NAME_TO_STRING);
		if (myToStringMethod == null) {
			// toStringを新たに生成
			myToStringMethod =
				declarationFactory.newMethod(declarationFactory
					.typeOf(String.class), METHOD_NAME_TO_STRING);
			CtAnnotationInstance<?> annotation =
				elementFactory.newAnnotationInstance(declarationFactory
					.newAnnotation("Override"));
			// Overrideアノテーションを付与
			myToStringMethod.getModifiersAndAnnotations().add(annotation);
			CtModifier modifier =
				elementFactory.newModifier(ModifierKind.PUBLIC);
			// publicのアクセス修飾子を付与
			myToStringMethod.getModifiersAndAnnotations().add(modifier);

			CtClass<ToStringBuilder> toStringBuilder =
				declarationFactory.classOf(ToStringBuilder.class);
			// new ToStringBuilder(this)
			CtNewInstance<?> newInstance =
				toStringBuilder.newInvocation(clazz.getThis());
			CtMethod<?> toStringMethod =
				toStringBuilder.getMethod(METHOD_NAME_TO_STRING);
			// .toString()
			CtMethodInvocation<?> invocation = toStringMethod.newInvocation();
			// new ToStringBuilder(this).toString() の 限定式を合成
			invocation.getQualifier().substitute(newInstance);
			// return文を生成
			CtReturn<?> ret = elementFactory.newReturn(invocation);
			// メソッドの本文に追加
			myToStringMethod.getBody().getStatements().add(ret);
			// メソッドをメンバーに追加
			clazz.getMembers().add(myToStringMethod);
		}

	}
}