<dependency>
<groupId>dev.tindersamurai</groupId>
<artifactId>eac</artifactId>
<version>1.0.3</version>
</dependency>
compile 'dev.tindersamurai:eac:1.0.3'
Escapy Active Component это рефлексивная библотека для сериализации и конфигурации компонентов с возможностью использования в скриптинге через внешние файлы.
На данный момент существует имплементация ореентированная на работу с XML
файлами, однако ничего не мешает написать свою заточенную
под JSON
.
В основе концепции лежит разделение объектов на два вида:
Plain Java Objet
Escapy Component
Первые как следует из названия это обыкновенные объекты java инстанцируемые как собственно обычные объекты через конструктор.
Вторые это компоненты библотеки которые конфигурируются через фабрики компонентов в коде с помощью callback' и аннотаций @EscapyComponent
,
такие компоненты не обязательно должны быть объектами java, а могут быть и вовсе обыкновенными методами. В свою очередь
фабрики компонентов @EscapyComponentFactory
могут иметь внутреннее состояние изменяемое через те самые методы, что собственно и позволяет использовать
библиотеку для скриптинга (от сюда и название), а в связке с Dependency injection
превращает библиотеку в убийственный комбайнер.
Создание компонентов, как и вообще работа с библиотекой пользователем ограничивается в целом
взаимодействием с интерфейсом EscapyComponentParser
(его реализациями). Интерфейс описывает 3 метода
чья роль ясна из названий, это:
void setComponentFactory(IEscapyComponentFactory nodeFactory);
void setObjectFactory(IEscapyObjectFactory nodeFactory);
<T> T parseComponent(String file);
Первые 2 метода нужны если потребуется расширить функциональность библиотеки и/или используемой
имплементации EscapyComponentParser
посредством собственных реализаций под-фабрик парсера.
В 95% случаев, обычному пользователю данная функциональность не пригодится, а все взаимодействие с библиотекой
сведется к созданию стандартной имплементации интерфейса - XmlStreamComponentParser
, конструктор которого
в качестве входных параметров получает массив Фабрик-компонентов
и вызову метода <T> T parseComponent(String file);
.
public final class ExampleMain {
@EscapyComponentFactory("test")
public static final class TestComponent {
@EscapyComponent("create_int")
public int createInt(@Arg("SomeVal") String value) {
// ...
return 42;
}
}
public static void main(String ... args) throws NoSuchFileException {
final EscapyComponentParser parser = new XmlStreamComponentParser(new TestComponent());
final String file = System.getProperty("user.dir") + "/resources/example.xml";
Object result = parser.parseComponent(file);
// ...
}
}
Не следует путать IEscapyComponentFactory
и фабрики компонентов отмеченные аннотацией @EscapyComponentFactory
, это не одно и тоже!
Декларация и конфигурация инстанцируемых через внешний файл компонентов производится через фабрики компонентов которые могут иметь внутреннее состояние.
Для этого следуеть отметить класс аннотацией @EscapyComponentFactory(%name%)
которая в качестве аргумента принимает
название фабрики. Так же фабрики могут содержать другие фложенные фабрики, однако в таком случае они должны иметь пустой публичный конструктор.
За инстанцирование компонентов внутри фабрик отвечают методы отмеченные аннотацией @EscapyComponent(%name%)
их входные параметры
могут быть отмечены аннотацией @Arg(%name%)
что бы можно было связать параметр из внешнего файла с параметром метода, в противном случае
связывание будет происходить по порядковому номеру, так же параметрами могут быть варарги (varrags), их тоже можно именовать, но в таком случае
во внешнем файле придется передавать варарг явным способом через массив.
Инстанцирование компонентов в фабрике можно перехватывать и контролировать через callback'и интерфейса EscapyComponentFactoryListener
,
для этого достаточно что бы фабрика просто реализовала 2 метода из этого интерфейса:
-
boolean enterComponent(String name);
-
Object leaveComponent(String name, Object instance);
Первый метод вызывается перед созданием компонента - с его именем в качестве входного параметра, а от возвращаемого значения зависит будет ли данный компонент создаваться вообще. Второй метод в качестве первого входного параметра принимает имя компонента, в свою очередь второй параметр это уже созданный компонент который по умолчанию возвращает метод.
Библиотека содержит некоторе число вспомагательных компонентов, среди которых:
-
@EscapyComponent("debug") void debug(@Arg("args") Object ... args);
-
@EscapyComponent("main") void main(Object ... args);
-
@EscapyComponent("array") Object newArrayInstance(@Arg("type") Class<?> type, @Arg("elements") Object ... args);
-
@EscapyComponent("array-auto") Object newAutoArray(@Arg("elements") Object ... args);
-
@EscapyComponent("array-list") List<?> newArrayList(@Arg("elements") Object ... args);
-
@EscapyComponent("class") Class<?> findClass(@Arg("object") Object o);
-
@EscapyComponent("class-by-name") Class<?> classByName(String name);
-
@EscapyComponent("entry") Entry createEntry(@Arg("key") Object key, @Arg("value") Object value);
-
@EscapyComponent("map") <K, V> Map<K, V> createMap(@Arg("entries") Entry<K, V> ... entries);
-
@EscapyComponent("null") Object nullObject(Object ... args);
-
@EscapyComponent("type") Class<?> primitiveType(@Arg("object") Object o);
C полным списком можно ознакомиться заглянув в класс-фабрику UtilityCoreComponent.java
.
Библиотека позволяет подключать компоненты и объекты из внешних файлов
с помощью директивы <c:f.include / >
принимающей в качестве аргумента
String
путь к файлу.
C полным списком стандартных компонентов работающих с файлами можно ознакомится
в классе фабрике FilesCoreComponent.java
С некоторых пор появилась возможность использовать переменные, а так же обрабатывать
компоненты прямо в стрингах c помощью нотации ${...}
, для этого служит компонент v.string
пример его использования:
<c:v.string name="rooted_root">${f.root}/bloop</c:v.string>
Полный список полезных компонентов можно найти в классе
VariablesCoreComponent.java
@EscapyComponentFactory("some")
public class SomeExampleFactory {
private Object someState = new Integer(42);
@EscapyComponent("mutate")
public final void justVoidCall(Object arg) {
if (someState instanceof Integer && arg instanceof Integer)
someState += arg;
else someState = arg;
}
@EscapyComponent("state")
public Object getState() {
return someState;
}
@EscapyComponentFactory("nested-a") // Static class
public static final class Nested_A implements EscapyComponentFactoryListener {
@Override
public boolean enterComponent(String name) {
System.out.println("Create: " + name);
return true;
}
@Override
public Object leaveComponent(String name, Object instance) {
System.out.println("Createed: " + name);
System.out.println("Instance: " + instance);
return instance;
}
@EscapyComponent("sum")
public String sumToText(@Arg("a") float a, @Arg("b") float b) {
return new Float(a + b).toString();
}
}
@EscapyComponentFactory("second")
public class SencondNestedFactory {
@EscapyComponent("hello")
public String hello(String ... varargs) {
for (String s: varargs)
System.out.println(s);
return "Hello world!";
}
@EscapyComponent("calculate")
public int calc(@Arg("val") int val, Float ... args) {
int r = val;
for (Float f: args)
r += (int) f;
return r;
}
@EscapyComponent("also-show-state")
public void showParentState() {
System.out.println(someState);
}
}
}
В конечном итоге, главная цель библиотеки заключается в использовании внешнего файла конфигурации,
по умолчанию в библиотеке присутствует поддержка XML
формата.
Для работы используются 3 зарезервированных префикса и один ключевой тэг:
<c: ...
- префикс применимый к компонентам<o: ...
- префикс применимый к объектам java<m: ...
- префикс применимый к методам объектов<new>
- тэг применимый к конструкторам объектов java
Каждый файл конфигурации должен иметь хотя бы один (и только один) корневой компонент, который
будет создан и возвращен при вызове метода <T> T parseComponent(String file);
из интерфейса EscapyComponentParser
.
Создание компонента в файле конфигурации объявляется через префикс <c: ...
и последующим за
ним названием компонента из фабрики компонентов разделенных (по умолчанию) точкой.
Например:
<c:some-comp-nodeFactory.nested-nodeFactory.component-name />
some-comp-nodeFactory
,nested-nodeFactory
- это фабрики компонентов отмеченные в коде аннотацией@EscapyComponentFactory(%name%)
component-name
- это компонент, метод из фабрики отмеченный аннотацией@EscapyComponent(%name%)
В случае с корневым компонентом, следует так же явно объявить используемые префиксы. Ниже показан полный пример демонстрационного корневого компонента:
<c:u.main
xmlns:c="https://henryco.net/escapy"
xmlns:o="https://henryco.net/escapy"
xmlns:m="https://henryco.net/escapy"
>
<!-- Some components and objects here -->
</с:u.main>
Так же, любой входной параметр компонента, не важно будь то другой компонент или же объект,
может быть именован и в дальнейшем связан с входным параметром метода создающего компонента, для этого к тегу компонента следует добавить аттрибут name
<c:nodeFactory.component>
<o:float name="someFloat"> 42 </o:float>
<c:nodeFactory-2.other name="other">
<o:string> test </o:string>
</c:nodeFactory-2.other>
</c:nodeFactory.component>
С объектами дело обстоит немного интереснее, как и в случае с компонентами, декларация объектов начинается с префикса, но на этом сходства заканчиваются.
На все теги объектов распространяется несколько важных правил:
- По умолчанию тип объекта следует из названия к которому добавляется префикс
java.lang.
, а первая буква заменяется заглавной:
<o:string />
---java.lang.String
<o:float />
---java.lang.Float
<o:object />
---java.lang.Object
- Тип объекта можно указать самому используя атрибут
class
:
<o:object class="java.util.List" />
---java.lang.Object
---java.util.List
<o:number class="java.lang.Integer" />
---java.lang.Number
---java.lang.Integer
- По умолчанию, для входного параметра конструктора отдается прдепочтение типу
String
если не указанно иначе:
<!-- new String( new String( "hello" ) ) -->
<!-- String into string -->
<o:string> hello </o:string>
<!-- new Short( new String( "42" ) ) -->
<o:short> 42 </o:short>
<!-- new Short( new String ( "42" ) ) -->
<o:short>
<o:string> 42 </o:string>
</o:short>
<!-- new Float( new Float( new Stirng( "50" ) ) ) -->
<o:float>
<o:float> 50 </o:float>
</o:float>
- Конструктор автоматически определяется из типа или же в ручную из тега
<new>
:
<!-- Constructor: java.lang.Float -->
<o:float> 10 </o:float>
<!-- Constructor: java.lang.Float -->
<o:float>
<new> 10 </new>
</o:float>
<!-- Constructor: java.lang.Integer -->
<o:object>
<new class="java.lang.Integer"> 10 </new>
</o:object>
<!-- Constructor: java.util.ArrayList -->
<!-- Type: java.util.List -->
<o:object class="java.util.List">
<new class="java.util.ArrayList" />
</o:object>
- У объектов можно вызывать методы используя тэг
<m: ...>
, вызов произойдет сразу после создания объекта и перед его возращением дальше во флоу:
<o:object class="java.util.List">
<!-- empty constructor -->
<new class="java.util.ArrayList"/>
<!-- this is method statements -->
<m:add>
<o:integer>10</o:integer>
</m:add>
<m:add>
<o:object>
<new class="java.lang.Integer">22</new>
</o:object>
</m:add>
</o:object>
.