-
Notifications
You must be signed in to change notification settings - Fork 22
Modules
A module is a package of code and assets that can be loaded at runtime. Each module is uniquely identified by a combination of an id and a version.
There are three types of module that are supported by ModuleFactory out of the box:
PathModules are modules that exist as a directory - this is typically used to support modules under development. These are also the only type of module that support embedded libraries.
ArchiveModules are modules that exist as an archive file - either a jar, aar (for android) or zip file. This generally supports distributed modules. It is expected that these modules follow standard jar conventions for class placement.
A PackageModule is a module build from a location on the classpath - it contains all the classes and resources in that package. This is intended to support "built-in" modules that are part of the application itself, and provides an easy way to expose classes and assets into the module security sandbox.
An important part of each module is a reflections manifest - this provides a listing of all classes and resources in the module with inheritance and annotation information. The manifest is generated using the Reflections Library. This can be done at runtime (on Java only, not Android) or preferably generated at compile time. Examples of this are here: java, android. By default ModuleFactory will load manifests named manifest.json, but it is also possible to register support for other manifest formats if desired.
It is also possible to create modules which are composed of any combination of file sources for resources, directories and archives containing classes to load and a predicate determining what already loaded classes to include in the module. The constructor for Module is:
public Module(ModuleMetadata metadata, ModuleFileSource fileSources, Collection<File> classpaths, Reflections moduleManifest, Predicate<Class<?>> classPredicate) ...Each module has a ModuleMetadata object that provides the id and version, as well as other metadata describing the module. The built-in information is:
- Id - the identifying name of the module. A module id is a Name - a case-insensitive string - so "core", "CORE", and "Core" are all equal Names.
- Version - the identifying version of the module. Versions are Semantic Versions composed of three numbers - major, minor and patch - separated by periods. Some examples are
2.0.0,1.7.3and4.1.3. Snapshot versions are also supported with a-SNAPSHOTsuffix. - DisplayName - the internationalised name of the module suitable for display.
- Description - the internationalised description of the module suitable for display.
- Dependencies - information on what other modules are required by the module.
- RequiredPermissions - any additional permissions that the module requires.
Dependencies is a list of other modules the module requires. Each module is qualified by an id and a range of supported versions - minVersion (inclusive) and maxVersion (exclusive). If not specified, the maxVersion is the next major version increment of the minVersion (so a minVersion of 1.2.3 would have a maxVersion of 2.0.0 by default).
Snapshot versions count as their version number, so a range of 1.2.3 to 2.0.0 would include 1.2.3-SNAPSHOT but not 2.0.0-SNAPSHOT. A real version is used over a snapshot version.
Dependencies can also be marked as Optional, either true or false defaulting to false. This indicates modules that aren't required, but if present must fall within the provided version range for the module to function.
ModulePathScanner is the recommended way of loading modules. By providing the scanner with a [ModuleRegistry](Module Registry) and one or more paths, the scanner will find and load all modules within those paths and register them into the registry.
ModulePathScanner scanner = new ModulePathScanner();
ModuleRegistry registry = new TableModuleRegistry();
scanner.scan(registry, path, additionalPath);By default the ModulePathScanner expects modules to follow these structural conventions, although they can be overridden though the ModuleLoader the path scanner uses:
- Compiled code for PathModules is expected to be under build/classes from the root path of a PathModule.
- Metadata is expected to be in a file called module.info in the root of each module.
Module metadata uses a json format that looks like this:
{
"id": "Core",
"version": "1.0.0",
"author": "Immortius",
"displayName": {
"en": "Core"
},
"description": {
"en": "A very important module"
},
"dependencies" : [
{
"id": "Base",
"minVersion": "1.0.0",
"maxVersion": "2.0.0",
"optional": false
}
]
}This corresponds to the core metadata attributes. If you don't need to support multiple languages, displayName and description can be simplified to
{
"displayName": "Core",
"description": "A very important module"
}It is possible to register extensions to metadata - extra fields that will be read from the metadata files and become available in the ModuleMetadata of Modules. This is done by registering them with a ModuleMetadataReader and creating the ModulePathScanner with this reader:
ModuleMetadataReader metadataReader = new ModuleMetadataReader();
metadataReader.registerExtension("rating", Integer.class);
ModulePathScanner scanner = new ModulePathScanner(new ModuleLoader(metadataReader));Extension data can then be read out using the same identifier as the extension was registered with:
Integer rating = module.getMetadata().getExtension("rating", Integer.class);If the extension uses a more complicated object, a custom Gson deserializer or type adapter can be written and registered with the MetadataReader.