//                 _      /\ |)|
//   _  _____ _ __(_)___ /  \| |   Keris Reflection API
//  | |/ / _ \ '__| / __|          (c) 2002-2003, Matthias Zenger
//  |   <  __/ |  | \__ \
//  |_|\_\___|_|  |_|___/          Contexts
//
//  [[id: MAIN.keris (2076) 24-Feb-03 17:01]]

package keris.reflect;

import java.lang.reflect.*;
import java.util.*;


/**
 * Instances of the class <code>Context</code> represent linking contexts.
 * Linking contexts are sets of interconnected module instances. A <code>
 * Context</code> object maps <code>Module</code> objects to a corresponding
 * implementation provided by the context. Therefore reflective <code>Context
 * </code> objects are share similarities with the module contexts implicitly
 * defined by Keris modules through requirements and submodule declarations.
 *
 * <p> It is possible to instantiate empty contexts and link in modules
 * dynamically, or one can use method <code>Module.getContext</code> to
 * retrieve a concrete context from a Keris module implementation.
 *
 * <p> The <code>Context</code> class provides means to create contexts,
 * to inspect them, and to modify them. A link between Keris modules and
 * contexts is provided by class <code>Module</code>.
 *
 * @author   Matthias Zenger
 * @version  1.0, 27-Feb-2003
 * @see      keris.reflect.Module
 */
public final class Context {
    
    /** A mapping from <code>Module</code> instances to module
     *  implementations.
     */
    private HashMap modules;
    
