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

package keris.reflect;

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


/**
 * Instances of the class <code>Module</code> represent modules and
 * module interfaces in a running Keris application similar to instances
 * of class <code>java.lang.Class</code> which represent regular classes.
 * 
 * <p> Like <code>Class</code>, <code>Module</code> has no public
 * constructors. Opposed to class <code>Class</code>, <code>Module</code>
 * instances do not get automatically created by the Keris runtime system.
 * Keris strongly separates the reflective model from the runtime system.
 * Therefore it is possible to use several reflective models simultaneously,
 * side by side.
 * 
 * <p> <code>Module</code> instances get created with three static methods:
 * <code>forName</code>, <code>forClass</code>, and <code>forImpl</code>
 * 
 * <p> There are several methods for retrieving information about the static
 * structure of a module. Furthermore, functionality for querying and
 * modifying dynamic aspects of concrete module instances is available.
 * The runtime configuration is expressed in form of a <code>Context</code>
 * object which maps <code>Module</code> instances to concrete module
 * implementations.
 *
 * @author   Matthias Zenger
 * @version  1.0, 25-Feb-2003
 * @see      keris.reflect.Context
 */
public final class Module {
    
    /** The corresponding Java class object.
     */
    private Class clazz;
    
    /** The parent module.
     */
    private Module parent;
    
    /** Is this module a refinement of 'parent'?
     */
    private boolean refinement;
    
    /** Is this module a specialization of 'parent'?
     */
    private boolean specialization;
    
    /** All implemented interfaces.
     */
    private Module[] interfaces;
    
    /** All required modules.
     */
    private Module[] required;
    
    /** All submodules.
     */
    private Module[] contained;
    
    private Module(Class clazz) {
        this.clazz = clazz;
        this.parent = null;
        try {
            this.parent = forClass(clazz.getSuperclass());
            refinement = hasField(clazz, "keris$refinement$");
            specialization = hasField(clazz, "keris$specialization$");
        } catch (ClassNotFoundException e) {
        }
    }
    
    /** Returns the fully qualified module name.
     */
    public String getName() {
        return clazz.getName();
    }
    
    /** Returns the modifier set.
     */
    public int getModifiers() {
        return clazz.getModifiers();
    }
    
    /** Returns the parent module.
     */
    public Module getParent() {
        return parent;
    }
    
    /** Returns an array of <code>Module</code> instances corresponding
     *  to all previous versions of this module.
     */
    public Module[] getPreviousVersions() {
        Set parents = new HashSet();
        getPreviousVersions(parents);
        return (Module[])parents.toArray(new Module[parents.size()]);
    }
    
    /** Add all <code>Module</code> instances corresponding
     *  to all previous versions of this module to a given set.
     *
     *  @param  parents  the set to which module instances should be added
     */
    public void getPreviousVersions(Set parents) {
        parents.add(this);
        if (!isInterface() || isRefinement()) {
            Module[] intfs = getInterfaces();
            for (int i = 0; i < intfs.length; i++)
                intfs[i].getPreviousVersions(parents);
        }
        if (!isInterface() && isRefinement() && (getParent() != null))
            getParent().getPreviousVersions(parents);
    }
    
    /** Returns all directly implemented interfaces.
     */
    public Module[] getInterfaces() {
        if (interfaces != null)
            return interfaces;
        Class[] cs = clazz.getInterfaces();
        Vector res = new Vector();
        for (int i = 0; i < cs.length; i++)
            try {
                res.add(forClass(cs[i]));
            } catch (ClassNotFoundException e) {
            }
        return interfaces = (Module[])res.toArray(new Module[res.size()]);
    }
    
    /** Returns the package in which this module is defined.
     */
    public Package getPackage() {
        return clazz.getPackage();
    }
    
