MirroredTypeException: Attempt to access Class object for TypeMirror
とか言われてブチギレてる人向け.
TL;DR
こんだけ.
public static List<TypeMirror> getClassesFromAnnotation(Element element, Class<? extends Annotation> annotationType, String argName) {
AnnotationMirror am = getAnnotationMirror(element, annotationType).get();
AnnotationValue av = getAnnotationValue(am, argName);
return TO_LIST_OF_TYPE.visit(av);
}
private static final AnnotationValueVisitor<ImmutableList<TypeMirror>, Void> TO_LIST_OF_TYPE = new SimpleAnnotationValueVisitor6<ImmutableList<TypeMirror>, Void>() {
@Override
public ImmutableList<TypeMirror> visitArray(List<? extends AnnotationValue> vals, Void aVoid) {
return FluentIterable.from(vals).transform(new Function<AnnotationValue, TypeMirror>() {
@Override
public TypeMirror apply(AnnotationValue input) {
return TO_TYPE.visit(input);
}
}).toList();
}
};
private static final AnnotationValueVisitor<TypeMirror, Void> TO_TYPE = new SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
@Override
public TypeMirror visitType(TypeMirror t, Void aVoid) {
return t;
}
};
長く見えるのはクラス名がいちいち長いからとジェネリクスのせいです.
IDEの補完があれば手間なく打てます.
static importされてるのはcommonのMoreElements.getAnnotationMirror()
とAnnotationMirrors.getAnnotationValue()
です.
目的
たとえばDagger使うときに書くこんなの.
@Component(
dependencies = {
CocoaComponent.class,
ChinoComponent.class,
RizeComponent.class,
ChiyaComponent.class,
SyaroComponent.class
}
)
public interface GochiusaComponent {
}
この@Component
に渡したdependencies
のクラスを取り出したい.
よくある事故
@Component
の定義は以下のようになっているはず(実際はもうちょい色々ある).
@Target(TYPE)
public @interface Component {
Class<?>[] dependencies() default {};
Class<?>[] modules() default {};
}
ここで,「じゃあふつうにgetAnnotation()
してくればいいじゃん!」となる.
element.getAnnotation(Component.class).dependencies();
すると最初に挙げたエラーを吐いて死ぬことになる.
一応,以下のようにしてMirroredTypeException
をcatchしてあげればTypeMirrorが取り出せる.
try {
element.getAnnotation(Component.class).dependencies();
} catch(MirroredTypeException e) {
return e.getTypeMirror();
}
Exception吐く前提の対応.
google/daggerの場合
では,コード生成大好き企業ことGoogleさんちのDaggerはどうしているのか.
google/autoというJavaでコード生成して遊ぶためのサムシングが入ったリポジトリが存在しており,Dagger compilerはその中のcommon(Auto common utilities)というライブラリをフル活用して実装されている.
本稿では記事執筆時点での最新のmasterのコードを追いながら,Daggerが如何にしてアノテーションからクラスをぶっこ抜いているかを紹介する.また,google/autoについてはauto-common-0.5のタグを参考にする.
前提知識
commonにはBaseAnnotationProcessor
)というクラスが存在する.これはAbstractProcessor
を実装しており,処理をProcessingStep
という単位に分割してくれる.Daggerでもこれを利用している.
ComponentProcessingStep
前述のとおり処理は複数のProcessingStepに分割されている.そのなかから今回の目的の処理をしてそうなStepであるComponentProcessingStep
を見ていく.
…が,DaggerのProcessingStepはすべてAbstractComponentProcessingStep
を継承しているので,まずはそこを見てみる.
とりあえずコンストラクタを見てみるが,フィールドの初期化しかしていない.ちょっとどこ見たらいいかわからなくなりそうだが,ここでオーバーライドされたprocess()
メソッドを見てみる.BaseAnnotationProcessor
が呼び出すための,実際の処理を記述していくメソッドである.
@Override
public final ImmutableSet<Element> process(
SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
// 省略
for (Element element : elementsByAnnotation.get(componentAnnotation)) {
TypeElement componentTypeElement = MoreElements.asType(element);
try {
if (componentElementValidator.validateComponent(componentTypeElement, messager)) {
ComponentDescriptor componentDescriptor =
componentDescriptorFactory.forComponent(componentTypeElement);
// 省略
}
} catch (TypeNotPresentException e) {
rejectedElements.add(componentTypeElement);
}
}
return rejectedElements.build();
}
注目すべきはComponentDescriptor$Factory#forComponent()
.ComponentDescriptor
…如何にもなネーミング.
ComponentDescriptor
ComponentDescriptor$Factory#forComponent()
はComponentDescriptor$Factory#create()
という非常に長いメソッドを呼び出す.このメソッドを読んでいくと以下のようなコードが見つかる.
private ComponentDescriptor create(TypeElement componentDefinitionType, Kind kind) {
ImmutableSet.Builder<ModuleDescriptor> modules = ImmutableSet.builder();
for (TypeMirror moduleIncludesType : getComponentModules(componentMirror)) {
modules.add(moduleDescriptorFactory.create(MoreTypes.asTypeElement(moduleIncludesType)));
}
}
getComponentModules()
…とてもアヤシイ.
ConfigurationAnnotations.getComponentModules()
先ほどのgetComponentModules()
はstatic importされており,呼ばれているのはConfigurationAnnotations.getComponentModules()
.
private static final String MODULES_ATTRIBUTE = "modules";
static ImmutableList<TypeMirror> getComponentModules(AnnotationMirror componentAnnotation) {
checkNotNull(componentAnnotation);
return convertClassArrayToListOfTypes(componentAnnotation, MODULES_ATTRIBUTE);
}
答えに近づいてきたような気がする.
ConfigurationAnnotations.convertClassArrayToListOtTypes()
まずConfigurationAnnotations.convertClassArrayToListOtTypes()ですが,これはVisitorにvisitしているだけ(Visitorパターンについて詳細はここでは説明しないので,知りたい方はJava言語で学ぶデザインパターン入門などを参照されたい).
Visitorの実装を読んでみる.
例外を投げるために丁寧に書かれているが,実体はここだけ.
FluentIterable.from(vals)
.transform(
new Function<AnnotationValue, TypeMirror>() {
@Override
public TypeMirror apply(AnnotationValue typeValue) {
return TO_TYPE.visit(typeValue);
}
})
.toList();
若干RxJavaっぽい(Streamっぽい)実装,Googleが出している便利ライブラリGuavaのFluentIterable
.ここでは別なVisitorにvisitすることで,AnnotationValue
からTypeMirror
を取り出してリストにまとめている.
呼びだされた別のVisitorを見てみる.
@Override
public TypeMirror visitType(TypeMirror t, Void p) {
return t;
}
…ふつうにTypeMirror
返してるだけ.これがDaggerでアノテーションからクラスを取り出す処理の全貌である(?).