All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.engine.modelling.resolver.ResolutionScope Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (C) 2007, 2015:
 * 
 * - Ferdinando Villa  - integratedmodelling.org - any
 * other authors listed in @author annotations
 *
 * All rights reserved. This file is part of the k.LAB software suite, meant to enable
 * modular, collaborative, integrated development of interoperable data and model
 * components. For details, see http://integratedmodelling.org.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms
 * of the Affero General Public License Version 3 or any later version.
 *
 * This program is distributed in the hope that it will be useful, but without any
 * warranty; without even the implied warranty of merchantability or fitness for a
 * particular purpose. See the Affero General Public License for more details.
 * 
 * You should have received a copy of the Affero General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite
 * 330, Boston, MA 02111-1307, USA. The license is also available at:
 * https://www.gnu.org/licenses/agpl.html
 *******************************************************************************/
package org.integratedmodelling.engine.modelling.resolver;

import java.text.NumberFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IExpression;
import org.integratedmodelling.api.knowledge.IKnowledge;
import org.integratedmodelling.api.knowledge.IProperty;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.metadata.IModelMetadata;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.ICoverage;
import org.integratedmodelling.api.modelling.IDataSource;
import org.integratedmodelling.api.modelling.IDependency;
import org.integratedmodelling.api.modelling.IDerivedObserver;
import org.integratedmodelling.api.modelling.IDirectObservation;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.IModelBean;
import org.integratedmodelling.api.modelling.IModelObject;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.IObjectSource;
import org.integratedmodelling.api.modelling.IObservableSemantics;
import org.integratedmodelling.api.modelling.IObserver;
import org.integratedmodelling.api.modelling.IPresenceObserver;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.ISubject;
import org.integratedmodelling.api.modelling.contextualization.IContextualizer;
import org.integratedmodelling.api.modelling.resolution.IModelPrioritizer;
import org.integratedmodelling.api.modelling.resolution.IResolution;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.modelling.scheduling.ITransition;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.monitoring.Messages;
import org.integratedmodelling.api.project.IProject;
import org.integratedmodelling.api.provenance.IProvenance;
import org.integratedmodelling.api.provenance.IProvenance.Action;
import org.integratedmodelling.api.time.ITemporalExtent;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.interfaces.NetworkDeserializable;
import org.integratedmodelling.common.interfaces.NetworkSerializable;
import org.integratedmodelling.common.interfaces.actuators.IDirectActuator;
import org.integratedmodelling.common.kim.KIMModel;
import org.integratedmodelling.common.kim.KIMNamespace;
import org.integratedmodelling.common.metadata.Metadata;
import org.integratedmodelling.common.model.Coverage;
import org.integratedmodelling.common.owl.Knowledge;
import org.integratedmodelling.common.utils.Dummy;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.common.vocabulary.ObservableSemantics;
import org.integratedmodelling.common.vocabulary.Traits;
import org.integratedmodelling.engine.modelling.kbox.ModelKbox;
import org.integratedmodelling.engine.modelling.resolver.ResolutionGraph.DependencyEdge;
import org.integratedmodelling.engine.modelling.resolver.ResolutionGraph.ProvenanceNode;
import org.integratedmodelling.engine.modelling.runtime.DirectObservation;
import org.integratedmodelling.engine.modelling.runtime.Scale;
import org.integratedmodelling.engine.modelling.runtime.Subject;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabInternalRuntimeException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.integratedmodelling.lang.LogicalConnector;

/*
 * resolution context of object X contains a scale, the link X will be target of in the
 * provenance diagram, and optionally the semantic context of the resolution, i.e. the
 * property that represents the link and if that isn't null, the concept it links to in
 * that capacity. TODO rename all the methods that return sub-contexts to something
 * sensible and address any redundancy or stupidity in them.
 */