    /** A default invocation handler for dynamically created propagators.
     */
    private InvocationHandler handler = new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String name = method.getName().substring(1).replace('_', '.');
            Module required = Module.forName(name);
            return implementationOf(required);
        }
    };
    
    Context(HashMap modules) {
        this.modules = modules;
    }
    
    /** Creates a new empty context.
     */
    public Context() {
        modules = new HashMap();
    }
    
    /** Returns an array of all modules provided by this context.
     */
    public Module[] modules() {
        Module[] res = new Module[modules.size()];
        Iterator iter = modules.keySet().iterator();
        int i = 0;
        while (iter.hasNext())
            res[i++] = (Module)iter.next();
        return res;
    }
    
    /** For a given module, return its implementation in this context.
     */
    public Object implementationOf(Module that) {
        Iterator iter = modules.keySet().iterator();
        while (iter.hasNext()) {
            Module mod = (Module)iter.next();
            if (mod.refinementOf(that))
                return modules.get(mod);
        }
        return null;
    }
    
    /** Check if the given module is implemented in this context.
     */
    public  boolean isImplemented(Module that) {
        Iterator iter = modules.keySet().iterator();
        while (iter.hasNext())
            if (((Module)iter.next()).refinementOf(that))
                return true;
        return false;
    }
    
    /** Check if the given module is compatible to the context so that
     *  any possible reference to this module is unambiguous.
     */
    public boolean compatibleTo(Module that) {
        return compatibleTo(that, null);
    }
    
    /** Check if the given array of modules is compatible to the
     *  context so that any possible reference to any of these modules
     *  is unambiguous.
     */
    public boolean compatibleTo(Module[] thatmod) {
        return compatibleTo(thatmod, null);
    }
    
    /** Check if the given context is compatible to this context so that any
     *  possible reference to any of the modules of the given context is
     *  is unambiguous in this module.
     */
    public boolean compatibleTo(Context that) {
        return compatibleTo(that.modules());
    }
    
    private boolean compatibleTo(Module that, Set ignore) {
        Iterator iter = modules.keySet().iterator();
        while (iter.hasNext()) {
            Module mod = (Module)iter.next();
            if (!((ignore != null) && ignore.contains(mod)) &&
                !that.differentFrom(mod))
                return false;
        }
        return true;
    }
    
    private boolean compatibleTo(Module[] thatmod, Set ignore) {
        for (int i = 0; i < thatmod.length; i++)
            for (int j = i + 1; j < thatmod.length; j++)
                if (!thatmod[i].differentFrom(thatmod[j]))
                    return false;
        for (int i = 0; i < thatmod.length; i++)
            if (!compatibleTo(thatmod[i], ignore))
                return false;
        return true;
    }
    
    /** Adds an implementation for a given module to this context.
     */
    public void include(Module mod, Object impl)
        throws IncompatibilityException {
        if (!compatibleTo(mod))
            throw new IncompatibilityException();
        modules.put(mod, impl);
    }
    
    /** Adds all module implementations of a given context to this context.
     */
    public void include(Context that)
        throws IncompatibilityException {
        Module[] thatmod = that.modules();
        for (int i = 0; i < thatmod.length; i++)
            include(thatmod[i], that.modules.get(thatmod[i]));
    }
    
    /** Links a new module instance for a given module into this context.
     */
    public Object link(Module mod) throws IncompatibilityException,
                                          ClassNotFoundException {
        if (!compatibleTo(mod))
            throw new IncompatibilityException();
        Object res;
        modules.put(mod, res = instantiate(mod));
        return res;
    }
    
    /** Links a set of new module instances (that may recursively depend on
     *  each other) into this context.
     */
    public Object[] link(Module[] mods) throws IncompatibilityException,
                                               ClassNotFoundException {
        if (!compatibleTo(mods))
            throw new IncompatibilityException();
        for (int i = 0; i < mods.length; i++)
            modules.put(mods[i], this);
        Object[] res = new Object[mods.length];
        for (int i = 0; i < mods.length; i++)
            res[i] = instantiate(mods[i]);
        for (int i = 0; i < mods.length; i++)
            modules.put(mods[i], res[i]);
        return res;
    }
    
    /** Updates an outdated version of a given module with an instance
     *  of the new version.
     */
    public Object update(Module mod) throws IncompatibilityException,
                                          ClassNotFoundException {
        HashSet ignore = new HashSet();
        mod.getPreviousVersions(ignore);
        if (!compatibleTo(mod, ignore))
            throw new IncompatibilityException();
        Iterator iter = ignore.iterator();
        while (iter.hasNext())
            modules.remove(iter.next());
        Object res;
        modules.put(mod, res = instantiate(mod));
        return res;
    }
    
    /** Updates the outdated versions of the given modules with instances
     *  of the new module versions.
     */
    public Object[] update(Module[] mods) throws IncompatibilityException,
                                              ClassNotFoundException {
        HashSet ignore = new HashSet();
        for (int i = 0; i < mods.length; i++)
            mods[i].getPreviousVersions(ignore);
        if (!compatibleTo(mods, ignore))
            throw new IncompatibilityException();
        Iterator iter = ignore.iterator();
        while (iter.hasNext())
            modules.remove(iter.next());
        Object[] res = new Object[mods.length];
        for (int i = 0; i < mods.length; i++)
            modules.put(mods[i], res[i] = instantiate(mods[i]));
        return res;
    }
    
    private Object instantiate(Module mod) throws ClassNotFoundException {
        Module[] contained = modules();
        Class[] intfs = new Class[contained.length];
        for (int i = 0; i < intfs.length; i++)
            intfs[i] = contained[i].accessorInterface();
        Object encl = Proxy.newProxyInstance(
            mod.asClass().getClassLoader(),
            intfs,
            handler);
        try {
            return mod.asClass()
                      .getConstructor(new Class[]{Object.class})
                      .newInstance(new Object[]{encl});
        } catch (Exception e) {
            return null;
        }
    }
    
    /** Returns a <code>HashMap</code> object which maps fully qualified
     *  module names to the corresponding implementations of this context.
     */
    public HashMap toHashMap() {
        HashMap map = new HashMap();
        Iterator iter = modules.keySet().iterator();
        while (iter.hasNext()) {
            Module key = (Module)iter.next();
            Module[] prev = key.getPreviousVersions();
            for (int i = 0; i < prev.length; i++)
                map.put(prev[i].getName(), modules.get(key));
        }
        return map;
    }
    
    /** Returns a string representation of this context.
     */
    public String toString() {
        return modules.toString();
    }
}
