/**
* ***************************************************************
* JADE - Java Agent DEvelopment Framework is a framework to develop
* multi-agent systems in compliance with the FIPA specifications.
* Copyright (C) 2000 CSELT S.p.A.
*
* GNU Lesser General Public License
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation,
* version 2.1 of the License.
*
* This library 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
* **************************************************************
*/
package jade.content.schema;
import jade.content.onto.*;
import jade.content.abs.*;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import jade.content.schema.facets.*;
import jade.core.CaseInsensitiveString;
import jade.util.leap.Serializable;
import jade.util.Logger;
/**
* @author Giovanni Caire - TILAB
*/
class ObjectSchemaImpl extends ObjectSchema {
private Logger logger = Logger.getMyLogger(this.getClass().getName());
static final String RESULT_SLOT_NAME = "__Result_SLOT_123";
private class SlotDescriptor implements Serializable {
private String name = null;
private ObjectSchema schema = null;
private int optionality = 0;
/**
Construct a SlotDescriptor
*/
private SlotDescriptor(String name, ObjectSchema schema, int optionality) {
this.name = name;
this.schema = schema;
this.optionality = optionality;
}
}
private String typeName = null;
private Hashtable slots;
private Vector slotNames;
private Vector superSchemas;
// Note that the list of facets for a given slot cannot be included in the slot descriptor.
// In fact facets are associated to the a slot in a given schema, not to the slot itself.
// For instance if we have
// - schema A that extends schema B (B is a super-schema of A)
// - B defines slot S and associates facet f1 to it
// - A associates facet f2 to slot S
// When validating an instance of B only facet f1 must be applied, while when validating
// an instance of A both f1 and f2 must be applied.
private Hashtable facets;
static {
baseSchema = new ObjectSchemaImpl();
}
/**
* Construct a schema that vinculates an entity to be a generic
* object (i.e. no constraints at all)
*/
private ObjectSchemaImpl() {
this(BASE_NAME);
}
/**
* Creates an <code>ObjectSchema</code> with a given type-name.
* @param typeName The name of this <code>ObjectSchema</code>.
*/
protected ObjectSchemaImpl(String typeName) {
this.typeName = typeName;
}
/**
* Add a slot to the schema.
* @param name The name of the slot.
* @param slotSchema The schema defining the type of the slot.
* @param optionality The optionality, i.e., <code>OPTIONAL</code>
* or <code>MANDATORY</code>
*/
protected void add(String name, ObjectSchema slotSchema, int optionality) {
CaseInsensitiveString ciName = new CaseInsensitiveString(name);
if (slots == null) {
slots = new Hashtable();
slotNames = new Vector();
}
if (slots.put(ciName, new SlotDescriptor(name, slotSchema, optionality)) == null) {
// We treat Action results as if they were slots. However we don't want the special
// RESULT_SLOT_NAME to be included among slot names
if (!name.equals(RESULT_SLOT_NAME)) {
slotNames.addElement(ciName);
}
}
}
/**
* Add a mandatory slot to the schema.
* @param name name of the slot.
* @param slotSchema schema of the slot.
*/
protected void add(String name, ObjectSchema slotSchema) {
add(name, slotSchema, MANDATORY);
}
/**
* Add a slot with cardinality between <code>cardMin</code>
* and <code>cardMax</code> to this schema.
* Adding such a slot corresponds to add a slot
* of type Aggregate and then to add proper facets (constraints)
* to check that the type of the elements in the aggregate are
* compatible with <code>elementsSchema</code> and that the
* aggregate contains at least <code>cardMin</code> elements and
* at most <code>cardMax</code> elements. By default the Aggregate
* is of type <code>BasicOntology.SEQUENCE</code>.
* @param name The name of the slot.
* @param elementsSchema The schema for the elements of this slot.
* @param cardMin This slot must get at least <code>cardMin</code>
* values
* @param cardMax This slot can get at most <code>cardMax</code>
* values
*/
protected void add(String name, ObjectSchema elementsSchema, int cardMin, int cardMax) {
add(name, elementsSchema, cardMin, cardMax, BasicOntology.SEQUENCE);
}
/**
* Add a slot with cardinality between <code>cardMin</code>
* and <code>cardMax</code> to this schema and allow specifying the type
* of Aggregate to be used for this slot.
* @param name The name of the slot.
* @param elementsSchema The schema for the elements of this slot.
* @param cardMin This slot must get at least <code>cardMin</code>
* values
* @param cardMax This slot can get at most <code>cardMax</code>
* values
* @param aggType The type of Aggregate to be used
* @see #add(String, ObjectSchema, int, int)
*/
protected void add(String name, ObjectSchema elementsSchema, int cardMin, int cardMax, String aggType) {
int optionality = (cardMin == 0 ? OPTIONAL : MANDATORY);
add(name, elementsSchema, cardMin, cardMax, aggType, optionality);
}
/**
* Add a slot with optionality and cardinality between <code>cardMin</code>
* and <code>cardMax</code> to this schema and allow specifying the type
* of Aggregate to be used for this slot.
* @param name The name of the slot.
* @param elementsSchema The schema for the elements of this slot.
* @param cardMin This slot must get at least <code>cardMin</code>
* values
* @param cardMax This slot can get at most <code>cardMax</code>
* values
* @param aggType The type of Aggregate to be used
* @param optionality The optionality, i.e., <code>OPTIONAL</code>
* @see #add(String, ObjectSchema, int, int)
*/
protected void add(String name, ObjectSchema elementsSchema, int cardMin, int cardMax, String aggType, int optionality) {
try {
// If the aggregate type is not yet present in the BasicOntology, add it (without elements type specification)
ObjectSchema aggTypeSchema = BasicOntology.getInstance().getSchema(aggType);
if (aggTypeSchema == null) {
// Create a new aggregate schema and add it to BasicOntology
aggTypeSchema = new AggregateSchema(aggType);
BasicOntology.getInstance().add(aggTypeSchema);
}
// If elements are typed, we need an ad-hoc schema to hold the elements type specification
ObjectSchema schema = null;
if (elementsSchema != null) {
schema = new AggregateSchema(aggType, (TermSchema) elementsSchema);
}
else {
schema = aggTypeSchema;
}
add(name, schema, optionality);
// Add proper facets
if (elementsSchema != null) {
addFacet(name, new TypedAggregateFacet(elementsSchema));
}
addFacet(name, new CardinalityFacet(cardMin, cardMax));
}
catch (OntologyException oe) {
// Should never happen
oe.printStackTrace();
}
}
/**
* Add a super schema to this schema, i.e. this schema will
* inherit all characteristics from the super schema
* @param superSchema the super schema.
*/
protected void addSuperSchema(ObjectSchema superSchema) {
if (superSchema != null) {
if (superSchemas == null) {
superSchemas = new Vector();
}
superSchemas.addElement(superSchema);
}
}
/**
Add a <code>Facet</code> on a slot of this schema
@param slotName the name of the slot the <code>Facet</code>
must be added to.
@param f the <code>Facet</code> to be added.
@throws OntologyException if slotName does not identify
a valid slot in this schema
*/
protected void addFacet(String slotName, Facet f) throws OntologyException {
if (containsSlot(slotName)) {
CaseInsensitiveString ciName = new CaseInsensitiveString(slotName);
if (facets == null) {
facets = new Hashtable();
}
Vector v = (Vector) facets.get(ciName);
if (v == null) {
v = new Vector();
facets.put(ciName, v);
//DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"Added facet "+f+" to slot "+slotName);
}
v.addElement(f);
}
else {
throw new OntologyException(slotName+" is not a valid slot in this schema");
}
}
/**
* Retrieves the name of the type of this schema.
* @return the name of the type of this schema.
*/
public String getTypeName() {
return typeName;
}
/**
* Returns the names of all the slots in this <code>Schema</code>
* (including slots defined in super schemas).
*
* @return the names of all slots.
*/
public String[] getNames() {
Vector allSlotNames = new Vector();
fillAllSlotNames(allSlotNames);
String[] names = new String[allSlotNames.size()];
int counter = 0;
for (Enumeration e = allSlotNames.elements(); e.hasMoreElements(); ) {
names[counter++] = ((CaseInsensitiveString) e.nextElement()).toString();
}
return names;
}
/**
* Returns the names of the slots defined in this <code>Schema</code>
* (excluding slots defined in super schemas).
*
* @return the names of the slots defined in this <code>Schema</code>.
*/
public String[] getOwnNames() {
if (slotNames != null) {
String[] ownNames = new String[slotNames.size()];
int counter = 0;
for (Enumeration e = slotNames.elements(); e.hasMoreElements(); ) {
ownNames[counter++] = ((CaseInsensitiveString) e.nextElement()).toString();
}
return ownNames;
}
else {
return new String[0];
}
}
/**
* Retrieves the schema of a slot of this <code>Schema</code>.
*
* @param name The name of the slot.
* @return the <code>Schema</code> of slot <code>name</code>
* @throws OntologyException If no slot with this name is present
* in this schema.
*/
public ObjectSchema getSchema(String name) throws OntologyException {
SlotDescriptor slot = getSlot(new CaseInsensitiveString(name));
if (slot == null) {
throw new OntologyException("No slot named: " + name);
}
return slot.schema;
}
/**
* Indicate whether a given <code>String</code> is the name of a
* slot defined in this <code>Schema</code>
*
* @param name The <code>String</code> to test.
* @return <code>true</code> if <code>name</code> is the name of a
* slot defined in this <code>Schema</code>.
*/
public boolean containsSlot(String name) {
SlotDescriptor slot = getSlot(new CaseInsensitiveString(name));
return (slot != null);
}
/**
* Indicate whether a given <code>String</code> is the name of a
* slot actually defined in this <code>Schema</code> (excluding super-schemas)
*
* @param name The <code>String</code> to test.
* @return <code>true</code> if <code>name</code> is the name of a
* slot actually defined in this <code>Schema</code> (excluding super-schemas).
*/
public boolean isOwnSlot(String name) {
SlotDescriptor slot = getOwnSlot(new CaseInsensitiveString(name));
return (slot != null);
}
/**
* Indicate whether a slot of this schema is mandatory
*
* @param name The name of the slot.
* @return <code>true</code> if the slot is mandatory.
* @throws OntologyException If no slot with this name is present
* in this schema.
*/
public boolean isMandatory(String name) throws OntologyException {
SlotDescriptor slot = getSlot(new CaseInsensitiveString(name));
if (slot == null) {
throw new OntologyException("No slot named: " + name);
}
return slot.optionality == MANDATORY;
}
/**
* Creates an Abstract descriptor to hold an object compliant to
* this <code>Schema</code>.
*/
public AbsObject newInstance() throws OntologyException {
throw new OntologyException("AbsObject cannot be instantiated");
}
private final void fillAllSlotNames(Vector v) {
// Get slot names of super schemas (if any) first
if (superSchemas != null) {
for (Enumeration e = superSchemas.elements(); e.hasMoreElements(); ) {
ObjectSchemaImpl superSchema = (ObjectSchemaImpl) e.nextElement();
superSchema.fillAllSlotNames(v);
}
}
// Then add slot names of this schema
if (slotNames != null) {
for (Enumeration e = slotNames.elements(); e.hasMoreElements(); ) {
v.addElement(e.nextElement());
}
}
}
/**
Check whether a given abstract descriptor complies with this
schema.
@param abs The abstract descriptor to be checked
@throws OntologyException If the abstract descriptor does not
complies with this schema
*/
public void validate(AbsObject abs, Ontology onto) throws OntologyException {
validateSlots(abs, onto);
}
/**
For each slot
- get the corresponding attribute value from the abstract descriptor
abs
- Check that it is not null if the slot is mandatory
- Check that its schema is compatible with the schema of the slot
- Check that it is a correct abstract descriptor by validating it
against its schema.
*/
protected void validateSlots(AbsObject abs, Ontology onto) throws OntologyException {
// Validate all the attributes in the abstract descriptor
String[] slotNames = getNames();
for (int i = 0; i < slotNames.length; ++i) {
AbsObject slotValue = abs.getAbsObject(slotNames[i]);
CaseInsensitiveString ciName = new CaseInsensitiveString(slotNames[i]);
validate(ciName, slotValue, onto);
}
}
/**
Validate a given abstract descriptor as a value for a slot
defined in this schema
@param slotName The name of the slot
@param value The abstract descriptor to be validated
@throws OntologyException If the abstract descriptor is not a
valid value
@return true if the slot is defined in this schema (or in
one of its super schemas). false otherwise
*/
private boolean validate(CaseInsensitiveString slotName, AbsObject value, Ontology onto) throws OntologyException {
// DEBUG
if(logger.isLoggable(Logger.FINE))
logger.log(Logger.FINE,"Validating "+(value != null ? value.toString() : "null")+" as a value for slot "+slotName);
// NOTE: for performance reasons we don't want to scan the schema
// to check if slotValue is a valid slot and THEN to scan again
// the schema to validate value. This is the reason for the
// boolean return value of this method
boolean slotFound = false;
// If the slot is defined in this schema --> check the value
// against the schema of the slot. Otherwise let the super-schema
// where the slot is defined validate the value
SlotDescriptor dsc = getOwnSlot(slotName);
if (dsc != null) {
// DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"Slot "+slotName+" is defined in schema "+this);
if (value == null) {
// Check optionality
if (dsc.optionality == MANDATORY) {
throw new OntologyException("Missing value for mandatory slot "+slotName+". Schema is "+this);
}
// Don't need to check facets on a null value for an optional slot
return true;
}
else {
// - Get from the ontology the schema s that defines the type
// of the abstract descriptor value.
// - Check if this schema is compatible with the schema for
// slot slotName
// - Finally check value against s
ObjectSchema s = onto.getSchema(value.getTypeName());
//DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"Actual schema for "+value+" is "+s);
if (s == null) {
throw new OntologyException("No schema found for type "+value.getTypeName()+". Ontology is "+onto.getName());
}
if (!s.isCompatibleWith(dsc.schema)) {
throw new OntologyException("Schema "+s+" for element "+value+" is not compatible with schema "+dsc.schema+" for slot "+slotName);
}
//DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"Schema "+s+" for type "+value+" is compatible with schema "+dsc.schema+" for slot "+slotName);
s.validate(value, onto);
}
slotFound = true;
}
else {
if (superSchemas != null) {
Enumeration e = superSchemas.elements();
while (e.hasMoreElements()) {
ObjectSchemaImpl s = (ObjectSchemaImpl) e.nextElement();
if (s.validate(slotName, value, onto)) {
slotFound = true;
// Don't need to check other super-schemas
break;
}
}
}
}
if (slotFound && facets != null) {
// Check value against the facets (if any) defined for the
// slot in this schema
Vector ff = (Vector) facets.get(slotName);
if (ff != null) {
Enumeration e = ff.elements();
while (e.hasMoreElements()) {
Facet f = (Facet) e.nextElement();
//DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"Checking facet "+f+" defined on slot "+slotName);
f.validate(value, onto);
}
}
else {
//DEBUG
if(logger.isLoggable(Logger.CONFIG))
logger.log(Logger.CONFIG,"No facets for slot "+slotName);
}
}
return slotFound;
}
/**
Check if this schema is compatible with a given schema s.
This is the case if
1) This schema is equals to s
2) s is one of the super-schemas of this schema
3) This schema descends from s i.e.
- s is the base schema for the XXXSchema class this schema is
an instance of (e.g. s is ConceptSchema.getBaseSchema() and this
schema is an instance of ConceptSchema)
- s is the base schema for a super-class of the XXXSchema class
this schema is an instance of (e.g. s is TermSchema.getBaseSchema()
and this schema is an instance of ConceptSchema)
*/
public boolean isCompatibleWith(ObjectSchema s) {
if (equals(s)) {
return true;
}
if (isSubSchemaOf(s)) {
return true;
}
if (descendsFrom(s)) {
return true;
}
return false;
}
/**
Return true if
- s is the base schema for the XXXSchema class this schema is
an instance of (e.g. s is ConceptSchema.getBaseSchema() and this
schema is an instance of ConceptSchema)
- s is the base schema for a super-class of the XXXSchema class
this schema is an instance of (e.g. s is TermSchema.getBaseSchema()
and this schema is an instance of ConceptSchema)
*/
protected boolean descendsFrom(ObjectSchema s) {
// The base schema for the ObjectSchema class descends only
// from itself
if (s!= null) {
return s.equals(getBaseSchema());
}
else {
return false;
}
}
/**
Return true if s is a super-schema (directly or indirectly)
of this schema
*/
private boolean isSubSchemaOf(ObjectSchema s) {
if (superSchemas != null) {
Enumeration e = superSchemas.elements();
while (e.hasMoreElements()) {
ObjectSchemaImpl s1 = (ObjectSchemaImpl) e.nextElement();
if (s1.equals(s)) {
return true;
}
if (s1.isSubSchemaOf(s)) {
return true;
}
}
}
return false;
}
public String toString() {
return getClass().getName()+"-"+getTypeName();
}
public boolean equals(Object o) {
if (o != null) {
return toString().equals(o.toString());
}
else {
return false;
}
}
public ObjectSchema[] getSuperSchemas() {
ObjectSchema[] ss = null;
if (superSchemas != null) {
ss = new ObjectSchema[superSchemas.size()];
for (int i = 0; i < ss.length; ++i) {
ss[i] = (ObjectSchema) superSchemas.elementAt(i);
}
}
else {
ss = new ObjectSchema[0];
}
return ss;
}
public Facet[] getFacets(String slotName) {
Vector v = getAllFacets(slotName);
Facet[] ff = null;
if (v != null) {
ff = new Facet[v.size()];
for (int i = 0; i < v.size(); ++i) {
ff[i] = (Facet) v.elementAt(i);
}
}
return ff;
}
/**
* Get the facets defined upon a slot. More in details this method returns
* all facets defined in this schema plus all facets defined in super-schemas
* up to the schema actually declaring the given slot.
* @return the facets defined upon a slot or null if the specified slot is not found.
*/
Vector getAllFacets(String slotName) { // Package-scoped for testing purposes only
Vector allFacets = new Vector();
CaseInsensitiveString caseInsensitiveSlotName = new CaseInsensitiveString(slotName);
if (facets != null) {
Vector v = (Vector)facets.get(caseInsensitiveSlotName);
if (v != null) {
// We don't use Vector.addAll() for MIDP compatibility
addAll(allFacets, v);
}
}
boolean found = false;
if (getOwnSlot(caseInsensitiveSlotName) == null) {
// The slot must be defined in one of the super-schema
if (superSchemas!=null) {
for (int i = 0; i < superSchemas.size(); i++) {
ObjectSchemaImpl superSchema = (ObjectSchemaImpl) superSchemas.elementAt(i);
if (superSchema.containsSlot(slotName)) {
found = true;
// We don't use Vector.addAll() for MIDP compatibility
addAll(allFacets, superSchema.getAllFacets(slotName));
}
}
}
}
else {
found = true;
}
return (found ? allFacets : null);
}
private final void addAll(Vector v1, Vector v2) {
for (int i = 0; i < v2.size(); ++i) {
v1.addElement(v2.elementAt(i));
}
}
private final SlotDescriptor getOwnSlot(CaseInsensitiveString ciName) {
return (slots != null ? (SlotDescriptor) slots.get(ciName) : null);
}
private final SlotDescriptor getSlot(CaseInsensitiveString ciName) {
SlotDescriptor dsc = getOwnSlot(ciName);
if (dsc == null) {
if (superSchemas != null) {
for (int i = 0; i < superSchemas.size(); ++i) {
ObjectSchemaImpl sc = (ObjectSchemaImpl) superSchemas.elementAt(i);
dsc = sc.getSlot(ciName);
if (dsc != null) {
break;
}
}
}
}
return dsc;
}
public boolean isAssignableFrom(ObjectSchema s) {
// This = destination schema
// s = source schema
// Type names must be equals or
// the source schema must have a super-schema with the same type name of the destination type name
// e.g.
// - source schema = LivingBeing
// - destination = Person and Person has a super-schema called LivingBeing
if (s.isCompatibleWith(this)) {
return true;
}
try {
// All slots of the destination schema that are present in the source schema too must be assignable.
// Slots of the destination schema not present in the source schema must be OPTIONAL.
ObjectSchema destSchema;
ObjectSchema srcSchema;
String[] destSlotNames = getNames();
for (int i = 0; i < destSlotNames.length; i++) {
String destSlotName = destSlotNames[i];
destSchema = getSchema(destSlotName);
try {
srcSchema = s.getSchema(destSlotName);
// The slot is present in source and destination schema -> check compatibility
if (!destSchema.isAssignableFrom(srcSchema)) {
return false;
}
} catch (OntologyException e) {
// The slot is present only in destination schema -> must be not mandatory
if (isMandatory(destSlotName)) {
return false;
}
}
}
return true;
} catch (OntologyException e) {
return false;
}
}
}