-
Notifications
You must be signed in to change notification settings - Fork 23
Gestalt Module Quick Start
A module is either a directory or an archive.
A directory module's classes should be under /build/classes
(by default).
An archive may be a jar or zip.
Both types of modules must contain a module metadata file (named 'module.info' by default) at their root. This is a json file with the following format:
{
"id": "Identifier",
"version": "1.0.0",
"displayName": {
"en": "Displayable name"
},
"description": {
"en": "Longer description of the module"
},
"dependencies": [
{
"id": "BaseModule",
"minVersion": "1.0.0",
"maxVersion": "2.0.0",
"optional": true
}
],
"requiredPermissions" : ["network", "io"]
}
For dependencies, "maxVersion" is the upperbound (exclusive) for accepted versions. It is optional, and if not provided the next major version is used - or next minor version if the version is below "1.0.0". Dependencies can be marked as optional if they are not required for a module to function, default is for dependencies to be mandatory.
For displayName and description, a simple string can also be provided instead of a mapping of languages.
A module registry is a store of all available modules.
import org.terasology.module.ModulePathScanner;
import org.terasology.module.ModuleRegistry;
import org.terasology.module.TableModuleRegistry;
public ModuleRegistry buildModuleRegistry() {
ModuleRegistry registry = new TableModuleRegistry();
registry.add(buildClasspathModule());
new ModulePathScanner().scan(registry, Paths.get(""));
return registry;
}
DependencyResolver takes a registry and one or more required module names, and determines a compatible set of modules (if any).
import org.terasology.module.DependencyResolver;
import org.terasology.module.ResolutionResult;
public Set<Module> determineModuleSet() {
DependencyResolver resolver = new DependencyResolver(buildModuleRegistry());
ResolutionResult resolutionResult = resolver.resolve(new Name("ModuleOne"), new Name("ModuleTwo"));
if (resolutionResult.isSuccess()) {
return resolutionResult.getModules();
} else {
throw new RuntimeException("Unable to resolve compatible dependency set for ModuleOne and ModuleTwo");
}
}
Modules can be run in a restricted environment where the classes and permissions they have access to are restricted.
import org.terasology.module.ModuleEnvironment;
import org.terasology.module.sandbox.BytecodeInjector;
import org.terasology.module.sandbox.ModuleSecurityManager;
import org.terasology.module.sandbox.ModuleSecurityPolicy;
public ModuleEnvironment establishSecureModuleEnvironment() {
ModuleSecurityManager securityManager = new ModuleSecurityManager();
// Establish standard permissions
securityManager.getBasePermissionSet().addAPIPackage("com.example.api");
securityManager.getBasePermissionSet().addAPIPackage("sun.reflect");
securityManager.getBasePermissionSet().addAPIClass(String.class);
// Add optional permission set "io"
PermissionSet ioPermissionSet = new PermissionSet();
ioPermissionSet.addAPIPackage("java.io");
securityManager.addPermissionSet("io", ioPermissionSet);
Policy.setPolicy(new ModuleSecurityPolicy());
System.setSecurityManager(securityManager);
return new ModuleEnvironment(determineModuleSet(), securityManager,
Collections.<BytecodeInjector>emptyList());
}
In addition to manually populating the security manager's api, the APIScanner can be used to populate it with all @API annotated classes and packages from classpath modules:
new APIScanner(securityManager).scan(buildModuleRegistry());
Once an environment is established, it provides a number of methods for discovering classes of interest.
public void discoverTypes() {
ModuleEnvironment environment = establishModuleEnvironment();
environment.getTypesAnnotatedWith(SomeAnnotation.class);
environment.getTypesAnnotatedWith(SomeAnnotation.class, new FromModule(environment, new Name("ModuleId")));
environment.getSubtypesOf(SomeInterface.class);
environment.getSubtypesOf(SomeInterface.class, new FromModule(environment, new Name("ModuleId")));
Name moduleId = environment.getModuleProviding(someDiscoveredClass);
}
Often it is good to have some part (or all) of the classpath to act as a module.
import org.terasology.module.Module;
import org.terasology.module.ClasspathModule;
import org.terasology.module.ModuleMetadata;
import org.terasology.naming.Name;
import org.terasology.naming.Version;
public Module buildClasspathModule() {
ModuleMetadata metadata = new ModuleMetadata();
metadata.setId(new Name("Core"));
metadata.setVersion(new Version("1.0.0"));
try {
return ClasspathModule.create(metadata, getClass());
} catch (URISyntaxException e) {
throw new RuntimeException("Source location could not be converted to URI", e);
}
}