ModuleExpression.java

package org.sterling.runtime.expression;

import static org.sterling.runtime.expression.ExpressionConversions.convertSymbol;
import static org.sterling.runtime.expression.ExpressionFactory.declaration;
import static org.sterling.runtime.expression.ExpressionFactory.symbol;
import static org.sterling.util.StringUtil.stringify;

import java.util.HashMap;
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.GlobalModule;
import org.sterling.runtime.exception.UndefinedMemberException;

public class ModuleExpression extends Expression {

    private static Map<Symbol, DeclaredExpression> processMembers(Set<DeclaredExpression> members) {
        Map<Symbol, DeclaredExpression> map = new HashMap<>();
        for (DeclaredExpression declaration : members) {
            map.put(declaration.getSymbol(), declaration);
        }
        return map;
    }

    private final Symbol symbol;
    private final Map<Symbol, DeclaredExpression> members;
    private final Set<Symbol> loadedMembers;
    private final GlobalModule loader;

    public ModuleExpression(Symbol symbol, GlobalModule loader, Set<DeclaredExpression> members) {
        this.symbol = symbol;
        this.loader = loader;
        this.members = new ConcurrentHashMap<>(processMembers(members));
        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();
    }

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

    private Symbol getModuleName(Symbol subModule) {
        return symbol(symbol.getValue() + "/" + subModule.getValue());
    }

    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, loader.loadModule(getModuleName(subModule))));
        }
    }
}