Architecture
You can skip this part of the wiki if you are not interested in knowing how everything works internally
I'm only going to cover Echo's architecture (at least for now).
Echo consists of several parts, such as:
processor
,
util
,
core
and
annotations
.
Annotation Processor
processor
is an annotaton processor which processes @Root
and @Echo
annotations, then, it writes two files into modules' jar: moduleid.meta.echo
and moduleid.ref.echo
(where moduleid
is modules' id).
Meta File
.meta.echo
is a
Version
object in binary format, which is then used to check compatibility.
Reference File
.ref.echo
is a ConcurrentHashMap<String, List<String>>
in binary format, where key is path to some "original" class, and value is list of paths to "echo" classes that modify the original class.
Util
util
contains utility classes.
Config Util
ConfigUtil
has code to read/write objects from/to objects. Used by processor
and core
to read and write .meta.echo
and .ref.echo
.
File Util
FileUtil
has code to manipulate file paths and read bytes from file.
Version
Version
class is used to determine version of echo toolchain which module was compiled with, it is being written in moduleid.meta.echo
(where moduleid
is modules' id) in compile-time and then compared in run-time with core
's version.
Version of echo toolchain is the same for every echo sub-project. So for processor-1.0
there is core-1.0
.
Annotations
annotations
contains Echo's annotations, which are used by processor
, core
and modules.
Root
Root
is an annotation that is used to define module root. Takes module id.
Echo
Echo
annotation annotates an echo class, it takes target class' path as an argument.
Shadow
Shadow
is an annotation that marks a method or method in echo class as already-defined. Allows to trick Java into using an actual method with the same name and descriptor.
Insert
Insert
annotation inserts code to the start of method. Takes name of target's method and it's descriptor.
Add
Add
annotation inserts code at the tail of method. Takes name of target's method and it's descriptor.
After
After
NOT-IMPLEMENTED
Before
Before
NOT-IMPLEMENTED
Until Echo v1 releases, more and more annotations will be added.
Core
core
's main class -
Echo
.
First, when it's initialized, it gets current version,
global config
and
custom ClassLoader
.
Then, launcher runs
load(String)
method, which takes module id. In return it reads the .meta.echo
file with the given id and compares it with runtime version. After that, it reads the .ref.echo
file of that module and merges it into the
global config,
which maps it's items to
ClassNode
's
(see this for more info). After all loading is done, launcher runs
finish
method, which takes the global config and starts the process of transformation (see
this
for more info).
Transformation Manager
TransformationManager
cycles through all entries in the global config and calls
Transformers
's handle
method,
to destribute needed event across all registered transformers.
Transformer
is an SPI, which can be used to register your own transformers (check Transformer API for more info).
After all transformers have finished their work,
DefineTransformer
will be called to get the bytecode of modified original class
(represented by ClassNode
)
and define it using the
custom ClassLoader
.
Defining the class before JVM has loaded it is a great way to modify classes, unfortunately, it can only work before JVM has read that class, because redefenition is not allowed (unless you are using Java Agents to modify classes, but it's much more time consuming).