/** * Copyright 2011-2012 Universite Joseph Fourier, LIG, ADELE team * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fr.imag.adele.apam.declarations; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import fr.imag.adele.apam.declarations.instrumentation.CallbackDeclaration; import fr.imag.adele.apam.declarations.references.ResolvableReference; import fr.imag.adele.apam.declarations.references.components.ComponentReference; import fr.imag.adele.apam.declarations.references.resources.InterfaceReference; import fr.imag.adele.apam.declarations.references.resources.ResourceReference; import fr.imag.adele.apam.declarations.references.resources.UnknownReference; /** * This class represents the declaration of a required resources needed by a * component, that will be resolved at runtime by APAM. * * @author vega * */ public class RelationDeclaration extends ConstrainedReference { /** * The events associated to the runtime life-cycle of the relation */ public enum Event { BIND, UNBIND } /** * A reference to a relation declaration. Notice that relation identifiers * must be only unique in the context of their defining component * declaration. */ public static class Reference extends FeatureReference { public Reference(ComponentReference<?> definingComponent, String identifier) { super(definingComponent, identifier); } } /** * The reference to this declaration */ private final Reference reference; /** * The level of abstraction where this relation can be instantiated */ private final ComponentKind sourceKind; /** * The level of abstraction of the target of the relation */ private final ComponentKind targetKind; /** * Whether this relation is declared explicitly as multiple */ private final boolean isMultiple; /** * The policy to handle unresolved dependencies */ private final MissingPolicy missingPolicy; /** * The exception to throw for the exception missing policy * */ private final String missingException; /** * The list of instrumentation that need to be performed in the source * primitive component to implement the semantics of this relation at * runtime. */ protected final List<RequirerInstrumentation> instrumentations; /** * The map of list of call back methods associated to the relation lifecycle */ protected final Map<Event, Set<CallbackDeclaration>> callbacks; /** * The resolution space to consider when resolving this relation */ private final ResolvePolicy resolvePolicy; /** * The time at which resolution must be done */ private final CreationPolicy creationPolicy; /** * Whether a resolution error must trigger a backtrack in the architecture */ private final Boolean mustHide; /** * Whether this is a contextual relation */ private final boolean isContextual; /** * The source component for this declaration in the case of contextual * dependencies */ private final String matchSource; /** * Whether this contextual relation is declared explicitly as an override */ private final boolean isOverride; /** * Declares a new relation with the default policies */ public RelationDeclaration(ComponentReference<?> component, String id, ResolvableReference target, boolean isMultiple) { this(component, id, ComponentKind.INSTANCE, target, ComponentKind.INSTANCE, isMultiple, CreationPolicy.MANUAL, ResolvePolicy.EXIST, MissingPolicy.OPTIONAL, null, false, false, null, false); } /** * Declares a new relation with the specified policies */ public RelationDeclaration(ComponentReference<?> component, String id, ComponentKind sourceKind, ResolvableReference target, ComponentKind targetKind, boolean isMultiple, CreationPolicy creationPolicy, ResolvePolicy resolvePolicy, MissingPolicy missingPolicy, String missingException, Boolean mustHide) { this(component, id, sourceKind, target, targetKind, isMultiple, creationPolicy, resolvePolicy, missingPolicy, missingException, mustHide, false, null, false); } /** * Declares a new contextual relation with the specified policies * * TODO we should think about having a another class to represent contextual relations */ public RelationDeclaration(ComponentReference<?> component, String id, ComponentKind sourceKind, ResolvableReference target, ComponentKind targetKind, boolean isMultiple, CreationPolicy creationPolicy, ResolvePolicy resolvePolicy, MissingPolicy missingPolicy, String missingException, Boolean mustHide, boolean isContextual, String matchSource, boolean isOverride) { super(target); assert component != null && getTarget() != null; id = (id == null) ? getTarget().as(fr.imag.adele.apam.declarations.references.Reference.class).getIdentifier() : id; this.reference = new Reference(component, id); this.sourceKind = sourceKind; this.targetKind = targetKind; this.missingPolicy = missingPolicy; this.missingException = missingException; this.creationPolicy = creationPolicy; this.resolvePolicy = resolvePolicy; this.isMultiple = isMultiple; this.mustHide = mustHide; this.instrumentations = new ArrayList<RequirerInstrumentation>(); this.callbacks = new HashMap<Event, Set<CallbackDeclaration>>(); this.isContextual = isContextual; this.isOverride = isOverride; this.matchSource = matchSource; } /** * Clone this declaration */ public RelationDeclaration(RelationDeclaration original) { super(original); this.reference = original.reference; this.sourceKind = original.sourceKind; this.targetKind = original.targetKind; this.isMultiple = original.isMultiple; this.creationPolicy = original.creationPolicy; this.resolvePolicy = original.resolvePolicy; this.missingPolicy = original.missingPolicy; this.missingException = original.missingException; this.mustHide = original.mustHide; this.instrumentations = new ArrayList<RequirerInstrumentation>(original.instrumentations); this.callbacks = new HashMap<Event, Set<CallbackDeclaration>>(); for (Map.Entry<Event,Set<CallbackDeclaration>>callbackEntry : original.callbacks.entrySet()) { this.callbacks.put(callbackEntry.getKey(), new HashSet<CallbackDeclaration>(callbackEntry.getValue())); } this.isContextual = original.isContextual; this.isOverride = original.isOverride; this.matchSource = original.matchSource; } /** * Creates a new declaration that is the result of merging the original declaration with * the specified refinement. * * See {@link #refinedBy(RelationDeclaration)} and {@link #overriddenBy(RelationDeclaration)} * for a description of the merging rules */ private RelationDeclaration(RelationDeclaration original, RelationDeclaration refinement) { super(target(original, refinement),original,refinement); this.reference = original.reference; this.sourceKind = original.sourceKind; this.targetKind = original.targetKind; this.isMultiple = original.isMultiple; this.creationPolicy = original.creationPolicy == null ? refinement.creationPolicy : refinement.isOverride() && refinement.creationPolicy != null ? refinement.creationPolicy : original.creationPolicy; this.resolvePolicy = original.resolvePolicy == null ? refinement.resolvePolicy : refinement.isOverride() && refinement.resolvePolicy != null ? refinement.resolvePolicy : original.resolvePolicy; this.missingPolicy = refinement.missingPolicy != null ? refinement.missingPolicy : original.missingPolicy; this.missingException = refinement.missingException != null ? refinement.missingException : original.missingException; this.mustHide = refinement.mustHide; this.instrumentations = new ArrayList<RequirerInstrumentation>(); this.instrumentations.addAll(original.instrumentations); this.instrumentations.addAll(refinement.instrumentations); this.callbacks = new HashMap<Event, Set<CallbackDeclaration>>(); for (Map.Entry<Event,Set<CallbackDeclaration>>callbackEntry : original.callbacks.entrySet()) { this.callbacks.put(callbackEntry.getKey(), new HashSet<CallbackDeclaration>(callbackEntry.getValue())); } for (Map.Entry<Event,Set<CallbackDeclaration>>callbackEntry : refinement.callbacks.entrySet()) { this.callbacks.put(callbackEntry.getKey(), new HashSet<CallbackDeclaration>(callbackEntry.getValue())); } this.isContextual = original.isContextual; this.isOverride = original.isOverride; this.matchSource = original.matchSource; } /** * Determines the effective target of a a refinement. * * We keep the most concrete target between the original and the refinement. * * NOTE If the target of the original and the refinement relation are not related by a abstraction * relationship this method may give wrong results. However, we can not validate this, as this requires * the full definition of the target resources and components. We assume that this is validated at * build-time, to ensure safe execution. * * NOTE For overriding it is not possible to refine the target, because the target of the override is * used as a matching regular expressions. */ private static ResolvableReference target(RelationDeclaration original, RelationDeclaration refinement) { if (refinement.isOverride()) return original.getTarget(); if (refinement.getTarget() instanceof UnknownReference) return original.getTarget(); if (original.getTarget() instanceof UnknownReference) return refinement.getTarget(); if (original.getTarget() instanceof ResourceReference && refinement.getTarget() instanceof ComponentReference) return refinement.getTarget(); if (original.getTarget() instanceof ComponentReference && refinement.getTarget() instanceof ResourceReference) return original.getTarget(); if (original.getTarget() instanceof ComponentReference && refinement.getTarget() instanceof ComponentReference) { ComponentKind originalTargetKind = original.getTarget().as(ComponentReference.class).getKind(); ComponentKind refinementTargetKind = refinement.getTarget().as(ComponentReference.class).getKind(); return originalTargetKind.isMoreAbstractThan(refinementTargetKind) ? refinement.getTarget() : original.getTarget(); } return original.getTarget(); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null) return false; if (!(object instanceof RelationDeclaration)) { return false; } RelationDeclaration that = (RelationDeclaration) object; /* * overrides and definitions are conceptually in different namespaces */ if (this.isOverride != that.isOverride) return false; /* * for overrides we need to consider source and target equality, and not * only identifier */ if (this.isOverride) { if (this.sourceKind != that.sourceKind) return false; if (this.targetKind != that.targetKind) return false; if (this.matchSource == null && that.matchSource != null) return false; if (this.matchSource != null && that.matchSource == null) return false; if (this.matchSource != null && !this.matchSource.equals(that.matchSource)) return false; if (!this.getTarget().equals(that.getTarget())) return false; } /* * Equality is based on identifier of the dependency */ return this.reference.equals(that.reference); } @Override public int hashCode() { return reference.hashCode(); } /** * The defining component */ public ComponentReference<?> getComponent() { return reference.getDeclaringComponent(); } /** * Get the reference to this declaration */ public Reference getReference() { return reference; } /** * Get the id of the relation in the declaring component declaration */ public String getIdentifier() { return reference.getIdentifier(); } public ComponentKind getSourceKind() { return sourceKind; } public ComponentKind getTargetKind() { return targetKind; } /** * The multiplicity of a relation. * * If this is an abstract declaration in specifications or composites, it must be explicitly * defined. Otherwise it is inferred from the needs of the declared instrumentation. * * TODO Perhaps this method should return the actually declared value of the multiplicity, and * the override by instrumentation must be performed by the semantic level validation. * */ public boolean isMultiple() { if (getInstrumentations().isEmpty()) { return isMultiple; } /* * If there is at least one instrumentation that can handle multiple * providers the relation is considered is multiple. * * TODO currently the way messages are handled, they always support * multiple providers, and consequently this forces the relation to be * multi-valued. This is not very intuitive so we added a special case * to ignore messages. * * Perhaps we should consider the more systematic alternative of * declaring a relation multiple if all the instrumentation can handle * it. But allow an explicit override. */ boolean oneRequiredService = false; boolean supportMultiple = false; for (RequirerInstrumentation injection : getInstrumentations()) { boolean isService = injection.getRequiredResource().as(InterfaceReference.class) != null; oneRequiredService = isService || oneRequiredService; if (isService && injection.acceptMultipleProviders()) { supportMultiple = true; } } return oneRequiredService ? supportMultiple : isMultiple; } public ResolvePolicy getResolvePolicy() { return resolvePolicy; } public CreationPolicy getCreationPolicy() { return creationPolicy; } /** * Get the policy associated with this relation */ public MissingPolicy getMissingPolicy() { return missingPolicy; } /** * Get the exception associated with the missing policy */ public String getMissingException() { return missingException; } /** * Whether an error resolving a relation matching this policy should trigger * a backtrack in resolution */ public Boolean isHide() { return mustHide; } /** * Get the callbacks associated to this relation */ public Set<CallbackDeclaration> getCallback(Event trigger) { return callbacks.get(trigger); } public void addCallback(Event trigger, CallbackDeclaration callback) { if (callbacks.get(trigger) == null) { callbacks.put(trigger, new HashSet<CallbackDeclaration>()); } callbacks.get(trigger).add(callback); } /** * Get the instrumentation metadata associated to this relation declaration */ public List<RequirerInstrumentation> getInstrumentations() { return instrumentations; } /** * Computes the effective declaration that is the result of applying the specified refinement * to this declaration. * * Refinements can be declared in members of a component or in contextual dependencies in a * composite. * * The refinement algorithm is the following : * * 1) The source kind and target kind must be defined in the this declaration * 2) The target can be refined to a member of the target specified by this declaration * 3) Constraints are concatenated (this is equivalent to a conjunction of the constraints) * 4) Preferences are concatenated * 5) Policies can be refined only if not explicitly defined in this declaration * 6) Missing policy (resolution failure) can be completely overridden in the refinement * 7) Instrumentation and callbacks are concatenated * */ public RelationDeclaration refinedBy(RelationDeclaration refinement) { return new RelationDeclaration(this,refinement); } /** * Whether this declaration is is a contextual declaration */ public boolean isContextual() { return isContextual; } /** * Checks if this declaration can refine the specified declaration for refinement or override. * Matching can be based on the name of the declaring component or one of its ancestor groups * * For refinement the name of the declaring component's group and the name of the relation must * match exactly. * * For overrides, the contextual relation can use regular expression patterns to match the source, * the target or the name of the relation. * */ public boolean refines(ComponentReference<?>group, RelationDeclaration relation) { /* * for contextual refinement match source and identifier exactly */ if (this.isContextual() && ! this.isOverride()) { return relation.getIdentifier().equals(this.getIdentifier()) && group.getName().equals(this.matchSource); } /* * for contextual override use regular expressions to match, if there is no * matching expression specified it matches every source */ if (this.isContextual() && this.isOverride()) { boolean sourceMatched = this.matchSource == null || group.getName().matches(this.matchSource); boolean targetClassMatched = relation.getTarget().getClass().equals(this.getTarget().getClass()); boolean targetMatched = this.getTarget() instanceof UnknownReference || (targetClassMatched && relation.getTarget().getName().matches(this.getTarget().getName())); return relation.getIdentifier().matches(this.getIdentifier()) && sourceMatched && targetMatched; } /* * The usual match for refinement of abstract components is based on relation * identifiers */ return relation.getIdentifier().equals(this.getIdentifier()); } /** * Checks if this contextual relation definition applies to the specified source */ public boolean appliesTo(ComponentReference<?> source) { return this.isContextual() && this.matchSource.equals(source.getName()) && this.getSourceKind() == source.getKind(); } /** * Whether this declaration is an override */ public boolean isOverride() { return isOverride; } /** * Computes the effective declaration that is the result of applying the specified override * to this declaration. * * Overrides can be declared in contextual dependencies in a composite. * * The override algorithm is the following : * * 1) The source kind and target kind must be defined in the this declaration * 2) The target can be refined to a member of the target specified by this declaration * 3) Constraints are concatenated (this is equivalent to a conjunction of the constraints) * 4) Preferences are concatenated * 5) Policies can be completely overridden * 6) Instrumentation and callbacks are concatenated * */ public RelationDeclaration overriddenBy(RelationDeclaration override) { assert override.isOverride(); return new RelationDeclaration(this,override); } public String printRelationDeclaration(String indent) { StringBuffer ret = new StringBuffer(); ret.append(indent + " relation " + getIdentifier() + " towards " + getTarget().getName() + " (creation =" + creationPolicy + ", resolve=" + resolvePolicy + ")"); if (!instrumentations.isEmpty()) { // ret += "\n Injected dependencies"; for (RequirerInstrumentation inj : instrumentations) { ret.append(" " + inj); } } if (getCallback(Event.BIND) != null && !getCallback(Event.BIND).isEmpty()) { ret.append("\n added"); for (CallbackDeclaration inj : getCallback(Event.BIND)) { ret.append("\n " + inj.getMethodName()); } } if (getCallback(Event.UNBIND) != null && !getCallback(Event.UNBIND).isEmpty()) { ret.append("\n removed"); for (CallbackDeclaration inj : getCallback(Event.UNBIND)) { ret.append("\n " + inj.getMethodName()); } } if (!getImplementationConstraints().isEmpty()) { ret.append("\n Implementation Constraints"); for (String inj : getImplementationConstraints()) { ret.append("\n " + inj); } } if (!getInstanceConstraints().isEmpty()) { ret.append("\n Instance Constraints"); for (String inj : getInstanceConstraints()) { ret.append("\n " + inj); } } if (!getImplementationPreferences().isEmpty()) { ret.append("\n Implementation Preferences"); for (String inj : getImplementationPreferences()) { ret.append("\n " + inj); } } if (!getInstancePreferences().isEmpty()) { ret.append("\n Instance Preferences"); for (String inj : getInstancePreferences()) { ret.append("\n " + inj); } } return ret.toString(); } @Override public String toString() { return printRelationDeclaration(""); } }