Pull to refresh

Custom Annotation Preprocessor — создание на базе Android-приложения и конфигурация в IntelliJ IDEA

Reading time4 min
Views16K
Всем привет!

Недавно передо мной встала задача написания своих кастомных аннотации и их обработки во время компиляции. Первый вопрос, который я себе задала: с чего начать? После анализа я решила поделиться с вами ответом на этот вопрос.
Думаю, рассказывать, что такое аннотации в java и с чем их едят, не имеет смысла, так как каждому юному программисту это знакомо ( а кому не знакомо, может прочесть самостоятельно). К тому же на хабре есть интересная ознакомительная статья об этом явлении.
Но сегодня я хочу поговорить именно о кастомных аннотациях в Android-приложении, которые обрабатываются в процессе компиляции проекта вашим собственным обработчиком и о автогенерации классов на их основе. А так же, по ходу дела, расскажу вам, как быстро все настроить в IDEA (сама я пользуюсь версией 12.1, возможно в других есть отличия).

Для реализации нам понадобится 2 проекта: в первом опишем наши кастомные аннотации и их обработчик, из него сгенерируем jar файл, который подключим ко второму тестовому проекту, который и будет использовать наши аннотации.

Шаг 1

Создаем новый library проект, в котором описываем свою аннотацию. Класс с аннотацией выглядит примерно так:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface CustomAnnotation {

    String className();
    String value() default "Hello";
    int type() default 0;
}


@Retention говорит о том, что наша аннотация будет присутствовать только в исходном коде и отброшена компилятором (а до этого момента мы её обработаем).

Шаг 2

Создаем непосредственно наш обработчик аннотаций. Он представляет собой класс, расширяющий AbstractProcessor. Ему мы говорим, что он будет обрабатывать все аннотации и указываем поддерживаемую версию исходных файлов таким образом:

@SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class CustomProcessor extends AbstractProcessor { 
}

Далее переопределяем метод process, в котором прописываем логику генерации нового класса. Мой метод выглядит так:

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        for (Element e : roundEnv.getElementsAnnotatedWith(CustomAnnotation.class)) {
            CustomAnnotation ca = e.getAnnotation(CustomAnnotation.class);
            String name = e.getSimpleName().toString();
            char[] c = name.toCharArray();
            c[0] = Character.toUpperCase(c[0]);
            name = new String(name);
            TypeElement clazz = (TypeElement) e.getEnclosingElement();
            try {
                JavaFileObject f = processingEnv.getFiler().
                        createSourceFile(clazz.getQualifiedName() + "Autogenerate");
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                        "Creating " + f.toUri());
                Writer w = f.openWriter();
                try {
                    String pack = clazz.getQualifiedName().toString();
                    PrintWriter pw = new PrintWriter(w);
                    pw.println("package "
                            + pack.substring(0, pack.lastIndexOf('.')) + ";");
                    pw.println("\npublic class "
                            + clazz.getSimpleName() + "Autogenerate {");

                    TypeMirror type = e.asType();

                    pw.println("\n    public " + ca.className() + " result = \"" + ca.value() + "\";");

                    pw.println("    public int type = " + ca.type() + ";");


                    pw.println("\n    protected " + clazz.getSimpleName()
                            + "Autogenerate() {}");
                    pw.println("\n    /** Handle something. */");
                    pw.println("    protected final void handle" + name
                            + "(" + ca.className() + " value" + ") {");
                    pw.println("\n//" + e);
                    pw.println("//" + ca);
                    pw.println("\n        System.out.println(value);");
                    pw.println("    }");
                    pw.println("}");
                    pw.flush();
                } finally {
                    w.close();
                }
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                        x.toString());
            }
        }
        return true;
    }

На этом этапе можно включить фантазию и писать, что душе угодно (ну или что требуется =). После того, как вы закончили с обработчиком аннотаций и описали все свои кастомные аннотации, из этого проекта нужно сгенерировать jar файл. В Idea 12 это делается достаточно просто: Project Settings -> Artifacts -> Add -> Jar -> From modules… Далее делаем Build -> Rebuild Project и находит наш сгенерированный файл jar в Output директории проекта.

Шаг 3

Создаем тестовый проект, в котором и будем использовать наши кастомные аннотации. К проекту подключаем сгенерированный на прошлом шаге jar файл и радуемся, что наши аннотации теперь нам доступны. В любом классе прописываем нашу аннотацию, например так:
public class MyActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @CustomAnnotation(className = "String", type = 1)
    public void annotatedMethod(String value) {
    }
}

Помните, что мы указали нашей аннотации @Target(ElementType.METHOD), а это значит что мы можем прописать ее только перед методом.

Шаг 4

Теперь скажем Idea использовать наш обработчик аннотаций и магия начнет работать! Для этого идём в Settings в раздел Compiler — > Annotation Processors. Ставим галочку Enable annotation processing, в поле Processor path указываем путь к нашему сгенерированному jar файлу. Так же в окне Processor FQ name вводим полное название класса, который отвечает за обработку. В нашем случает это CustomProcessor. Заполненное окно должно выглядеть примерно так.



Шаг 5

Делаем Build -> Rebuild project и наслаждаемся результатами. В дереве проекта должна появиться папка generated, в которой будут лежать новые файлы.
Вот что получилось у меня:

public class MyActivityAutogenerate {

    public String result = "Hello";
    public int type = 1;

    protected MyActivityAutogenerate() {}

    /** Handle something. */
    protected final void handleannotatedMethod(String value) {

//annotatedMethod(java.lang.String)
//@com.example.AnnotationsProcessor.CustomAnnotation(type=1, value=Hello, className=String)

        System.out.println(value);
    }
}


Happy codding!
Tags:
Hubs:
+11
Comments5

Articles

Change theme settings