public class ResolutionScope
        implements IResolutionScope, NetworkSerializable, NetworkDeserializable {

    enum Type {
        ROOT,
        SUBJECT,
        OBSERVABLE,
        MODEL,
        OBSERVER,
        DEPENDENCY,
        CONDITIONAL_DEPENDENCY,
        DATASOURCE,
        OBJECTSOURCE,
        GROUP,
        CONCEPT
    };

    Type                                  type;
    IActiveDirectObservation              subject;
    IObservableSemantics                  observable;
    IProperty                             property;
    IObserver                             observer;
    IModel                                model;
    IDataSource                           datasource;
    IObjectSource                         objectsource;
    IActiveSubject                        contextSubject;
    IDependency                           dependency;
    IMonitor                              monitor;
    // if true, we give the user control of editable parameters and optional
    // outputs.
    boolean                               interactive;

    // if true, this is the scope of resolution of an abstract concept...
    boolean                               forConcept                = false;
    // which will be set in this field:
    IConcept                              abstractConcept           = null;
    /*
     * used to compile the provenance graph after successful resolution; not passed
     * around.
     */
    IProvenance.Action                    cause;

    // this only exists for a context handling a direct observable.
    IDirectActuator                    actuator;
    // TODO should probably substitute the above
    IContextualizer                       contextualizer;

    // the scale we keep harmonizing as we resolve; exclude time given that we
    // resolve for
    // initialization
    IScale                                scale;

    // the time scale, if any, so we can tune the model retrieval for currency
    // and
    // coverage.
    ITemporalExtent                       time;

    // the link we will use to link to root if resolved
    DependencyEdge                        link;
    // the node we get for our target when resolution is accepted by the
    // resolver, and the
    // root node for
    // our children contexts.

    ProvenanceNode                        node;
    // provenance graph that gets built along the resolution chain, passed
    // around to
    // children
    ResolutionGraph                       resolutionGraph;

    // TODO fill these in
    Collection                    nsWhitelist               = null;
    Collection                    nsBlacklist               = null;

    // scenarios are defined externally on the root context and passed around
    // unmodified
    HashSet                       scenarios                 = null;

    // attributes extracted from the context, used when the kbox is consulted
    // for ranking
    // of options.
    Set                         attributes                = null;

    // the namespace of reference for the context (the one we're resolving into
    // at the
    // moment - model that has dependencies or root direct observer).
    private INamespace                    resolutionNamespace;

    // these are not dealt with directly, but the resolver may add metadata, in
    // which
    // case they're passed on to the provenance nodes when they're built.
    IMetadata                             metadata                  = null;

    /*
     * this should only be true when we're resolving a conditional branch. It will color
     * all nodes as conditional unless they have been seen in an unconditional branch too.
     */
    boolean                               isOptional;

    /**
     * If this is true, the context has been created by an explicit observation action
     * from the outside of the context, and it should not propagate the model to its
     * parent.
     */
    boolean                               isDirect                  = false;

    /*
     * A mismatch between attribute semantics may make a context unsatisfiable, which
     * prunes a resolution subtree instantly.
     */
    boolean                               isUnsatisfiable           = false;

    /*
     * connector for multiple coverage merges in resolution of dependencies. Default is
     * AND, but some contexts (namely conditional dependencies) will use OR.
     */
    LogicalConnector                      connector                 = LogicalConnector.INTERSECTION;

    /*
     * model cache, passed around; holds node->model correspondences as they're accepted.
     * We hold all the resolved observables with the respective model and coverage.
     */
    HashMap models;

    /**
     * Here we keep those direct observables for which we could not find a model, but we
     * can still resolve.
     */
    HashSet         noModelsFor;

    /*
     * model cache, as before but separate for instantiators of direct observables (the
     * observable is the same, so we must keep these separate).
     */
    HashMap instantiatorModels;

    /*
     * coverage cache for models. We just pass this around.
     */
    HashMap            coverages;

    /*
     * overall coverage, updated by the resolver after each step.
     */
    ICoverage                             coverage;

    /*
     * our parent node which will accept() us if the merged coverage is sufficient.
     */
    ResolutionScope                       parent;

    /*
     * if we're representing a conditional dependency, the condition that comes with it
     * (may be null even for a conditional dependency).
     */
    IExpression                           condition;

    /*
     * if we're representing a conditional dependency, the order of definition of the
     * condition.
     */
    int                                   conditionIndex            = -1;

    /*
     * cache of model IDs being resolved to avoid infinite loops when a model matches its
     * own observable.
     */
    HashSet                 resolving                 = new HashSet<>();

    /*
     * we may be resolving during a transition. For now this is only in for semantic
     * suppor but always null - will be used later.
     */
    ITransition                           transition;

    /**
     * If true, we're looking to observe an observable for an already instantiated direct
     * observation, as opposed to observing the direct observable in a context. Any
     * matching model found down the resolution chain must be of the explanatory kind -
     * i.e. not an instantiator ('each').
     */
    boolean                               isExplanatoryModel        = false;

    /**
     * If not null, we're resolving a dependency with the 'as ' clause, which
     * requires us to merge this trait with the observable of any observation made under
     * this branch.
     */
    IConcept                              interpretAs               = null;

    IModelPrioritizer     prioritizer               = null;

    /**
     * This collects all the dependencies that are used, so that contextualizers may
     * inquire if an optional output needs to be produced.
     */
    Set             usedDependencies          = new HashSet<>();

    // next two are for indirect resolution
    private IDerivedObserver              indirectObserver;
    IContextualizer                       alternativeContextualizer = null;

    // dependencies for non-abstract countables that force abstract resolution
    boolean                               forceGeneric;

    /**
     * Calling this from API before initializing the contextualizer tells it to produce
     * this output even if it's optional.
     * 
     * @param observable
     */
    public void requireOutput(IObservableSemantics observable) {
        usedDependencies.add(observable);
    }

    /*
     * ONLY for "child" contexts - automatically sets the parent context and prepares a
     * new provenance graph, to be merged with the parent's at finish() if coverage is
     * sufficient. Copies all common info and leave everything else undefined. Constructor
     * using this must set the link and add the provenance node as the context's node,
     * then link it as necessary. Each context can only have one root node.
     */
    private ResolutionScope(ResolutionScope ctx, Type type,
            IActiveDirectObservation subject) {

        // CallTracer.indent("" + type);

        this.subject = subject;

        this.models = new HashMap<>(ctx.models);
        this.noModelsFor = new HashSet<>(ctx.noModelsFor);
        this.instantiatorModels = new HashMap<>(ctx.instantiatorModels);

        if (ctx.attributes != null) {
            this.attributes = new HashSet<>(ctx.attributes);
        }

        this.usedDependencies.addAll(ctx.usedDependencies);

        this.resolutionGraph = new ResolutionGraph(ctx.monitor);
        this.scenarios = ctx.scenarios;
        this.nsWhitelist = ctx.nsWhitelist;
        this.nsBlacklist = ctx.nsBlacklist;
        this.scale = ctx.scale;
        this.monitor = ctx.monitor;
        this.resolutionNamespace = ctx.resolutionNamespace;
        // this.tasks = ctx.tasks;
        this.coverages = ctx.coverages;
        this.isOptional = ctx.isOptional;
        this.resolving.addAll(ctx.resolving);
        this.time = ctx.time;
        this.isExplanatoryModel = ctx.isExplanatoryModel;
        this.transition = ctx.transition;
        this.indirectObserver = ctx.indirectObserver;

        // the model and context subject stay unless redefined later
        this.model = ctx.model;
        this.parent = ctx;
        this.type = type;
        this.contextSubject = ctx.contextSubject;
        this.interpretAs = ctx.interpretAs;
        this.abstractConcept = ctx.abstractConcept;
        this.forConcept = ctx.forConcept;
        this.forceGeneric = ctx.forceGeneric;

        //
        // this.resolutionStrategy = new ResolutionStrategy(this.subject);
    }

    /**
     * Call after resolution of this context to merge in results into the parent context.
     * If the merged coverage is acceptable, the parent's accept() is called with this
     * context as an argument.
     * 
     * @param coverage
     * @return the merged coverage of this context. Never null.
     * @throws KlabException
     */
    public ICoverage finish(ICoverage coverage) throws KlabException {
        this.coverage = coverage;
        return finish();
    }

    /**
     * Call after resolution of this context to merge in results into the father context.
     * If the merged coverage is acceptable, merge provenance and model cache and link the
     * root node to the father's.
     *
     * @return finished coverage
     * @throws KlabException
     */
    public ICoverage finish() throws KlabException {

        if (coverage == null) {

            // we're a group with no members or something that hasn't accepted
            // or resolved
            // anything.
            // Also happens for previous model observers. Needs checking.
            boolean startWithFullCoverage = type == Type.MODEL || type == Type.SUBJECT
                    || type == Type.OBSERVER
                    || (type == Type.DEPENDENCY
                            && NS.isThing(dependency.getObservable()));

            coverage = startWithFullCoverage ? new Coverage(scale, 1.0) : Coverage.EMPTY;
        }

        if (coverage.isEmpty()) {
            // an empty optional dependency is OK, doesn't alter the parent's
            // coverage
            if (isOptional) {
                if (dependency != null) {
                    monitor.warn("optional dependency on "
                            + dependency.getObservable().getType()
                            + " is unsatisfied");
                }
                // CallTracer.unIndent();
                return coverage;
            }
            // CallTracer.unIndent();
            return coverage;
        }

        // CallTracer.unIndent();
        return parent.accept(this);

    }

    /**
     * Public empty constructor is only for internal use.
     */
    public ResolutionScope() {
        // only for the deserializer
        /*
         * make up a fake namespace (with a name that won't conflict and the passed
         * resolution criteria)
         */
        this.resolutionNamespace = new KIMNamespace(ModelKbox.DUMMY_NAMESPACE_ID);
        this.noModelsFor = new HashSet<>();
        this.scenarios = new HashSet<>();
    }

    /**
     * Root resolution context. All others should be created using public methods.
     * 
     * @param subject
     * @param scenarios
     * @return root scope for subject
     */
    public static ResolutionScope root(IActiveSubject subject,
            /*
             * FIXME no need for this
             */IActiveSubject contextSubject, IMonitor monitor, Collection scenarios) {
        ResolutionScope ret = new ResolutionScope(subject, contextSubject, monitor, scenarios);
        ((Subject) subject).setPrimary(true);
        ret.type = Type.ROOT;
        return ret;
    }

    public ResolutionScope(IDirectObservation subject, IActiveSubject contextSubject,
            IMonitor monitor,
            Collection scenarios) {
        this.models = new HashMap<>();
        this.instantiatorModels = new HashMap<>();
        this.coverages = new HashMap<>();
        this.noModelsFor = new HashSet<>();
        this.monitor = monitor;
        this.contextSubject = contextSubject;
        this.scenarios = new HashSet<>();
        if (scenarios != null) {
            for (String s : scenarios) {
                this.scenarios.add(s);
            }
        }
        this.resolutionGraph = new ResolutionGraph(monitor);
        // this.resolutionStrategy = new ResolutionStrategy(subject);
    }

    @Override
    public void deserialize(IModelBean object) {

        if (!(object instanceof org.integratedmodelling.common.beans.Scope)) {
            throw new KlabInternalRuntimeException("cannot adapt a "
                    + object.getClass().getCanonicalName()
                    + " to a resolution scope");
        }

        org.integratedmodelling.common.beans.Scope bean = (org.integratedmodelling.common.beans.Scope) object;

        this.scale = KLAB.MFACTORY.adapt(bean.getScale(), Scale.class);
        this.observable = KLAB.MFACTORY
                .adapt(bean.getObservable(), ObservableSemantics.class);
        ((KIMNamespace) this.resolutionNamespace)
                .setResolutionCriteria(KLAB.MFACTORY
                        .adapt(bean.getResolutionCriteria(), Metadata.class));
        this.subject = Dummy.subject(Knowledge
                .parse(bean.getSubjectType()), subject == null ? null : ((DirectObservation) subject)
                        .getContext(), scale, resolutionNamespace);
        this.isExplanatoryModel = bean.isExplanatory();
        if (bean.getTraits().size() > 0) {
            this.attributes = new HashSet<>();
            for (String trait : bean.getTraits()) {
                this.attributes.add(Knowledge.parse(trait));
            }
        }
        for (String s : bean.getScenarios()) {
            this.scenarios.add(s);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public  T serialize(Class desiredClass) {

        if (!desiredClass
                .isAssignableFrom(org.integratedmodelling.common.beans.Scope.class)) {
            throw new KlabInternalRuntimeException("cannot adapt a resolution scope to "
                    + desiredClass.getCanonicalName());
        }
        org.integratedmodelling.common.beans.Scope ret = new org.integratedmodelling.common.beans.Scope();

        ret.setExplanatory(isExplanatoryModel);
        ret.setSubjectType(((Knowledge) subject.getType()).asText());
        ret.setScale(KLAB.MFACTORY
                .adapt(getScale(), org.integratedmodelling.common.beans.Scale.class));
        ret.setObservable(KLAB.MFACTORY
                .adapt(getObservable(), org.integratedmodelling.common.beans.Observable.class));
        if (scenarios != null) {
            ret.getScenarios().addAll(scenarios);
        }
        if (attributes != null) {
            for (IConcept c : attributes) {
                ret.getTraits().add(((Knowledge) c).asText());
            }
        }

        if (prioritizer != null) {
            ret.setResolutionCriteria(new org.integratedmodelling.common.beans.Metadata(((ModelPrioritizer) prioritizer)
                    .getCriteria()));
        }
        return (T) ret;
    }

    /**
     * Get the closure of all possible observables for the passed observable in this
     * context.
     *
     * @param observable
     * @return closure
     * @throws KlabException
     */
    public Set getObservableClosure(IObservableSemantics observable)
            throws KlabException {

        Set observables = new HashSet<>();
        if (this.observable != null && this.property != null
                && this.observable.getType() instanceof IConcept) {
            for (IConcept cc : this.observable.getType()
                    .getPropertyRange(this.property)) {
                observables.add(cc);
            }
        } else {
            observables.add(observable.getType());
        }

        if (observables.size() == 0) {
            observables.add(observable.getType());
        }

        return observables;
    }

    @Override
    public IScale getScale() {
        return scale;
    }

    @Override
    public Collection getScenarios() {
        return scenarios;
    }

    public IObservableSemantics getObservable() {
        return observable;
    }

    @Override
    public IModel getModel() {
        return model;
    }

    @Override
    public INamespace getResolutionNamespace() {
        return resolutionNamespace;
    }

    @Override
    public IActiveDirectObservation getSubject() {
        return subject;
    }

    /**
     * The property linking the current object being resolved to the subject it's inherent
     * to, or null if we're resolving a root context.
     * 
     * @return property context
     */
    public IProperty getPropertyContext() {
        return property;
    }

    /**
     * Called on the context when an accessor redefines the scale for a subject. This
     * should set the scale to the passed one, and if necessary use the monitor to
     * complain of any incompatibilities.
     * 
     * @param scale
     */
    public void forceScale(IScale scale) {
        this.scale = scale;
    }

    @Override
    public IResolution getResolutionGraph() {
        return resolutionGraph;
    }

    @Override
    public ICoverage getCoverage() {
        return coverage;
    }

    /**
     * If the context is for a subject that is in the context of another, return the
     * other, otherwise return null.
     * 
     * @return subject
     */
    public ISubject getContextSubject() {
        return contextSubject;
    }

    @Override
    public void setMetadata(String key, Object value) {
        if (metadata == null) {
            metadata = new Metadata();
        }
        metadata.put(key, value);
    }

    public ResolutionScope forObservable(IObservableSemantics observable) {

        ResolutionScope ret = new ResolutionScope(this, Type.OBSERVABLE, subject);
        ret.observable = observable;
        ret.attributes = mergeTraits(this.attributes, observable);
        ret.model = null;

        if (NS.isQuality(observable) || NS.isTrait(observable)) {
            // force explanation, which may be false from upstream context.
            ret.isExplanatoryModel = true;
        }

        // trace(ret);

        return ret;
    }

    public ResolutionScope forObservable(IObservableSemantics observable, Collection scenarios) {

        ResolutionScope ret = new ResolutionScope(this, Type.OBSERVABLE, subject);
        ret.observable = observable;
        if (scenarios != null && scenarios.size() > 0) {
            ret.scenarios.clear();
            ret.scenarios.addAll(scenarios);
        }
        ret.model = null;
        ret.attributes = mergeTraits(this.attributes, observable);
        // needs this as it's an entry point for the resolver and the subject
        // wasn't
        // known.
        // ret.resolutionStrategy = new ResolutionStrategy(subject);

        if (NS.isQuality(observable) || NS.isTrait(observable)) {
            // force explanation, which may be false from upstream context.
            ret.isExplanatoryModel = true;
        }

        // trace(ret);

        return ret;
    }
    
    public void setInstantiatingContext(boolean b) {
        this.isExplanatoryModel = !b;
    }

    /*
     * add any traits that weren't there already, update those that were there. If any
     * traits are incompatible, set the context to unsatisfiable.
     */
    private Set mergeTraits(Set oldt, IObservableSemantics observable) {

        Set ret = new HashSet<>();
        try {

            Pair> ast = Traits
                    .separateAttributes(observable.getType());
            for (IConcept attribute : ast.getSecond()) {
                IConcept baset = NS.getBaseParentTrait(attribute);
                if (oldt != null) {
                    for (IConcept c : oldt) {
                        if (!c.equals(attribute) && c.is(baset)) {
                            isUnsatisfiable = true;
                            return ret;
                        }
                    }
                }
                /*
                 * it gets here only if oldt didn't have it or had exactly the same.
                 */
                ret.add(attribute);
            }

            /*
             * anything not added that was in oldt is safe for addition at this point
             */
            if (oldt == null) {
                return ret.isEmpty() ? null : ret;
            }
            ret.addAll(oldt);

        } catch (KlabValidationException e) {

            // better empty than wrong
            ret.clear();
        }

        return ret;
    }

    public ResolutionScope forDependency(IDependency dependency, INamespace namespace) {
        ResolutionScope ret = new ResolutionScope(this, Type.DEPENDENCY, subject);
        ret.model = this.model;
        ret.observer = this.observer;
        ret.datasource = this.datasource;
        ret.dependency = dependency;
        ret.property = dependency.getProperty();
        ret.isOptional = this.isOptional || dependency.isOptional();
        ret.interpretAs = dependency.reinterpretAs();
        ret.resolutionNamespace = namespace;
        ret.forceGeneric = dependency.isGeneric();

        return ret;
    }

    private boolean isObserverContext() {
        if (this.type.equals(Type.OBSERVER))
            return true;
        if (this.type.equals(Type.MODEL) || this.type.equals(Type.DEPENDENCY))
            return false;
        return parent == null ? false : parent.isObserverContext();
    }

    public ResolutionScope forModel(IModel model) {

        ResolutionScope ret = new ResolutionScope(this, Type.MODEL, subject);
        ret.model = model;
        ret.resolutionNamespace = model.getNamespace();
        ret.node = ret.resolutionGraph.getNode(model);
        ret.attributes = this.attributes == null ? null
                : mergeTraits(this.attributes, model.getObservable());
        ret.resolving.add(model);

        // /*
        // * get the actuator here so we can pass it to the resolver when
        // looking
        // * up dependencies and give it a chance to define contextual
        // * dependencies. FIXME revise this - currently another actuator is
        // * created by ResolutionStrategy. Ideally Dataflow and
        // * ResolutionStrategy should merge and only one actuator should be run
        // * in different steps.
        // */
        // if (NS.isDirect(model)) {
        // ret.actuator = getDirectActuator(this, model);
        // }

        // if data model, link to parent is "provides"; otherwise we are an
        // entry point
        // and we don't want a
        // link. Dependency must be completed upstream by adding formal name and
        // conditions if any.
        if (model.getObserver() != null) {
            ret.link = new DependencyEdge(isObserverContext()
                    ? DependencyEdge.Type.RESOLVES
                    : DependencyEdge.Type.DEPENDENCY, "", model.getObservable());
        }

        // trace(ret);

        return ret;
    }

    public ResolutionScope forConcept(IConcept concept) {

        ResolutionScope ret = new ResolutionScope(this, Type.CONCEPT, subject);
        ret.abstractConcept = concept;
        ret.forConcept = true;
        ret.node = ret.resolutionGraph.getNode(concept);
        return ret;
    }

    public ResolutionScope forSubject(IActiveDirectObservation subject, Collection scenarios) {
        ResolutionScope ret = forSubject(subject);
        ret.isDirect = true;
        if (scenarios != null && scenarios.size() > 0) {
            ret.scenarios.clear();
            ret.scenarios.addAll(scenarios);
        }
        /*
         * TODO insert all pre-resolved observables.
         */
        return ret;
    }

    public ResolutionScope forSubject(IActiveDirectObservation subject) {

        ResolutionScope ret = new ResolutionScope(this, Type.SUBJECT, subject);

        // null in root context
        ret.contextSubject = (IActiveSubject) this.subject;
        ret.scale = subject.getScale().getSubscale(KLAB.c(NS.TIME_DOMAIN), 0);

        ret.time = subject.getScale().getTime();
        // TODO check the namespace that gets here
        ret.resolutionNamespace = subject.getNamespace();
        ret.attributes = mergeTraits(this.attributes, subject.getObservable()
                .getSemantics());
        // ret.resolutionStrategy.subject = (Subject) subject;
        // ret.resolutionStrategy.contextSubject = ret.contextSubject;

        // whatever is in there, we reuse; use the observer's observable, which
        // is what
        // was
        // matched, not the dependency's. Use the explanatory model cache, not
        // the
        // instantiators.
        for (IState s : subject.getStates()) {
            ret.models.put(s.getObserver()
                    .getObservable(), new StateModel(s.getObservable()
                            .getSemantics(), s, subject.getNamespace()));
        }

        /*
         * FIXME/TODO this needs to use the CONTEXT model states also, knowing they may
         * have different scale so using a scale mediator model initialized with the
         * state.
         */
        // no node - we use the subject model as the entry point for the
        // workflow.

        // trace(ret);

        return ret;
    }

    public ResolutionScope forObserver(IObserver observer, IDataSource modelDatasource) {
        ResolutionScope ret = new ResolutionScope(this, Type.OBSERVER, subject);
        ret.model = this.model;
        ret.observer = observer;
        ret.datasource = modelDatasource;
        ret.node = ret.resolutionGraph.getNode(observer);
        ret.attributes = mergeTraits(this.attributes, observer.getObservable());

        /*
         * FIXME the following is no longer true - observers such as distance have no
         * mediated or datasource but may have coverage. For now just adapted the Workflow
         * to force-compile in any computed observer, but coverage may be inaccurate.
         */

        // no need for coverage, observer always has a datasource or a mediated
        // observer.
        // observers define the state of models.
        ret.link = new DependencyEdge(DependencyEdge.Type.DEFINE_STATE, "", observer
                .getObservable());

        // trace(ret);

        return ret;
    }

    public ResolutionScope forDatasource(IDataSource datasource) throws KlabException {

        ResolutionScope ret = new ResolutionScope(this, Type.DATASOURCE, subject);
        ret.datasource = datasource;
        ret.model = this.model;
        ret.observer = this.observer;

        ret.node = ret.resolutionGraph.getNode(datasource);
        // non-conditional node with no further resolution, so it doesn't go
        // through
        // finish() and
        // gets added here.
        ret.resolutionGraph.add(ret.node);

        // datasources are interpreted by observers.
        ret.link = new DependencyEdge(DependencyEdge.Type.INTERPRET_AS, "", observer
                .getObservable());

        // initial coverage is the intersection of the datasource with the
        // scale. We use
        // the model
        // to capture any other restriction from the model or the namespace.
        ret.coverage = new Coverage(scale, 1.0);
        IScale modelScale = model.getCoverage(monitor);
        ret.coverage = ret.coverage.and(new Coverage(modelScale));

        /*
         * if the observer is a presence, any coverage > 0 will be OK FIXME check this
         * condition - should probably be anything that expresses an inherent quality 'of'
         * something that is not the context itself and is countable
         */
        if (ret.coverage.getCoverage() > 0 && !ret.coverage.isRelevant()
                && ret.observer instanceof IPresenceObserver) {
            ((Coverage) ret.coverage).forceRelevant();
        }

        // boolean wasthere = false;
        // if (((Monitor) monitor).getSpatialDisplay() == null) {
        // ((Monitor) monitor).setSpatialDisplay((SpaceExtent) ((Coverage)
        // ret.coverage)
        // .getOriginalExtent(Env.c(NS.SPACE_DOMAIN)));
        // } else {
        // wasthere = true;
        // }
        // ((Monitor) monitor).getSpatialDisplay()
        // .add(((SpaceExtent) ((Coverage)
        // ret.coverage).getCurrentExtent(Env.c(NS.SPACE_DOMAIN)))
        // .getShape());
        //
        // if (!wasthere) {
        // ((Monitor) monitor).getSpatialDisplay().show();
        // }
        // trace(ret);

        return ret;
    }

    public ResolutionScope forObjectSource(IObjectSource objectsource)
            throws KlabException {

        final ResolutionScope ret = new ResolutionScope(this, Type.OBJECTSOURCE, subject);
        ret.objectsource = objectsource;
        ret.model = model;
        ret.observer = observer;

        ret.node = ret.resolutionGraph.getObjectSourceNode(objectsource);
        ret.node.model = model;

        // non-conditional node with no further resolution, so it doesn't go
        // through
        // finish() and
        // gets added here.
        ret.resolutionGraph.add(ret.node);

        // coverage is the intersection of the datasource with the scale. We use
        // the model
        // to capture
        // any other restrictions from the model or the namespace. Use the 'forObjects'
        // flag to ensure we don't throw away small objects.
        ret.coverage = new Coverage(scale, true);
        IScale osCoverage = model.getCoverage(monitor);
        ret.coverage = ret.coverage.and(new Coverage(osCoverage, true));

        // trace(ret);

        return ret;
    }

    public ResolutionScope forMediatedObserver(IObserver observer) {
        ResolutionScope ret = new ResolutionScope(this, Type.OBSERVER, subject);
        ret.model = model;
        ret.observer = observer;
        ret.datasource = datasource;
        ret.link = new DependencyEdge(DependencyEdge.Type.MEDIATE_TO, "", observer
                .getObservable());
        ret.observable = observer.getObservable();
        ret.node = ret.resolutionGraph.getNode(observer);

        // trace(ret);

        return ret;
    }

    public ResolutionScope forCondition(IExpression expression, int n) {

        /*
         * FIXME - this should merely color the downstream dependency link with a
         * conditional flavor and pass the expression and index, otherwise works like a
         * dependency, so it should have no link and node. and the condition/index should
         * be in the context.
         */
        ResolutionScope ret = new ResolutionScope(this, Type.CONDITIONAL_DEPENDENCY, subject);
        ret.model = model;
        ret.observer = observer;
        ret.condition = expression;
        ret.conditionIndex = n;

        // trace(ret);

        return ret;
    }

    /**
     * Serves as the joining context for a group of dependencies or conditional observers,
     * linking the resolved ones to the parent context's node. The group coverage may be
     * in AND or in OR according to the calling context; if OR, additional coverage rather
     * than total coverage is the base for an additional acceptance choice within
     * accept().
     *
     * @param connector
     * @return group scope
     */
    public ResolutionScope forGroup(LogicalConnector connector) {
        ResolutionScope ret = new ResolutionScope(this, Type.GROUP, subject);
        ret.connector = connector;
        ret.coverage = (connector.equals(LogicalConnector.INTERSECTION)
                ? new Coverage(ret.scale, 1.0)
                : Coverage.EMPTY);

        // act as a worker for the parent, so take its node to link to each
        // child that
        // satisfies
        // the rule for group inclusion.
        ret.node = this.node;

        // will use the child link to the parent node; we have no link of our
        // own, but
        // will link
        // the parent's to the child's at each accept()

        // trace(ret);

        return ret;
    }

    public boolean hasNoModel(IObservableSemantics observable) {
        return noModelsFor.contains(observable);
    }

    public void proceedWithNoModel(IObservableSemantics observable) {
        noModelsFor.add(observable);
    }

    /**
     * If we have resolved this observable before, or can resolve it based on pre-existing
     * states of our subject, link it up and return the resulting coverage. Otherwise
     * return null.
     * 
     * @param observable
     * @param matchInstantiators
     *            if true, search for models that will create instances of the (direct)
     *            observable; otherwise find models that will observe a pre-existing
     *            instance.
     * @return model
     */
    public IModel getModelFor(IObservableSemantics observable, boolean matchInstantiators) {

        /*
         * TODO/CHECK this will not match, e.g., an indirect observation with a
         * measurement - which may be a problem although usually only for manually
         * observed concepts.
         */
        return matchInstantiators ? instantiatorModels.get(observable)
                : models.get(observable);

    }

    /**
     * Called from within the finish() method of a sub-context when the sub-context has
     * enough coverage to be useful. Result will depend on what is being resolved. May be
     * called multiple times. It is always called with context arguments that have been
     * returned by one of the forXXX methods on this. In situations where accept() may be
     * called multiple times, the coverage.isSufficient() method will decide whether it is
     * called again. Must merge provenance and model cache from the passed context and
     * link our root node to the child. Must also harmonize the common scale being used
     * across resolution with that, if any, of the object just resolved.
     * 
     * @param chld
     *            a context that was generated by one of the forXXX() methods.
     * @return the merged coverage after acceptance.
     * @throws KlabException
     */
    public ICoverage accept(IResolutionScope chld) throws KlabException {

        ResolutionScope child = (ResolutionScope) chld;
        boolean noOp = false;
        boolean linkToParentSubject = this.type == Type.MODEL
                && NS.isDirect(this.model.getObservable());
        ProvenanceNode source = linkToParentSubject ? getParentNode() : this.node;
        ProvenanceNode target = child.node;
        DependencyEdge dlink = linkToParentSubject
                ? new DependencyEdge(DependencyEdge.Type.DEPENDENCY, "", child.observable)
                : child.link;

        if (this.indirectObserver != null && child.alternativeContextualizer != null) {
            resolutionGraph.merge(child.resolutionGraph);
            if (this.indirectObserver
                    .acceptAlternativeContextualizer(alternativeContextualizer, child.coverage).isComplete()) {
                return Coverage.FULL(scale);
            }
            return child.coverage;
        }

        if (child.type == Type.DEPENDENCY) {
            /*
             * float any roles or traits given to the dependency to the model requesting
             * it.s
             */
            if (child.interpretAs != null) {
                ((KIMModel) this.model).notifyRole(child.dependency.getObservable(), child.interpretAs);
            }
        }

        /*
         * determine coverage
         */
        if (type.equals(Type.OBSERVABLE)) {

            /*
             * child can only be a model
             */
            if (coverage == null) {

                coverage = child.coverage;

                /*
                 * model may be null in subjects, which have a right to exist without one.
                 */
                if (child.model != null) {

                    // may have been seen before
                    if (!getModelCache(isExplanatoryModel).containsValue(child.model)) {

                        /*
                         * don't bore the adorable user with something they will ask you
                         * about every time they see it and never understand once.
                         */
                        if (!(child.model instanceof StateModel)) {
                            monitor.info(child.model.getName() + " satisfies "
                                    + NumberFormat.getPercentInstance()
                                            .format(coverage.getCoverage())
                                    + " of "
                                    + this.observable.getType() + " in "
                                    + subject.getName(), Messages.INFOCLASS_MODEL);
                        }
                        /*
                         * store in cache
                         */
                        getModelCache(isExplanatoryModel)
                                .put(this.observable, child.model);
                        coverages.put(child.model, coverage);

                        /*
                         * ...along with any other observables explained.
                         */
                        if (isExplanatoryModel) {
                            for (int i = 1; i < child.model.getObservables()
                                    .size(); i++) {
                                getModelCache(isExplanatoryModel)
                                        .put(child.model.getObservables().get(i), child.model);
                            }
                        }
                    }
                }

            } else {

                /*
                 * the child is a resolved model. Check if it adds enough coverage to be
                 * useful; if not, do nothing.
                 */
                double tcov = coverage.getCoverage();
                ICoverage cv = coverage.or(child.coverage);
                double additional = cv.getCoverage() - tcov;
                if (additional > 0) {

                    monitor.info(child.model.getName() + " adds "
                            + NumberFormat.getPercentInstance().format(additional)
                            + " of "
                            + this.observable.getType(), Messages.INFOCLASS_MODEL);

                    coverage = cv;
                    // link up the model into a common one.
                    if (node == null) {
                        node = child.node;
                    } else {

                        KLAB.info("MUST MERGE MODELS " + node.model + " WITH "
                                + child.model);
                        /*
                         * TODO substitute the model in the node with the merged model for
                         * the additional coverage.
                         */
                    }

                    /*
                     * TODO store merged model in cache in lieu of previous store in cache
                     */

                } else {
                    noOp = true;
                }

            }

        } else if (type.equals(Type.GROUP)) {

            /*
             * merge according to the connector we are using
             */
            if (connector.equals(LogicalConnector.INTERSECTION)) {
                coverage = coverage.and(child.coverage);
            } else {
                coverage = coverage.or(child.coverage);
            }

        } else {
            coverage = child.coverage;
        }

        /*
         * merge the child's context if we're using it within this subject. Of course we
         * don't merge anything if we're resolving a child subject - we should, though,
         * link the provenance info in some way.
         */
        if (!coverage.isEmpty()) {

            for (IObservableSemantics o : child.models.keySet()) {
                if (!(child.models.get(o) instanceof StateModel)) {
                    models.put(o, child.models.get(o));
                }
            }
            instantiatorModels.putAll(child.instantiatorModels);
            noModelsFor.addAll(child.noModelsFor);
            usedDependencies.addAll(child.usedDependencies);

            if (child.type == Type.SUBJECT) {

                if (model == null) {
                    model = child.model;
                }
                /*
                 * TODO link provenance to parent context
                 */
                // child.resolutionStrategy.execute(transition);

            } else {

                // if (!isDirect) {

                resolutionGraph.merge(child.resolutionGraph);
                // resolutionStrategy.merge(child.resolutionStrategy);

                if (!noOp) {
                    if (source != null) {
                        resolutionGraph.add(source);
                    }
                    if (source != null && target != null && dlink != null) {
                        resolutionGraph.link(target, source, dlink);
                        // CallTracer.msg("Linked " + dlink.describeType());
                    }

                    /*
                     * if we're in a group and we have all the coverage we need, we also
                     * want to merge the found models with our own cache, so the next
                     * dependency can find them. HMMM may not work - what happens to the
                     * next in line for the same observable in a union? Must be done when
                     * coverage is sufficient
                     */
                    // if (parent != null && parent.type.equals(Type.GROUP)) {
                    // parent.models.putAll(models);
                    // }
                }

                /*
                 * float these if we're just a linkpoint to the parent.
                 */
                if (node == null && link == null) {

                    node = child.node;
                    link = child.link;

                    /*
                     * "color" the link if our context contains anything relevant to it.
                     */
                    if (link != null) {
                        if (dependency != null) {
                            link.formalName = dependency.getFormalName();
                            link.property = dependency.getProperty();
                        }
                        link.condition = condition;
                        link.conditionIndex = conditionIndex;
                    }
                }
                // }

                /*
                 * inherit the model if we have resolved a new one in the child and we are
                 * still resolving.
                 */
                if (model == null /* && !isDirect */) {
                    model = child.model;
                }

                /*
                 * float the scale (only to the root context) or harmonize it with
                 */
                if (scale == null) {
                    scale = child.scale;
                } else {

                    /*
                     * TODO harmonize the common scale with that of the object we have
                     * just resolved.
                     */
                    scale = scale.harmonize(child.scale);
                }
            }
        }

        /*
         * if we've been given metadata by the resolver, transfer them to the node we're
         * handling.
         */
        if (metadata != null && node != null) {
            node.getMetadata().merge(metadata, true);
        }

        return coverage;

    }

    private HashMap getModelCache(boolean isExplanatoryModel) {
        return isExplanatoryModel ? models : instantiatorModels;
    }

    /**
     * Get the first parent node available. Used when we must connect a process model's
     * dependencies to the subject they inhere to.
     * 
     * @return
     */
    private ProvenanceNode getParentNode() {
        ResolutionScope ctx = parent;
        while (ctx != null && ctx.node == null) {
            ctx = ctx.parent;
        }
        return ctx == null ? null : ctx.node;
    }

    public boolean isRoot() {
        return type.equals(Type.ROOT);
    }

    @Override
    public String toString() {
        return resolutionGraph.vertexSet().size() + "#{" + type + " # "
                + (coverage == null ? "null" : coverage.getCoverage()) + " # " + subject
                + "/" + model + "/"
                + observer
                + "}";
    }

    public boolean isRootSubjectObservable() {
        return type.equals(Type.OBSERVABLE) && parent.type.equals(Type.SUBJECT)
                && parent.parent.type.equals(Type.ROOT);
    }

    /**
     * Return the traits that our resolved target is expected to inherit.
     * 
     * @return traits
     */
    public Set getTraits() {
        // don't return null, make it easy for your friends.
        return attributes == null ? new HashSet<>() : attributes;
    }

    /*
     * resolve the current context with the model passed, which is already in the
     * provenance graph.
     */
    public ICoverage resolve(IModel model) throws KlabException {
        coverage = coverages.get(model);
        resolutionGraph.add(resolutionGraph.getNode(model));
        return finish();
    }

    @Override
    public boolean isOptional() {
        return isOptional;
    }

    public boolean isResolving(IModel m) {
        return resolving.contains(m);
    }

    public void reset() {

        for (ResolutionScope c = this; c != null; c = c.parent) {
            c.resolutionGraph = new ResolutionGraph(monitor);
            for (IState s : subject.getStates()) {
                c.models.put(s.getObserver()
                        .getObservable(), new StateModel(s.getObservable()
                                .getSemantics(), s, subject.getNamespace()));
            }
        }
    }

    /**
     * Use the current model to provide a resolution step if it creates objects or has a
     * non-empty process accessor.
     */
    public void acceptModel() {
        if (model instanceof KIMModel && (((KIMModel) model).hasContextualizer()
                || ((KIMModel) model).getActions().size() > 0
                || ((KIMModel) model).hasObjectSource())) {
        }
    }

    /*
     * change the default context from seeking instantiators to seeking explanatory
     * models.
     */
    public ResolutionScope forExplanatoryModel() {
        isExplanatoryModel = true;
        return this;
    }

    @Override
    public boolean isForInstantiation() {
        return !isExplanatoryModel;
    }

    @Override
    public IModelPrioritizer getPrioritizer2() {
        if (prioritizer == null) {
            prioritizer = new ModelPrioritizer(this);
        }
        return prioritizer;
    }

    /**
     * Return how far away in the resolution is the passed namespace; -1 if not found.
     * 
     * @param ns
     * @return namespace distance
     */
    public int getNamespaceDistance(INamespace ns) {

        if (ns == null) {
            return -1;
        }

        int ret = 0;
        if (!this.resolutionNamespace.getId().equals(ns.getId())) {
            ResolutionScope rc = parent;
            while (rc != null) {
                ret++;
                if (rc.resolutionNamespace != null
                        && rc.resolutionNamespace.getId().equals(ns.getId())) {
                    return ret;
                }
                rc = rc.parent;
            }
        }

        return -1;
    }

    public int getProjectDistance(IProject ns) {

        if (ns == null) {
            return -1;
        }

        if (this.resolutionNamespace.getProject() != null
                && this.resolutionNamespace.getProject().getId().equals(ns.getId())) {
            return 0;
        }

        int ret = 0;
        if (this.resolutionNamespace.getProject() == null
                || !this.resolutionNamespace.getProject().getId().equals(ns.getId())) {
            ResolutionScope rc = parent;
            while (rc != null) {
                ret++;
                if (rc.resolutionNamespace != null
                        && rc.resolutionNamespace.getProject() != null
                        && rc.resolutionNamespace.getProject().getId()
                                .equals(ns.getId())) {
                    return ret;
                }
                rc = rc.parent;
            }
        }

        return -1;
    }

    public IMonitor getMonitor() {
        return monitor;
    }

    public void setMonitor(IMonitor monitor) {
        this.monitor = monitor;
    }

    @Override
    public boolean isInteractive() {
        return interactive;
    }

    public void setCause(Action cause) {
        this.cause = cause;
        // this.resolutionStrategy.setCause(cause);
    }

    @Override
    public boolean isUsed(IObservableSemantics observable) {
        return usedDependencies.contains(observable);
    }

    @Override
    public boolean isRequired(IObservableSemantics observable) {

        boolean ret = false;
        if (model != null) {
            ret = ((KIMModel) model).isOutputRequired(observable);
        }
        if (!ret) {
            ret = usedDependencies.contains(observable);
        }
        return ret;
    }

    /**
     * Notify that an additional (indirect or database-related) contextualizer is
     * available, and let the model or observer decide what to do with it, according to
     * which kind of model we're using. If there's no model, accept it and make a model If
     * this returns true the resolver is content with that and does not look for models to
     * resolve further.
     * 
     * @deprecated probably wrong strategy - see Resolver where it's called.
     * @param instantiator
     * @return
     */
    @Deprecated
    public boolean notifyContextualizer(IContextualizer instantiator) {

        if (this.model != null) {
            return ((KIMModel) model).acceptContextualizer(instantiator);
        } else if (NS.isDirect(this.observable)) {
            this.model = new KIMModel(this.observable, instantiator, this);
            this.node = this.resolutionGraph.getNode(this.model);
            this.resolutionGraph.add(this.node);
        }
        return true;
    }

    public ResolutionScope forIndirectObservable(IObservableSemantics observable, IDerivedObserver observer) {
        ResolutionScope ret = forObservable(observable);
        ret.indirectObserver = observer;
        ret.isExplanatoryModel = !NS.isDirect(observable);
        return ret;
    }

    @Override
    public boolean isGeneric() {
        return forceGeneric;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy