GlobalModule.java

package org.sterling.runtime;

import static java.util.regex.Pattern.quote;
import static org.sterling.runtime.expression.ExpressionConversions.convertSymbol;
import static org.sterling.runtime.expression.ExpressionFactory.declaration;
import static org.sterling.runtime.expression.ExpressionFactory.module;
import static org.sterling.runtime.expression.ExpressionFactory.symbol;
import static org.sterling.util.StringUtil.stringify;

import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.sterling.SterlingException;
import org.sterling.runtime.exception.LoadModuleException;
import org.sterling.runtime.exception.UndefinedMemberException;
import org.sterling.runtime.expression.DeclaredExpression;
import org.sterling.runtime.expression.Expression;
import org.sterling.runtime.expression.ModuleExpression;
import org.sterling.runtime.expression.Symbol;
import org.sterling.source.scanner.InputReader;

public class GlobalModule extends Expression {

    private final Compiler compiler;
    private final Map<Symbol, DeclaredExpression> members;
    private final Set<Symbol> loadedMembers;
    private final ClassLoader classLoader;

    public GlobalModule(ClassLoader classLoader, Compiler compiler) {
        this.compiler = compiler;
        this.classLoader = classLoader;
        this.members = new ConcurrentHashMap<>();
        this.loadedMembers = new CopyOnWriteArraySet<>();
    }

    @Override
    public Expression access(Expression member) throws SterlingException {
        Symbol symbol = convertSymbol(member);
        if (!isDefined(symbol)) {
            try {
                return super.access(member);
            } catch (UndefinedMemberException exception) {
                load(symbol);
            }
        }
        return members.get(symbol).getExpression();
    }

    public Expression load(String name) throws SterlingException {
        Expression expression = this;
        for (String part : name.split(quote("/"))) {
            expression = expression.access(symbol(part));
        }
        return expression;
    }

    public ModuleExpression loadModule(Symbol module) throws SterlingException {
        URL url = getModuleUrl(module);
        if (url == null) {
            return module(module, this);
        } else {
            try (InputReader reader = readSource(url)) {
                return module(module, this, compiler.compile(reader, this));
            } catch (IOException exception) {
                throw new LoadModuleException(exception);
            }
        }
    }

    @Override
    public String toString() {
        return stringify(this);
    }

    private boolean isDefined(Symbol symbol) {
        return members.containsKey(symbol);
    }

    private void load(Symbol subModule) throws SterlingException {
        if (!loadedMembers.contains(subModule)) {
            loadedMembers.add(subModule);
            members.put(subModule, declaration(subModule, loadModule(subModule)));
        }
    }

    private URL getModuleUrl(Symbol module) {
        String moduleName = module.getValue();
        URL url = classLoader.getResource(moduleName + ".ag");
        if (url == null) {
            url = classLoader.getResource(moduleName + "/_base.ag");
        }
        return url;
    }

    private InputReader readSource(URL url) throws SterlingException, IOException {
        return new InputReader(url.toString(), url.openStream());
    }
}