Все программисты сталкиваются с boiler-plate кодом. Особенно Android-программисты. Писать шаблонный код — дело неблагодарное и, я уверен, что нет программиста, которому бы это доставляло удовольствие. В один прекрасный день я начал искать решения. Несмотря на то, что идея довольно проста: генерировать шаблонный код в отдельный класс и позже вызывать этот код в рантайме, готовых решений не нашлось, и я принялся за дело. Первая версия была реализована как один из подмодулей одного из рабочих проектов. Более двух лет я был доволен этим решением. Оно действительно работало и работало именно так, как я этого ожидал. Время шло, модуль дополнялся новыми функциями, рефакторился, оптимизировался. В целом PoC можно было назвать успешным, и я решил поделиться проектом с комьюнити. Спустя 8 месяцев программирования по вечерам, я на Хабре со своим первым в жизни постом. Итак, Jeta — фреймворк для генерации исходного кода, построенного на javax.annotation.processing. Open-Source, Apache 2.0, исходный код на GitHub, артефакты на jCenter, tutorials, samples, unit-tests, в общем все как положено. Для наглядности, давайте рассмотрим простой пример. В библиотеку входит аннотация @Log. С ее помощью упрощается объявление именованных логгеров внутри класса. Так, для этого класса, Jeta сгенерирует класс LogSample_Metacode с методом applyLogger: Из примера видно, что по аннотации @Log генерируется код, который присваивает логгер с именем “LogSample” аннотированному полю. Остается реализовать NamedLoggerProvider который будет поставлять логгеры из библиотеки, которая используется в вашем проекте. Помимо неявного именования логгеров, которое, как видно из примера, берется из названия класса, можно указать конкретное значение через параметр аннотации, как например @Log(“REST”). Этот прием избавляет от копи-пасты строки типа: что в свою очередь избавляет проект от логгеров с именами “соседов”, так как часто программисты забывают заменить передаваемый в качестве параметра класс. Конечно, это довольно простой пример. Тем не менее, он показывает основную идею фреймворка — меньше кода, больше стабильности. Несмотря на то, что основная цель Jeta — это избавление от шаблонного кода, на приеме, показанном выше, реализовано множество полезных функций, таких как Dependency Injection, Event Bus, Validators и др. Нужно заметить, что все они написаны согласно принципам фреймворка — без Java Reflection и, по возможности, все ошибки находятся на стадии компиляции. В этой статье мы так же не будем избавляться от выдуманного boiler-plate кейса. Вместо этого мы напишем кое-что полезное, а именно Data Binding (далее DB). Хотя, принципиальной разницы тут нет, и эту статью можно будет использовать как руководство для решения задач, связанных с избавлением от шаблонного кода. Android программисты, возможно, уже знакомы с этим термином. Не так давно Google выпустила Data Binding Library. Для тех из Вас, кто не знаком с этим паттерном, я уверен, что не составит большого труда разобраться с его концепцией из примеров в этой статье. Так же привожу два спойлера с небольшими экскурсами по Android и Data-Binding, соответственно. Экран, в контексте Android-программирования, называется Activity. Это Java класс наследованный от android.app.Activity. Для каждой активити существует XML-файл с разметкой, называемый Layout. Вот пример Activity из “Hello, World” приложения: Строка setContentView(R.layout.activity_main) связывает активити и лейаут через R файл, который генерируется автоматически. Так, для нашего лейаута activity_main.xml, R-файл будет содержать внутренний класс layout c полем activity_main и каким-то уникальным числовым значением. Для TextView, которому мы присвоили id = text1, это будет внутренний класс id и поле text1, соответственно. Data-binding позволяет писать DSL выражения внутри XML-файла. Вот пример с официального сайта developer.android.com: Так, в нужный момент, мы связываем объект пользователя (com.example.User) с лейаутом и data-binding автоматически проставляет значения в соответствующие компоненты. Так, первый TextView отобразит имя пользователя, а второй его фамилию. В этой статье мы напишем свой Data-Binding, правда, пока без преферанса, ну а в конце вас ждет небольшой интерактив. Перед тем как приступим, пара замечаний по Jeta. Все специфичные для андроида функции вынесены в отдельную библиотеку — Androjeta. Она расширяет Jeta а значит все, что доступно в Jeta, т.е. для любого Java-проекта, так же доступно в Androjeta. Первым делом мы создадим самый обычный Android проект с одной активити и с pojo-классом User. Наша задача — к концу статьи записать имя и фамилию юзера в соответствующие UI-компоненты посредством DB. Для наглядности я буду приводить скриншоты со структурой проекта.  Так как генерация кода происходит на стадии компиляции, и все сопутствующие для этого классы запускаются в отдельном окружении, нам понадобится модуль, который будет доступен и в рантайме и во время кода-генерации. Замечу, что это обычный Java-модуль, который будет содержать два файла — аннотацию DataBind и Metacode-интерфейс DataBindMetacode.  apt модуль содержит необходимые для кода-генерации классы. Как уже было сказано, этот модуль зависит от common и будет доступен только на стадии компиляции. Как и common, это обычный Java-модуль, который будет содержать единственный файл — DataBindProcessor. Именно в этом классе мы будем обрабатывать DataBind аннотацию, парсить XML-лейаут и генерировать соответствующий мета-код. Обратите внимание что apt модуль также зависит от org.brooth.androjeta:androjeta-apt:+:noapt, таким образом получая доступ к классам фреймворка.  Прежде чем приступить непосредственно к генерации мета-кода, сначала мы должны подготовить наше приложение. Первый делом мы изменим наш лейаут: Небольшое пояснение: мы объявили свой namespace с префиксом “androjeta” и добавили двум TextView атрибуты androjeta:setText с DB-выражениями. Так мы сможем найти и обработать эти выражения в DataBindProcessor, сгенерировав соответствующий мета-код. Тут важным являются две вещи. Во-первых, мы добавили на активити аннотацию @DataBind, которую ранее мы создали в common модуле. Таким образом, на стадии генерации Jeta найдет этот класс и передаст его в DataBindProcessor. Во-вторых, после того, как мы установили лейаут, мы вызываем MetaHelper.applyDataBind(this). C помощью таких статических методов проще обращаться к мета-коду. Давайте создадим этот класс. MetaHelper — необязательный класс. Это способ организации обращение к мета-коду. Он служит исключительно для удобства. Подробней об этом классе можно прочитать на этой странице. Тут же нам важно, что метод applyDataBinding передает работу DataBindController-у: Напомню, контроллеры — это классы, которые применяют мета-код к мастерам. Больше информации можно найти на этой странице. На последнем шаге нам нужно добавить DataBindProcessor в список процессоров, которые Jeta вызывает для генерации мета-кода. Для этого в корневом пакете app модуля (app/src/main/java) мы создадим файл jeta.properties с содержимым: Подробнее об этом файле и о доступных настройках вы можете найти на этой странице. Думаю, излишне будет комментировать каждый шаг процессора, т.к. ничего инновационного они не содержат. Достаточно описать основные моменты: мы проходимся SAX-парсером по XML-лейауту, находим DB-выражения и генерируем соответствующий Java-код. Нужно заметить, что Jeta использует JavaPoet — замечательную библиотеку от Square для генерации Java-кода. Рекомендую пройтись по README, если соберетесь писать свой процессор. Ниже привожу исходный код DataBindProcessor: Для начала удостоверимся, что все работает. Для этого в директории проекта выполним команду: Если в выводе нет никаких ошибок, и вы видите запись: значит все ОК, и по пути /app/build/generated/source/apt/ мы сможем увидеть сгенерированный код:  Как видно, мета-код отформатирован и хорошо читаем, следовательно, его легко отлаживать. Так же, важным плюсом является то, что все возможные ошибки обнаружатся на стадии компиляции. Так, если добавить @DataBind на Activity у которой нет поля user, передать в параметры неправильное название лейаута или ошибиться в DB-выражении, то сгенерированный код не скомпилируется и проект не соберется. На этом этапе вы можете запустить приложение, и, как ожидается, на экране вы увидите данные о пользователе user. Прошу отнестись к примеру именно как к Proof-Of-Concept, а не как к готовому решению. К тому же, его задача — продемонстрировать работу фреймворка, и не факт, что Jeta-DB пойдет в лайв. Собственно, обещанный интерактив. Напишите в комментариях, что бы вы хотели видеть в Data-Binding-е. Возможно, вам не хватает каких — то возможностей в реализации от Google. Возможно, вы хотите избавиться от какого-то еще boiler-plate кейса. Также, буду благодарен за любые другие замечания или пожелания. Я, в свою очередь, постараюсь выбрать самое интересное и реализовывать в будущих версиях. Спасибо, что дочитали до конца.
Happy code-generating! :) » Официальный сайт
» Исходный код примера на GitHub
» Jeta на GitHub
» Androjeta на GitHub Mobile Developer