    /** Returns all required modules.
     */
    public Module[] getRequired() throws ClassNotFoundException {
        if (required != null)
            return required;
        String[] required = new String[0];
        try {
            Field field = clazz.getDeclaredField("$requires");
            required = (String[])field.get(null);
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
        Module[] res = new Module[required.length];
        for (int i = 0; i < res.length; i++)
            res[i] = forName(required[i]);
        return this.required = res;
    }
    
    /** Returns all submodules.
     */
    public Module[] getSubmodules() throws ClassNotFoundException {
        if (contained != null)
            return contained;
        String[] required = new String[0];
        try {
            Field field = clazz.getDeclaredField("$contains");
            required = (String[])field.get(null);
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
        Module[] res = new Module[required.length];
        for (int i = 0; i < res.length; i++)
            res[i] = forName(required[i]);
        return contained = res;
    }
    
    /** Checks if the given module is required by this module.
     */
    public boolean isRequired(Module mod) {
        try {
            Module[] req = getRequired();
            for (int i = 0; i < req.length; i++)
                if (req[i].refinementOf(mod))
                    return true;
            return false;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
    
    /** Checks if the given module is a submodule of this module.
     */
    public boolean isSubmodule(Module mod) {
        try {
            Module[] req = getSubmodules();
            for (int i = 0; i < req.length; i++)
                if (req[i].refinementOf(mod))
                    return true;
            return false;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
    
    /** Does this instance represent a module interface?
     */
    public boolean isInterface() {
        return clazz.isInterface();
    }
    
    /** Does this module refine the parent module?
     */
    public boolean isRefinement() {
        return refinement;
    }
    
    /** Does this module specialize the parent module?
     */
    public boolean isSpecialization() {
        return specialization;
    }
    
    /** Return the corresponding <code>Class</code> object.
     */
    public Class asClass() {
        return clazz;
    }
    
    /** Is this module a refinement of the given module?
     */
    public boolean refinementOf(Module that) {
        if (that == null)
            throw new NullPointerException();
        else if (this == that)
            return true;
        else if (that.isInterface()) {
            if (isInterface() && isSpecialization())
                return false;
            Module[] intfs = getInterfaces();
            for (int i = 0; i < intfs.length; i++)
                if (intfs[i].refinementOf(that))
                    return true;
            return false;
        } else {
            Module thiz = this;
            while ((thiz != null) &&
                   (thiz != that) &&
                   thiz.isRefinement()) {
                thiz = thiz.getParent();
            }
            return thiz == that;
        }
    }
    
    /** Is this module a specialization of the given module?
     */
    public boolean specializationOf(Module that) {
        if (that == null)
            throw new NullPointerException();
        else if (this == that)
            return false;
        else if (that.isInterface()) {
            Module[] intfs = getInterfaces();
            if (isInterface() && isSpecialization()) {
                for (int i = 0; i < intfs.length; i++)
                    if (intfs[i].refinementOf(that))
                        return true;
                return false;
            }
            for (int i = 0; i < intfs.length; i++)
                if (intfs[i].specializationOf(that))
                    return true;
            return false;
        } else {
            Module thiz = this;
            while ((thiz != null) &&
                   (thiz != that) &&
                   thiz.isRefinement()) {
                thiz = thiz.getParent();
            }
            if ((thiz != null) && (thiz != that) && thiz.isSpecialization())
                return thiz.getParent().refinementOf(that);
            else
                return false;
        }
    }
    
    /** Does this module "overlap" with the given module?
     */
    public boolean differentFrom(Module that) {
        if (that == null)
            throw new NullPointerException();
        else if (this == that)
            return false;
        else if (that.isInterface()) {
            if (isInterface() && isSpecialization())
                return !that.refinementOf(this);
            Module[] intfs = getInterfaces();
            for (int i = 0; i < intfs.length; i++)
                if (!intfs[i].differentFrom(that))
                    return false;
            return true;
        } else {
            Module thiz = this;
            while ((thiz != null) &&
                   (thiz != that) &&
                   thiz.isRefinement()) {
                thiz = thiz.getParent();
            }
            return (thiz == null) || !that.refinementOf(thiz);
        }
    }
    
    /** Returns a string representation for this module.
     */
    public String toString() {
        return "module " + getName();
    }
    
    Class accessorInterface() throws ClassNotFoundException {
        String name = clazz.getName();
        int offset = name.lastIndexOf('.');
        if (offset < 0)
            name = "$" + name;
        else
            name = name.substring(0, offset) + ".$" +
                   name.substring(offset + 1);
        return Class.forName(name);
    }
    
    /** Configures the given module instance by resolving context
     *  dependencies and by creating instances for all submodules.
     */
    public static void configure(Object impl) {
        try {
            Module mod = forImpl(impl);
            mod.asClass()
               .getMethod("$configure", new Class[0])
               .invoke(impl, new Object[0]);
        } catch (Exception e) {
        }
    }
    
    /** Initializes the given module instance by configuring the
     *  module and executing its module initializers.
     */
    public static void init(Object impl) {
        try {
            impl.getClass()
                .getMethod("$access", new Class[0])
                .invoke(impl, new Object[0]);
        } catch (Exception e) {
        }
    }
    
    /** Invokes the given module instances <code>main(String[] args)</code>
     *  method and returns true if it was executed successfully.
     */
    public static boolean invoke(Object impl) {
        try {
            invoke(impl, new String[0]);
            return true;
        } catch (Exception e) {
            return false;   
        }
    }
    
    /** Invokes the given module instances <code>main(String[] args)</code>
     *  method with a given set of arguments.
     */
    public static void invoke(Object impl, String[] args)
        throws NoSuchMethodException, SecurityException,
               IllegalAccessException, InvocationTargetException {
        init(impl);
        impl.getClass()
            .getMethod("$main", new Class[]{String[].class})
            .invoke(impl, new Object[]{args});
    }
    
    /** Returns the configuration of a given module instance in form of a
     *  <code>Context</code> object.
     */
    public static Context getContext(Object impl) {
        try {
            HashMap required = (HashMap)
                impl.getClass()
                    .getMethod("$requires", new Class[0])
                    .invoke(impl, new Object[0]);
            HashMap contains = (HashMap)
                impl.getClass()
                    .getMethod("$contains", new Class[0])
                    .invoke(impl, new Object[0]);
            HashMap res = new HashMap();
            Iterator iter = required.keySet().iterator();
            while (iter.hasNext()) {
                String name = (String)iter.next();
                res.put(forName(name), required.get(name));
            }
            iter = contains.keySet().iterator();
            while (iter.hasNext()) {
                String name = (String)iter.next();
                res.put(forName(name), contains.get(name));
            }
            res.put(forClass(impl.getClass()), impl);
            return new Context(res);
        } catch (Exception e) {
            return null;
        }
    }
    
    /** Reconfigures a given module implementation with a given module
     *  context.
     */
    public static void setContext(Object impl, Context context)
        throws IncompatibilityException, ClassNotFoundException {
        Module mod = forImpl(impl);
        Module[] subs = mod.getSubmodules();
        for (int i = 0; i < subs.length; i++)
            if (!context.isImplemented(subs[i]))
                throw new IncompatibilityException(subs[i] + " missing");
        try {
            impl.getClass()
                .getMethod("$update$submodules", new Class[]{HashMap.class})
                .invoke(impl, new Object[]{context.toHashMap()});
        } catch (Exception e) {
            throw new IncompatibilityException();
        }
    }
    
    /** Reconfigures the given module instance and propagates the new
     *  setting to all submodules. This method can be used to hot swap
     *  a particular module in the context of the given module instance.
     */
    public static void propagateContext(Object impl, Context context)
        throws IncompatibilityException, ClassNotFoundException {
        setContext(impl, context);
        propagate(impl);
    }
    
    /** Propagate the context of a module to all submodules.
     */
    public static void propagate(Object impl)
        throws IncompatibilityException, ClassNotFoundException {
        Context context = getContext(impl);
        HashMap map = context.toHashMap();
        Module[] subs = forImpl(impl).getSubmodules();
        for (int i = 0; i < subs.length; i++)
            try {
                Object o = context.implementationOf(subs[i]);
                if (o != null) {
                    o.getClass()
                     .getMethod("$update$requirements", new Class[]{HashMap.class})
                     .invoke(o, new Object[]{map});
                    propagate(o);
                }
            } catch (Exception e) {
            }
    }
    
    /** Is this object a module instance?
     */
    public static boolean isImpl(Object impl) {
        return isModule(impl.getClass());
    }
    
    /** Does this <code>Class</code> object represent a Keris module?
     */
    public static boolean isModule(Class clazz) {
        return hasField(clazz, "$requires");
    }
    
    private static HashMap modules = new HashMap(31);
    
    /** Returns a <code>Module</code> object representing the module
     *  specified by a fully qualified module name.
     */
    public static Module forName(String moduleName) throws ClassNotFoundException {
        return forClass(Class.forName(moduleName));
    }
    
    /** Returns a <code>Module</code> object representing the module
     *  specified by a regular Java <code>Class</code> instance.
     */
    public static Module forClass(Class clazz) throws ClassNotFoundException {
        if (!isModule(clazz))
            throw new ClassNotFoundException(clazz + " does not refer to a module");
        Module res = (Module)modules.get(clazz);
        if (res == null)
            modules.put(clazz, res = new Module(clazz));
        return res;
    }
    
    /** Returns a <code>Module</code> object for the given module
     *  implementation.
     */
    public static Module forImpl(Object o) throws ClassNotFoundException {
        return forClass(o.getClass());
    }
    
    private static boolean hasField(Class clazz, String fieldname) {
        try {
            return clazz.getDeclaredField(fieldname) != null;
        } catch (NoSuchFieldException e) {
            return false;
        } catch (SecurityException e) {
            return false;
        }
    }
}
