Skip to content

Gestalt Module Quick Start

Immortius edited this page May 28, 2015 · 11 revisions

Module Structure

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.

Create a Module Registry and populate with modules from a directory

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;
}

Determine a compatible set of dependencies for a selection of modules

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");
    }
}

Establish a sandboxed module environment

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());

Discover classes from a module

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);
}

Create a Module for a library on the Classpath

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);
    }
}