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;
}
}