/**
* 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.lang.reflect.Constructor;
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.references.components.ComponentReference;
import fr.imag.adele.apam.declarations.references.components.VersionedReference;
import fr.imag.adele.apam.declarations.references.resources.ResourceReference;
/**
* This class represents the common description of a component at all levels of
* abstraction (specification, implementation, composite or instance)
*
* @author vega
*
*/
public abstract class ComponentDeclaration {
/**
* The identifier of the service provider
*/
private final String name;
/**
* The reference to this component declaration
*/
private final ComponentReference<?> reference;
/**
* Whether this declaration has inherited features
*/
private final boolean isEffective;
/**
* The resources provided by this service
*/
protected final Set<ResourceReference> providedResources;
/**
* The resources required by this service
*/
private final Set<RelationDeclaration> relations;
/**
* The properties describing this service provider
*/
private final Map<String, String> properties;
private final List<PropertyDefinition> definitions;
/**
* Whether instances of this component must be accessed exclusively by a
* single client at the time
*/
private boolean isExclusive;
private boolean isDefinedExclusive;
/**
* Whether we can create instances of this component
*/
private boolean isInstantiable;
private boolean isDefinedInstantiable;
/**
* Whether there can be a single instance of this component in the execution
* platform
*/
private boolean isSingleton;
private boolean isDefinedSingleton;
/**
* Whether the instance of this component can be shared by different clients
*/
private boolean isShared;
private boolean isDefinedShared;
private ComponentDeclaration(String name, boolean isEffective) {
assert name != null;
this.name = name;
reference = generateReference();
/*
* provided resources
*/
providedResources = new HashSet<ResourceReference>();
/*
* declarations
*/
relations = new HashSet<RelationDeclaration>();
definitions = new ArrayList<PropertyDefinition>();
/*
* property values
*/
properties = new HashMap<String, String>();
/*
* Initialize default value for global properties
*/
this.isInstantiable = true;
this.isExclusive = false;
this.isSingleton = false;
this.isShared = true;
this.isDefinedInstantiable = false;
this.isDefinedExclusive = false;
this.isDefinedSingleton = false;
this.isDefinedShared = false;
this.isEffective = isEffective;
}
/**
* The usual constructor for a new declaration
*/
protected ComponentDeclaration(String name) {
this(name,false);
}
/**
* Copy constructor, creates a clone of a given component declaration, this is
* used for computing effective declarations
*/
protected ComponentDeclaration(ComponentDeclaration original) {
/*
* initialize clone
*/
this(original.name, true);
/*
* provided resources
*/
this.providedResources.addAll(original.providedResources);
/*
* declarations
*/
this.relations.addAll(original.relations);
this.definitions.addAll(original.definitions);
/*
* property values
*/
this.properties.putAll(original.properties);
/*
* Initialize default value for global properties
*/
this.isInstantiable = original.isInstantiable;
this.isExclusive = original.isExclusive;
this.isSingleton = original.isSingleton;
this.isShared = original.isShared;
this.isDefinedInstantiable = original.isDefinedInstantiable;
this.isDefinedExclusive = original.isDefinedExclusive;
this.isDefinedSingleton = original.isDefinedSingleton;
this.isDefinedShared = original.isDefinedShared;
}
/**
* Generates a unique resource identifier to reference this declaration.
*
*
* NOTE IMPORTANT this method is invoked at the beginning of the constructor when only
* the name filed is available.
*
* The generated reference must have the appropriate kind to reflect the abstraction level
* of the component, and the generic type of the reference must be the class of the defining
* declaration.
*/
protected abstract ComponentReference<?> generateReference();
/**
* Get the reference to this declaration
*/
public ComponentReference<?> getReference() {
return reference;
}
/**
* Get the name of the provider
*/
public String getName() {
return name;
}
/**
* The kind of this component
*/
public final ComponentKind getKind() {
return getReference().getKind();
}
/**
* Get the reference to the parent group of this declaration
*/
public abstract ComponentReference<?> getGroup();
/**
* Get the reference to the parent group of this declaration, including the specific
* range of versions
*/
public abstract VersionedReference<?> getGroupVersioned();
/**
* Whether this declaration has merged the inherited features of its group
*/
public boolean isEffective() {
return isEffective;
}
/**
* Computes the effective declaration that is the result of merging this declaration with
* the definitions in its group.
*
* @see #inheritFrom(ComponentDeclaration) for a description of the merging algorithm
* common to all kinds of components
*
*/
public final ComponentDeclaration getEffectiveDeclaration(ComponentDeclaration group) {
/*
* First we clone this declaration, we need to use reflection because we want to create
* an object of the same exact class as this, so we assume a clone constructor is defined
* for each subclass of ComponentDeclaration.
*/
try {
Constructor<? extends ComponentDeclaration> cloneConstructor = this.getClass().getDeclaredConstructor(this.getClass());
ComponentDeclaration effective = cloneConstructor.newInstance(this);
effective.inheritFrom(group);
return effective;
} catch (Exception e) {
/*
* This should never happen, the component declaration hierarchy has been completely defined
*/
e.printStackTrace();
return null;
}
}
/**
* Perform the standard inheritance of component features from the group.
*
* The following elements are propagated from the group
*
* 1) provided resources
* 2) property and relation declarations
* 3) valued properties
*
* The following elements in this declaration can refine definitions in the group
*
* 1) add constraints and instrumentation to relations
* 2) add instrumentation to property definitions
* Notice that this method is invoked by {@link #getEffectiveDeclaration(ComponentDeclaration)}
* on a clone of the original declaration.
*
* NOTE this method is intended to be redefined in subclasses to implement the specific
* refinements depending on the kind of component.
*/
protected void inheritFrom(ComponentDeclaration group) {
if (this.getGroup() == null && group != null)
throw new IllegalArgumentException("Component "+ getName() +": trying to refine from invalid group "+group);
if (this.getGroup() != null && group == null)
throw new IllegalArgumentException("Component "+ getName() +": trying to refine from invalid null group");
if (this.getGroup() != null && group != null && !this.getGroup().equals(group.getReference()))
throw new IllegalArgumentException("Component "+ getName() +": trying to refine from invalid group "+group);
if (group == null)
return;
/*
* Inherit the list of provided resources, only if an explicit list was not specified
*/
if (getProvidedResources().isEmpty())
this.getProvidedResources().addAll(group.getProvidedResources());
/*
* For relation definitions, verify inheritance and refinement
*/
for (RelationDeclaration relation : group.getRelations()) {
RelationDeclaration refinement = this.getRelation(relation.getIdentifier());
if (refinement == null) {
this.getRelations().add(new RelationDeclaration(relation));
}
else {
this.getRelations().remove(refinement);
this.getRelations().add(relation.refinedBy(refinement));
}
}
/*
* For property definitions, verify inheritance and refinement
*/
for (PropertyDefinition property : group.getPropertyDefinitions()) {
PropertyDefinition refinement = this.getPropertyDefinition(property.getName());
if (refinement == null) {
this.getPropertyDefinitions().add(property);
}
else {
this.getPropertyDefinitions().remove(refinement);
this.getPropertyDefinitions().add(property.refinedBy(refinement));
}
}
/*
* Property values in the group propagate and override this definition
*/
for (Map.Entry<String, String> property : group.getProperties().entrySet()) {
this.getProperties().put(property.getKey(),property.getValue());
}
}
/**
* Get the declared dependencies of this component
*/
public Set<RelationDeclaration> getRelations() {
return relations;
}
/**
* Get a relation declaration by name
*/
public RelationDeclaration getRelation(String id) {
for (RelationDeclaration relation : relations) {
if (relation.getIdentifier().equals(id)) {
return relation;
}
}
return null;
}
/**
* Get a relation declaration by reference
*/
public RelationDeclaration getRelation(RelationDeclaration.Reference relation) {
if (!this.getReference().equals(relation.getDeclaringComponent())) {
return null;
}
return getRelation(relation.getIdentifier());
}
/**
* Get the properties describing this provider
*/
public Map<String, String> getProperties() {
return properties;
}
/**
* Get the value of a property
*/
public String getProperty(String property) {
return properties.get(property);
}
/**
* Get the property definitions defined by this component
*/
public List<PropertyDefinition> getPropertyDefinitions() {
return definitions;
}
/**
* Get the named property definition, if declared
*/
public PropertyDefinition getPropertyDefinition(String propertyName) {
for (PropertyDefinition definition : definitions) {
if (definition.getName().equalsIgnoreCase(propertyName)) {
return definition;
}
}
return null;
}
/**
* Get the named property definition, if declared
*/
public PropertyDefinition getPropertyDefinition(PropertyDefinition.Reference property) {
if (!this.getReference().equals(property.getDeclaringComponent())) {
return null;
}
return getPropertyDefinition(property.getIdentifier());
}
/**
* Whether the specified property is defined in this component
*/
public boolean isDefined(String propertyName) {
return getPropertyDefinition(propertyName) != null;
}
/**
* Get the provided resources
*/
public Set<ResourceReference> getProvidedResources() {
return providedResources;
}
/**
* Get the provided resources of a given kind, for example Services or
* Messages.
*
* We use subclasses of ResourceReference as tags to identify kinds of
* resources. To add a new kind of resource a new subclass must be added.
*
* Notice that we return a set of resource references but typed to
* particular subtype of references, the unchecked downcast is then safe at
* runtime.
*/
public <T extends ResourceReference> Set<T> getProvidedResources(Class<T> kind) {
Set<T> resources = new HashSet<T>();
for (ResourceReference resourceReference : providedResources) {
if (kind.isInstance(resourceReference)) {
resources.add(kind.cast(resourceReference));
}
}
return resources;
}
public boolean isDefinedExclusive() {
return isDefinedExclusive;
}
public boolean isDefinedInstantiable() {
return isDefinedInstantiable;
}
public boolean isDefinedShared() {
return isDefinedShared;
}
public boolean isDefinedSingleton() {
return isDefinedSingleton;
}
/**
* Whether the component is exclusive
*/
public boolean isExclusive() {
return isExclusive;
}
/**
* Whether the component is instantiable
*/
public boolean isInstantiable() {
return isInstantiable;
}
/**
* Whether the component is shared
*/
public boolean isShared() {
return isShared;
}
/**
* Whether the component is singleton
*/
public boolean isSingleton() {
return isSingleton;
}
/**
* Displays the declaration on screen, indented. Same as toString, plus the
* indentation.
*
* @param indent
* : a number of white characters as indentation.
*/
public String printDeclaration(String indent) {
String nl = "\n" + indent;
StringBuffer ret = new StringBuffer();
ret.append(indent + " Declaration of " + name);
if (providedResources.size() != 0) {
ret.append(nl + " Provided resources: ");
for (ResourceReference resRef : providedResources) {
ret.append(nl + " " + resRef);
}
}
if (relations.size() != 0) {
ret.append(nl + " Dependencies: \n");
for (RelationDeclaration resRef : relations) {
ret.append(resRef.printRelationDeclaration(indent + " ")
+ "\n");
}
}
// if (properties.size() != 0) {
// ret.append(nl + " Properties: ");
// for (Object resRef : properties.keySet()) {
// ret.append(nl + " " + (String) resRef + " = " +
// properties.get(resRef));
// }
// }
if (definitions.size() != 0) {
ret.append(nl + " Attribute definitions ");
for (PropertyDefinition resRef : definitions) {
ret.append(nl + " " + resRef);
}
}
return ret.toString();
}
public void setExclusive(boolean isExclusive) {
this.isExclusive = isExclusive;
this.isDefinedExclusive = true;
}
public void setInstantiable(boolean isInstantiable) {
this.isInstantiable = isInstantiable;
this.isDefinedInstantiable = true;
}
public void setShared(boolean isShared) {
this.isShared = isShared;
this.isDefinedShared = true;
}
public void setSingleton(boolean isSingleton) {
this.isSingleton = isSingleton;
this.isDefinedSingleton = true;
}
@Override
public String toString() {
return printDeclaration("");
}
}