#前置き
ちょっと前にQiitaやTwitterで盛り上がっていたネタなんですが、Activity.createIntent/Fragment.newInstanceが便利そうだと知り、ここ数ヶ月ActivityやFragmentに入れて作るようになりました。
ただ、Javaの制約上、staticメソッドの実装は強制が出来ません。
これでは運用でカバーと同じ。自分は規約厨なので、我慢なりません。
そういえば、カスタムインスペクションって作れるとか書いておきながら、やってないじゃん!ってことで、作ってみました。
インスペクションってなによ?って方はAndroidStudioのInspectionでコードチェックを楽にしてみた。を見て下さい。
#参考にしたコード
butterknife_inspections
https://github.com/erikzielke/butterknife_inspections
HungarianInspectionPlugin (@takahiromさんいつもありがとうございます!)
https://github.com/takahirom/HungarianInspectionPlugin
IntelliJ community (特にLogger initialized with foreign classインスペクション)
https://github.com/JetBrains/intellij-community
Activity.createIntentを強制するInspection
Activity createIntent Inspection
Github: https://github.com/shiraji/create-intent-inspection
Plugin: https://plugins.jetbrains.com/plugin/7915?pr=
Fragment.newInstanceを強制するInspection
Fragment newInstance Inspection
Github: https://github.com/shiraji/new-instance-inspection
Plugin: https://plugins.jetbrains.com/plugin/7916?pr=
コードの説明
基本的にどちらも同じ構成になっています。
plugin.xmlにInspectionの登録
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<localInspection enabledByDefault="true"
groupBundle="messages.InspectionsBundle"
groupPath="Android"
implementationClass="com.github.shiraji.createintentinspection.inspection.CreateIntentInspection"
language="JAVA" level="WARNING"/>
</extensions>
CreateIntentInspection.javaはJava用InspectionクラスのBaseJavaLocalInspectionToolを継承し、必要メソッドを実装しました。
public class CreateIntentInspection extends BaseJavaLocalInspectionTool {
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return "Android";
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return "Activity should implement createIntent()";
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
return new CreateIntentInspectionVisitor(holder, isOnTheFly);
}
}
PsiElementVisitor.javaを継承したVisitor内で実際にワーニングを出すのかどうか、どんなワーニングを出すのかなどを実装します。難しいのがIntelliJ独自のPsiClass関連。このクラスが表示しているクラスやメソッド、メンバーなどにアクセスできるようになるのですが、大規模+複雑でなかなかわかりづらい・・・。
必要だった処理をUtilとして作成しました。(IntelliJ内にあるのかもしれないけど、見つけられなかった・・・。)
public class InspectionPsiUtil {
public static PsiClass createPsiClass(String qualifiedName, Project project) {
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
return psiFacade.findClass(qualifiedName, searchScope);
}
public static boolean isAbstactClass(PsiClass aClass) {
if (aClass == null || aClass.getModifierList() == null) {
return false;
}
return aClass.getModifierList().hasModifierProperty("abstract");
}
public static boolean isStaticMethod(PsiMethod method) {
return hasMethodModifier(method, "static");
}
public static boolean isPublicMethod(PsiMethod method) {
return hasMethodModifier(method, "public");
}
private static boolean hasMethodModifier(PsiMethod method, String aStatic) {
return method != null &&
method.getModifierList().hasModifierProperty(aStatic);
}
}
特に大変だったのが、今見ているクラスがAcitivityやFragmentを継承しているかどうか?という判別処理。
PsiClass#isInheritor(PsiClass, boolean)を使えばわかるのですが、引数のPsiClassをActivityやFragmentのPsiClassにする必要があって、それがなかなか大変。モックでなんとかごまかそうとしたけどわかりにくい上に気持ち悪い。
そこでbutterknife_inspectionsのコードを参考にして、createPsiClass(String, Project)
を作りました。
これは今開いているproject内からqualifiedNameのクラスを探してきて!というメソッドです。
ここを突破しちゃえばあとはすんなりいけました。
#終わりに
作ってみて改めて実感。ライブラリは小さくするべき。風呂敷ひろげすぎても何もいいことない。
(この二つリリースする前に大失敗を犯した。どこかの勉強会で話すネタとしておきます。)