さて、最後にアノテーションプロセッサのコードを解説して終わろう。
処理の大部分、特にコードをWriterに出力する処理は以前に紹介したActionAnnotationProcessorと同様なため、抽象クラスAbstractBeanAnnotationProcessorクラスにプルアップした。
-
- AbstractBeanAnnotationProcessor.java
package net.kazzz.annotation; import static net.kazzz.annotation.AptConst.KEY_OVERWRITE_FLEEZE; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Map; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; @SupportedSourceVersion(SourceVersion.RELEASE_6) public abstract class AbstractBeanAnnotationProcessor extends AbstractProcessor { protected final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); protected Filer filer; protected JavaFileObject fileObject; protected Writer writer; protected String outputPackage; protected void writeComment(String className) { this.format("/*\n"); this.format(" * このファイルはアノテーションプロセッサ(" + this.getClass().getName() + ")により自動生成されました。\n"); this.format(" * file : %s.java;\n", className); this.format(" */\n"); } protected void writeGeneratedAnnotation() { this.format("@Generated(\n"); this.format(" value={\"%s\"}\n", this.getClass().getName()); this.format(" , date=\"%s\")\n", iso8601.format( Calendar.getInstance().getTimeInMillis())); } protected void writeEndBrace() { this.format("}\n"); } protected void format(String content, Object...args) { String formatted = String.format(content, args); try { this.writer.write(formatted); } catch (IOException e) { Messager messager = processingEnv.getMessager(); messager.printMessage(Kind.ERROR, e.toString()); } } protected final String capitalize(String name) { if (name.isEmpty()) { return name; } char[] chars = name.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } protected final String getPackageName(String fqcn) { int pos = fqcn.lastIndexOf('.'); if (pos > 0) { return fqcn.substring(0, pos); } return null; } protected final String getShortClassName(String className) { int i = className.lastIndexOf('.'); if (i > 0) { return className.substring(i + 1); } return className; } }
特に解説するようなコードは無いが、writeGeneratedAnnotationメソッドは以前には無かったメソッドだ。
このメソッドはJava6で実装されたjavax.annotation.Generatedと同じであり、Androidで同アノテーションが用意されていないので、別に用意している。今の所は単なるマーカでしかないがプロセッサにより生成されたコードであることを判断する場合に使う予定で入れてある。date()属性にはISO 8601に準拠したフォーマットで出力する必要がある。
お次は幾つか定数を追加した。
-
- AptConst.java
public final class AptConst {
public static final String KEY_AUTOREGISTER_OUTPUT_PACKAGE = "autoregister_output_package";
public static final String KEY_AUTOREGISTER_GENERATE_CLASS = "autoregister_generateClass";
public static final String KEY_AUTOBEAN_OUTPUT_PACKAGE = "autobean_output_package";
public static final String KEY_XMLAUTOBEAN_OUTPUT_PACKAGE = "xmlautobean_output_package";
public static final String VALUE_AUTOREGISTER_OUTPUT_PACKAGE = "net.kazzz.autoregister";
public static final String VALUE_XMLAUTOBEAN_SUPER_CLASS = "net.kazzz.dto.xml.AbstractXmlDto";
}解説は特にない。次は今回のアノテーションプロセッサの実装そのものだ。
-
- XmlAutoBeanAnnotationProcessor.java
package net.kazzz.annotation;
import static net.kazzz.annotation.AptConst.KEY_XMLAUTOBEAN_OUTPUT_PACKAGE;
import static net.kazzz.annotation.AptConst.VALUE_XMLAUTOBEAN_SUPER_CLASS;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes({"net.kazzz.annotation.XmlAutoBean"
, "net.kazzz.annotation.Element"})
@SupportedOptions({KEY_XMLAUTOBEAN_OUTPUT_PACKAGE})
public class XmlAutoBeanAnnotationProcessor extends AbstractBeanAnnotationProcessor {
protected int round;
class ElementHolder {
String name;
String fieldName;
String type;
}
/* (non-Javadoc)
* @see javax.annotation.processing.AbstractProcessor#init(javax.annotation.processing.ProcessingEnvironment)
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//オプションパラメタの取得(指定がなければデフォルト値を使う)
Map<String , String> options = this.processingEnv.getOptions();
this.outputPackage = options.containsKey(KEY_XMLAUTOBEAN_OUTPUT_PACKAGE)
? options.get(KEY_XMLAUTOBEAN_OUTPUT_PACKAGE)
: null;
}
/* (non-Javadoc)
* @see javax.annotation.processing.AbstractProcessor#process(java.util.Set, javax.annotation.processing.RoundEnvironment)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 処理対象のルート要素を全て取得
Set<? extends Element> roots = roundEnv.getRootElements();
for (Element root : roots) {
// ルート要素がクラスである場合
if (root.getKind() == ElementKind.CLASS) {
TypeElement typeElement = (TypeElement) root;
for ( AnnotationMirror m : typeElement.getAnnotationMirrors()) {
//対象の型が@SupportedAnnotationTypesで指定した型に含まれていたら対象
String annotationName = m.getAnnotationType().asElement().toString();
if ( this.getSupportedAnnotationTypes().contains(annotationName)) {
this.outputBean(typeElement, m);
}
}
}
}
return true;
}
@SuppressWarnings("unchecked")
protected void outputBean(TypeElement typeElement, AnnotationMirror annotation) {
Messager messager = processingEnv.getMessager();
try {
//getter
boolean getter = true;
// setter()
boolean setter = true;
// beandClass()
String beanClass = typeElement.getSimpleName() + "Bean";
// superClass()属性は今回は使わず固定のスーパクラスを使っている
String superClass = VALUE_XMLAUTOBEAN_SUPER_CLASS;
// elementts
List<ElementHolder> elements = new ArrayList<ElementHolder>();
//アノテーションの属性値を取得する
Iterator<?> i = annotation.getElementValues().entrySet().iterator();
while ( i.hasNext() ) {
Map.Entry<ExecutableElement, AnnotationValue> m =
(Map.Entry<ExecutableElement, AnnotationValue>)i.next();
//getter
if ( m.getKey().getSimpleName().contentEquals("getter") ) {
getter = (Boolean)m.getValue().getValue();
continue;
}
//setter
if ( m.getKey().getSimpleName().contentEquals("setter") ) {
setter = (Boolean)m.getValue().getValue();
continue;
}
//beandClass
if ( m.getKey().getSimpleName().contentEquals("beandClass") ) {
beanClass = (String)m.getValue().getValue();
// fqnの場合、そのクラスのパッケージを使用する
String pcackageName = this.getPackageName(beanClass);
if ( pcackageName != null && !pcackageName.isEmpty() ) {
this.outputPackage = pcackageName;
}
beanClass = this.getShortClassName(beanClass);
continue;
}
//elements
if ( m.getKey().getSimpleName().contentEquals("elements") ) {
//配列の要素は何故かListで取れる (配列ではない)
List<AnnotationValue> l = (List<AnnotationValue>)m.getValue().getValue();
for ( AnnotationValue av : l ) {
AnnotationMirror mirror = (AnnotationMirror)av.getValue();
String name = null;
String fieldName = null;
String type = "String";
Iterator<?> i2 = mirror.getElementValues().entrySet().iterator();
while ( i2.hasNext() ) {
Map.Entry<ExecutableElement, AnnotationValue> m2 =
(Map.Entry<ExecutableElement, AnnotationValue>)i2.next();
//name()
if ( m2.getKey().getSimpleName().contentEquals("name") ) {
name = (String)m2.getValue().getValue();
fieldName = name;
} else
//fieldName()
if ( m2.getKey().getSimpleName().contentEquals("fieldName") ) {
fieldName = (String)m2.getValue().getValue();
} else
//type() 今回の実装ではString型へのマップしか行わない
/*
if ( m2.getKey().getSimpleName().contentEquals("type") ) {
type = (String)m2.getValue().getValue().toString();
}
*/
}
if ( name != null && type != null ) {
ElementHolder eh = new ElementHolder();
eh.name = name;
eh.type = type;
eh.fieldName = fieldName;
elements.add(eh);
}
}
continue;
}
}
//パッケージ名がまだ指定無しだった場合、元クラスのパッケージを使用
if ( this.outputPackage == null ) {
this.outputPackage =
this.getPackageName(typeElement.toString());
}
String fqn = this.outputPackage + "." + beanClass;
this.filer = processingEnv.getFiler();
this.fileObject = this.filer.createSourceFile(fqn);
this.writer = this.fileObject.openWriter();
try {
//コメント出力
this.writeComment(fqn);
//クラス定義部を出力
this.writeClassDefinition(superClass, beanClass);
//フィールド部を出力
this.writeFieldDefinition(elements);
//メソッド定義部を出力
this.writeMethodDefinition(elements, getter, setter, superClass);
this.writeEndBrace();
this.writer.flush();
} catch (Exception e ) {
messager.printMessage(Kind.ERROR, e.getMessage());
e.printStackTrace(new PrintWriter(this.writer));
} finally {
this.writer.close();
messager.printMessage(Kind.NOTE,
"AutoBeanAnnotationProcessor Creating .. " + fileObject.toUri());
}
} catch (Exception e ) {
messager.printMessage(Kind.ERROR, e.getMessage());
e.printStackTrace(new PrintWriter(this.writer));
}
}
protected void writeClassDefinition(String superClass, String className) {
this.format("package %s;\n" , this.outputPackage);
this.format("\n");
this.format("import java.io.IOException;\n");
this.format("\n");
this.format("import org.xmlpull.v1.XmlPullParser;\n");
this.format("import org.xmlpull.v1.XmlPullParserException;\n");
this.format("\n");
this.format("import net.kazzz.annotation.Generated;\n");
this.format("\n");
if ( !this.outputPackage.equals(this.getPackageName(superClass)) ) {
this.format("import %s;\n", superClass);
this.format("\n");
}
//Genareated アノテーション
this.writeGeneratedAnnotation();
//クラス定義
this.format("public class %s extends %s {\n", className, this.getShortClassName(superClass));
}
private void writeFieldDefinition(List<ElementHolder> elements) {
this.format("\n");
for ( ElementHolder f : elements ) {
this.format(" protected %s %s;\n", f.type, f.fieldName);
}
this.format("\n");
}
protected void writeMethodDefinition(List<ElementHolder> elements, boolean getter, boolean setter, String superclass) {
Iterator<ElementHolder> i = elements.iterator();
//parseメソッドのオーバライド
this.format(" /* (non-Javadoc)\n");
this.format(" * @see parse(" + superclass + ")\n");
this.format(" */\n");
this.format(" @Override\n");
this.format(" public void parse(XmlPullParser parser) throws XmlPullParserException, IOException {\n");
this.format("\n");
this.format(" int type;\n");
this.format(" String tagName = \"\";\n");
this.format("\n");
this.format(" while((type = parser.next()) != XmlPullParser.END_DOCUMENT) {\n");
this.format(" switch(type) {\n");
this.format(" case XmlPullParser.START_DOCUMENT:\n");
this.format(" break;\n");
this.format(" case XmlPullParser.START_TAG:\n");
this.format(" tagName = parser.getName();\n");
this.format(" break;\n");
this.format(" case XmlPullParser.TEXT:\n");
while ( i.hasNext() ) {
ElementHolder eh = i.next();
this.format(" if( tagName.equalsIgnoreCase(\"%s\")) {\n", eh.name);
this.format(" this.set%s(parser.getText());\n", this.capitalize(eh.fieldName));
if ( i.hasNext() ) {
this.format(" } else \n");
} else {
this.format(" }\n");
this.format(" break;\n");
}
}
this.format(" case XmlPullParser.END_TAG:\n");
this.format(" tagName = \"\";\n");
this.format(" break;\n");
this.format(" }\n");
this.format(" }\n");
this.format(" }\n");
//アクセサの出力
for ( ElementHolder f : elements ) {
String typeName = f.type;
String fieldName = f.fieldName;
if ( getter ) {
// ゲッターメソッドを出力 (booleanの場合はis〜)
this.format("\n");
if ( typeName.equalsIgnoreCase("boolean") || typeName.endsWith("Boolean") ) {
this.format(" public s% is%s() {\n", typeName, capitalize(fieldName));
} else {
this.format(" public %s get%s() {\n", typeName, capitalize(fieldName));
}
this.format(" return this.%s;\n", fieldName);
this.format(" }\n");
}
if ( setter ) {
this.format("\n");
this.format(" public void set%s(%s %s) {\n", capitalize(fieldName), typeName, fieldName );
this.format(" this.%s = %s;\n", fieldName, fieldName);
this.format(" }\n");
}
}
}
}Elementに記述されたアノテーションの属性値が任意の属性名一発で取得できず、Map.Entryの列挙を回す必要があるのと、コードのインデントが面倒な位で何も難しいことは無い。Elementの種別を判別してコードを書くのが煩わしい場合、Java6で追加された要素の種類に合わせた処理をVisitorパターンで書くためのジェネリクスTypeVisitor
現コードではシンプルにXMLの要素をマップする際にJavaフィールドの型をString型に限定しているが、この部分に既存のライブラリィや独自の型コンバータを適用することでXML要素の値を別な型にマッピングすることが可能になるだろう。(実際にそのように使う予定だ)
この後の予定としては、上に書いたようにXML要素の文字列からJavaの型へのマッピング等、DAOへの応用を当て込みつつ、AndroidのActivityに対してDTOを自動的に生成する用途に使うかなと考えている。
※Visitorパターンは書いていてOOPぽいというか、覚えると面白いのでつい使いたくなるが、多用するとかえってコードの可読性が落ちる。程ほどに、本当に必要な場合のみ使うのが良いと思う。