/** * A class representing the record property. * * Copyright (c) 2008-2009 The Regents of the University of California. All * rights reserved. Permission is hereby granted, without written agreement and * without license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies of * this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.data.properties.lattice; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import ptolemy.data.RecordToken; import ptolemy.data.properties.Property; import ptolemy.graph.CPO; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; ////////////////////////////////////////////////////////////////////////// //// RecordProperty /** * A class representing the property of a RecordToken. To set the property of a * propertyable object (such as a port or parameter) to a record with particular * fields, create an instance of this class and call setEquals() with that * instance as an argument. * <p> * Note that a record property with more fields is a subproperty of a record * property with a subset of the fields. For example, {x = double, y = int} is a * subproperty of {x = double}. When a record of property {x = double, y = int} * is converted to one of property {x = double}, the extra field is discarded. * The converted record, therefore, will have exactly the fields in the * property. * <p> * A consequence of this is that all record properties are subproperties of the * empty record property. Hence, to require that a propertyable object be a * record property without specifying what the fields are, use * * <pre> * propertyable.setAtMost(new RecordProperty(new String[0], new Property[0])); * </pre> * * Note, however, that by itself this property constraint will not be useful * because it does not, by itself, prevent the property from resolving to * unknown (the unknown property is at the bottom of the property lattice, and * hence satisfies this property constraint). * * @author Man-Kit Leung * @version $Id: RecordProperty.java 49948 2008-06-24 20:46:43Z eal $ * @since Ptolemy II 7.1 * @Pt.ProposedRating Red (mankit) * @Pt.AcceptedRating Red (mankit) */ public class RecordProperty extends StructuredProperty implements Cloneable { /** * Construct a RecordProperty with the labels and values specified by a * given Map object. The object cannot contain any null keys or values. * @param lattice The specified lattice where this property resides. * @param fieldMap A Map that has keys of property String and values of * property Token. * @exception IllegalActionException If the map contains null keys or * values, or if it contains non-String keys or non-Property values */ public RecordProperty(PropertyLattice lattice, Map fieldMap) throws IllegalActionException { super(lattice); Iterator fields = fieldMap.entrySet().iterator(); while (fields.hasNext()) { Map.Entry entry = (Map.Entry) fields.next(); if (entry.getKey() == null || entry.getValue() == null) { throw new IllegalActionException("RecordProperty: given map" + " contains either null keys or null values."); } if (!(entry.getKey() instanceof String) || !(entry.getValue() instanceof Property)) { throw new IllegalActionException("RecordProperty: given map" + " contains either non-String keys or" + " non-Property values."); } _fields.put(entry.getKey(), new FieldProperty( (LatticeProperty) entry.getValue())); } } /** * Construct a new RecordProperty with the specified labels and properties. * To leave the properties of some fields undeclared, use * BaseProperty.UNKNOWN. The labels and the properties are specified in two * arrays. These two arrays must have the same length, and their elements * have one to one correspondence. That is, the i'th entry in the properties * array is the property for the i'th label in the labels array. To * construct the empty record property, set the length of the argument * arrays to 0. * @param lattice The specified lattice where this property resides. * @param labels An array of String. * @param properties An array of Property. * @exception IllegalArgumentException If the two arrays do not have the * same size. * @exception NullPointerException If one of the arguments is null. */ public RecordProperty(PropertyLattice lattice, String[] labels, LatticeProperty[] properties) { super(lattice); if (labels.length != properties.length) { throw new IllegalArgumentException("RecordProperty: the labels " + "and properties arrays do not have the same size."); } for (int i = 0; i < labels.length; i++) { FieldProperty fieldProperty = new FieldProperty(properties[i]); _fields.put(labels[i], fieldProperty); } } /////////////////////////////////////////////////////////////////// //// public methods //// /** * Return a deep copy of this RecordProperty if it is a variable, or itself * if it is a constant. * @return A RecordProperty. */ @Override public Object clone() { if (isConstant()) { return this; } else { // empty record is a constant, so this record property is not empty. // construct the labels and declared properties array Object[] labelsObj = _fields.keySet().toArray(); String[] labels = new String[labelsObj.length]; LatticeProperty[] properties = new LatticeProperty[labelsObj.length]; for (int i = 0; i < labels.length; i++) { labels[i] = (String) labelsObj[i]; FieldProperty fieldProperty = (FieldProperty) _fields .get(labels[i]); properties[i] = fieldProperty._declaredProperty; } RecordProperty newObj = new RecordProperty(_lattice, labels, properties); try { newObj.updateProperty(this); } catch (IllegalActionException ex) { throw new InternalErrorException( "RecordProperty.clone: Cannot " + "update new instance. " + ex.getMessage()); } return newObj; } } /** * Return the depth of a record property. The depth of a record property is * the number of times it contains other structured properties. For example, * a record of arrays has depth 2. * @return the depth of a record property. */ @Override public int depth() { Object[] labelsObj = _fields.keySet().toArray(); String[] labels = new String[labelsObj.length]; int[] depth = new int[labelsObj.length]; int maxDepth = 1; for (int i = 0; i < labels.length; i++) { labels[i] = (String) labelsObj[i]; Property fieldProperty = get(labels[i]); depth[i] = 1; if (fieldProperty instanceof StructuredProperty) { depth[i] += ((StructuredProperty) fieldProperty).depth(); } if (depth[i] > maxDepth) { maxDepth = depth[i]; } } return maxDepth; } /** * Determine if the argument represents the same RecordProperty as this * object. Two record properties are equal if they have the same field names * and the property of each field is the same. * @param object Another object. * @return True if the argument represents the same RecordProperty as this * object. */ @Override public boolean equals(Object object) { if (!(object instanceof RecordProperty)) { return false; } RecordProperty RecordProperty = (RecordProperty) object; // Check that the label sets are equal Set myLabelSet = _fields.keySet(); Set argLabelSet = RecordProperty._fields.keySet(); if (!myLabelSet.equals(argLabelSet)) { return false; } Iterator fieldNames = myLabelSet.iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); Property myProperty = get(label); Property argProperty = RecordProperty.get(label); if (!myProperty.equals(argProperty)) { return false; } } return true; } /** * Return the property of the specified label. If this property does not * contain the specified label, return null. * @param label The specified label. * @return a Property. */ public LatticeProperty get(String label) { FieldProperty fieldProperty = (FieldProperty) _fields.get(label); if (fieldProperty == null) { return null; } return fieldProperty._resolvedProperty; } /** * Return the PropertyTerm representing the property of the specified label. * @param label The specified label. * @return An PropertyTerm. */ public PropertyTerm getPropertyTerm(String label) { return (PropertyTerm) _fields.get(label); } /** * Return a static instance of RecordProperty. * @return a RecordProperty. */ public StructuredProperty getRepresentative() { Object key = _lattice.getName(); if (!_representativeMap.containsKey(key)) { _representativeMap.put(key, new RecordProperty(_lattice, new String[0], new LatticeProperty[0])); } return _representativeMap.get(key); } /** * Return the class for tokens that this property represents. * @return The class for tokens that this property represents. */ public Class getTokenClass() { return RecordToken.class; } /** * Return a hash code value for this object. */ @Override public int hashCode() { return _fields.keySet().hashCode() + 2917; } /** * Set the elements that have declared property BaseProperty.UNKNOWN (the * leaf property variable) to the specified property. * @param property the property to set the leaf property variable to. */ @Override public void initialize(Property property) { try { Iterator fieldNames = _fields.keySet().iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); FieldProperty fieldProperty = (FieldProperty) _fields .get(label); if (fieldProperty.isSettable()) { fieldProperty.initialize(property); } } } catch (IllegalActionException iae) { throw new InternalErrorException( "RecordProperty.initialize: Cannot " + "initialize the element property to " + property + " " + iae.getMessage()); } } /** * Return true if this property does not correspond to a single token class. * This occurs if the property is not instantiable, or it represents either * an abstract base class or an interface. * @return true if the property of any field is abstract. */ @Override public boolean isAbstract() { // Loop through all of the fields. Iterator fieldNames = _fields.keySet().iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); LatticeProperty property = get(label); // Return false if the field is not instantiable. if (property.isAbstract()) { return true; } } return false; } /** * Test if the argument property is compatible with this property. The given * property will be compatible with this property if it is * BaseProperty.UNKNOWN, or a RecordProperty that contains at least as many * fields. * @param property An instance of Property. * @return True if the argument is compatible with this property. */ @Override public boolean isCompatible(Property property) { if (!(property instanceof RecordProperty)) { return false; } RecordProperty argumentRecordProperty = (RecordProperty) property; // Loop through all of the fields of this property... Iterator iterator = _fields.keySet().iterator(); while (iterator.hasNext()) { String label = (String) iterator.next(); // The given property cannot be losslessly converted to this property // if it does not contain one of the fields of this property. Property argumentFieldProperty = argumentRecordProperty.get(label); if (argumentFieldProperty == null) { // argument token does not contain this label return false; } // The given property cannot be losslessly converted to this property // if the individual fields are not compatible. LatticeProperty thisFieldProperty = get(label); if (!thisFieldProperty.isCompatible(argumentFieldProperty)) { return false; } } return true; } /** * Test if this RecordProperty is a constant. A RecordProperty is a constant * if the declared property of all of its fields are constant. * @return True if this property is a constant. */ @Override public boolean isConstant() { // Loop through all of the fields. Iterator fieldProperties = _fields.values().iterator(); while (fieldProperties.hasNext()) { FieldProperty fieldProperty = (FieldProperty) fieldProperties .next(); LatticeProperty property = fieldProperty._declaredProperty; // Return false if the field is not constant. if (!property.isConstant()) { return false; } } return true; } /////////////////////////////////////////////////////////////////// //// public fields //// /** * Test if this property corresponds to an instantiable token class. A * RecordProperty is instantiable if all of its fields are instantiable. * @return True if this property is instantiable. */ @Override public boolean isInstantiable() { // Loop through all of the fields. Iterator fieldNames = _fields.keySet().iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); Property property = get(label); // Return false if the field is not instantiable. if (!property.isInstantiable()) { return false; } } return true; } /////////////////////////////////////////////////////////////////// //// protected methods //// /** * Test if the specified property is a substitution instance of this * property. One record is a substitution instance of another if they have * fields with the same names and each field of the given property is a * substitution instance of the corresponding field in this property. * @param property A Property. * @return True if the argument is a substitution instance of this property. */ public boolean isSubstitutionInstance(Property property) { if (!(property instanceof RecordProperty)) { return false; } RecordProperty RecordProperty = (RecordProperty) property; // Check if this record property and the argument have the same // label set. Set myLabelSet = _fields.keySet(); Set argLabelSet = RecordProperty._fields.keySet(); if (!myLabelSet.equals(argLabelSet)) { return false; } // Loop over all the labels. Iterator fieldNames = myLabelSet.iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); FieldProperty fieldProperty = (FieldProperty) _fields.get(label); LatticeProperty myDeclaredProperty = fieldProperty._declaredProperty; Property argProperty = RecordProperty.get(label); if (!myDeclaredProperty.isSubstitutionInstance(argProperty)) { return false; } } return true; } /** * Return the labels of this record property as a Set. * @return A Set containing strings. */ public Set labelSet() { return _fields.keySet(); } /** * Return the string representation of this property. The format is {<i>label</i> = * <i>property</i>, <i>label</i> = <i>property</i>, ...}. The record * fields are listed in the lexicographical order of the labels determined * by the java.lang.String.compareTo() method. * @return A String. */ @Override public String toString() { Object[] labelArray = _fields.keySet().toArray(); // Order the labels int size = labelArray.length; for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { String labeli = (String) labelArray[i]; String labelj = (String) labelArray[j]; if (labeli.compareTo(labelj) >= 0) { Object temp = labelArray[i]; labelArray[i] = labelArray[j]; labelArray[j] = temp; } } } // construct the string representation of this token. StringBuffer results = new StringBuffer("{"); for (int i = 0; i < size; i++) { String label = (String) labelArray[i]; String property = get(label).toString(); if (i != 0) { results.append(", "); } results.append(label + " = " + property); } return results.toString() + "}"; } /** * Update this Property to the specified RecordProperty. The specified * property must be a RecordProperty and have the same structure as this * one, and have depth less than the MAXDEPTHDOUND. This method will only * update the component whose declared property is BaseProperty.UNKNOWN, and * leave the constant part of this property intact. * @param newProperty A StructuredProperty. * @exception IllegalActionException If the specified property is not a * RecordProperty or it does not have the same structure as this one. */ public void updateProperty(StructuredProperty newProperty) throws IllegalActionException { super.updateProperty(newProperty); if (isConstant()) { if (equals(newProperty)) { return; } throw new IllegalActionException("RecordProperty.updateProperty: " + "This property is a constant and the argument is not the" + " same as this property. This property: " + toString() + " argument: " + newProperty.toString()); } // This property is a variable. if (!isSubstitutionInstance(newProperty)) { throw new IllegalActionException("RecordProperty.updateProperty: " + "Cannot update this property to the new property."); } Iterator fieldNames = _fields.keySet().iterator(); while (fieldNames.hasNext()) { String label = (String) fieldNames.next(); FieldProperty fieldProperty = (FieldProperty) _fields.get(label); if (fieldProperty.isSettable()) { Property newFieldProperty = ((RecordProperty) newProperty) .get(label); fieldProperty.setValue(newFieldProperty); } } } /////////////////////////////////////////////////////////////////// //// inner class //// // A class that encapsulates the declared and resolved properties of a // field and implements the PropertyTerm interface. private class FieldProperty implements PropertyTerm { // Construct an instance of FieldProperty. private FieldProperty(LatticeProperty declaredProperty) { try { _declaredProperty = (LatticeProperty) declaredProperty.clone(); _resolvedProperty = _declaredProperty; } catch (CloneNotSupportedException cnse) { throw new InternalErrorException( "RecordProperty.FieldProperty: " + "The specified property cannot be cloned."); } } /////////////////////////////////////////////////////////////// //// public inner methods //// /** * Return this RecordProperty. * @return a RecordProperty. */ public Object getAssociatedObject() { return RecordProperty.this; } /** * Return this FieldProperty in an array if it represents a property * constant. Otherwise, return an array of size zero. * @return An array of PropertyTerm. */ public PropertyTerm[] getConstants() { if (!isSettable()) { PropertyTerm[] variable = new PropertyTerm[1]; variable[0] = this; return variable; } return new PropertyTerm[0]; } /** * Return the resolved property. * @return a Property. */ public Object getValue() { return _resolvedProperty; } /** * Return this FieldProperty in an array if it represents a property * variable. Otherwise, return an array of size zero. * @return An array of PropertyTerm. */ public PropertyTerm[] getVariables() { if (isSettable()) { PropertyTerm[] variable = new PropertyTerm[1]; variable[0] = this; return variable; } return new PropertyTerm[0]; } /** * Reset the variable part of the element property to the specified * property. * @param property A Property. * @exception IllegalActionException If this property is not settable, * or the argument is not a Property. */ public void initialize(Object property) throws IllegalActionException { if (!isSettable()) { throw new IllegalActionException( "RecordProperty$FieldProperty." + "initialize: The property is not settable."); } if (!(property instanceof Property)) { throw new IllegalActionException("FieldProperty.initialize: " + "The argument is not a Property."); } // if (_declaredProperty == BaseProperty.UNKNOWN) { if (_declaredProperty == _lattice.bottom()) { _resolvedProperty = (LatticeProperty) property; } else { // this field property is a structured property. ((StructuredProperty) _resolvedProperty) .initialize((Property) property); } } /** * Return true. * @return True. */ public boolean isEffective() { return true; } /** * Test if this field property is a property variable. * @return True if this field property is a property variable. */ public boolean isSettable() { return !_declaredProperty.isConstant(); } /** * Check whether the current element property is acceptable. The element * property is acceptable if it represents an instantiable object. * @return True if the element property is acceptable. */ public boolean isValueAcceptable() { return _resolvedProperty.isInstantiable(); } /** * Set the effectiveness of this property term to the specified value. Do * nothing in this base by default. * @param isEffective The specified effective value, not used by this class. */ public void setEffective(boolean isEffective) { } /** * Set the element property to the specified property. * @param e a Property. * @exception IllegalActionException If the specified property violates * the declared field property. */ public void setValue(Object e) throws IllegalActionException { if (!isSettable()) { throw new IllegalActionException( "RecordProperty$FieldProperty.setValue: The property is not " + "settable."); } if (!_declaredProperty.isSubstitutionInstance((Property) e)) { throw new IllegalActionException( "FieldProperty.setValue: " + "Cannot update the field property of this RecordProperty " + "to the new property." + " Field property: " + _declaredProperty.toString() + ", New property: " + e.toString()); } //if (_declaredProperty == BaseProperty.UNKNOWN) { if (_declaredProperty == _lattice.bottom()) { try { _resolvedProperty = (LatticeProperty) ((LatticeProperty) e) .clone(); } catch (CloneNotSupportedException cnse) { throw new InternalErrorException( "RecordProperty$FieldProperty.setValue: " + "The specified property cannot be cloned."); } } else { ((StructuredProperty) _resolvedProperty) .updateProperty((StructuredProperty) e); } } /** * Return a string representation of this term. * @return A String. */ public String toString() { return "(RecordFieldProperty, " + getValue() + ")"; } /////////////////////////////////////////////////////////////// //// private inner variables //// private LatticeProperty _declaredProperty = null; private LatticeProperty _resolvedProperty = null; } /////////////////////////////////////////////////////////////////// //// protected methods //// /** * Compare this property with the specified property. The specified property * must be a RecordProperty, otherwise an exception will be thrown. * * This method returns one of ptolemy.graph.CPO.LOWER, * ptolemy.graph.CPO.SAME, ptolemy.graph.CPO.HIGHER, * ptolemy.graph.CPO.INCOMPARABLE, indicating this property is lower than, * equal to, higher than, or incomparable with the specified property in the * property hierarchy, respectively. * @param property a RecordProperty. * @return An integer. * @exception IllegalArgumentException If the specified property is not a * RecordProperty. */ @Override protected int _compare(StructuredProperty property) { if (!(property instanceof RecordProperty)) { throw new IllegalArgumentException("RecordProperty._compare: " + "The argument is not a RecordProperty."); } if (equals(property)) { return CPO.SAME; } if (_isLessThanOrEqualTo(this, (RecordProperty) property)) { return CPO.LOWER; } if (_isLessThanOrEqualTo((RecordProperty) property, this)) { return CPO.HIGHER; } return CPO.INCOMPARABLE; } /** * Return the greatest lower bound of this property with the specified * property. The specified property must be a RecordProperty, otherwise an * exception will be thrown. * @param property a RecordProperty. * @return a RecordProperty. * @exception IllegalArgumentException If the specified property is not a * RecordProperty. */ @Override protected StructuredProperty _greatestLowerBound(StructuredProperty property) { // FIXME: we should consider the case where the two RecordProperty // are incomparable. if (!(property instanceof RecordProperty)) { throw new IllegalArgumentException( "RecordProperty.greatestLowerBound: The argument is not a " + "RecordProperty."); } RecordProperty RecordProperty = (RecordProperty) property; // the label set of the GLB is the union of the two label sets. Set unionSet = new HashSet(); Set myLabelSet = _fields.keySet(); Set argLabelSet = RecordProperty._fields.keySet(); unionSet.addAll(myLabelSet); unionSet.addAll(argLabelSet); // construct the GLB RecordToken Object[] labelArray = unionSet.toArray(); int size = labelArray.length; String[] labels = new String[size]; LatticeProperty[] properties = new LatticeProperty[size]; for (int i = 0; i < size; i++) { labels[i] = (String) labelArray[i]; LatticeProperty property1 = get(labels[i]); LatticeProperty property2 = RecordProperty.get(labels[i]); if (property1 == null) { properties[i] = property2; } else if (property2 == null) { properties[i] = property1; } else { properties[i] = (LatticeProperty) _lattice.greatestLowerBound( property1, property2); } } return new RecordProperty(_lattice, labels, properties); } /** * Return the least Upper bound of this property with the specified * property. The specified property must be a RecordProperty, otherwise an * exception will be thrown. * @param property a RecordProperty. * @return a RecordProperty. * @exception IllegalArgumentException If the specified property is not a * RecordProperty. */ protected StructuredProperty _leastUpperBound(StructuredProperty property) { // FIXME: we should consider the case where the two RecordProperty // are incomparable. if (!(property instanceof RecordProperty)) { throw new IllegalArgumentException( "RecordProperty.leastUpperBound: " + "The argument is not a RecordProperty."); } RecordProperty RecordProperty = (RecordProperty) property; // the label set of the LUB is the intersection of the two label sets. Set intersectionSet = new HashSet(); Set myLabelSet = _fields.keySet(); Set argLabelSet = RecordProperty._fields.keySet(); intersectionSet.addAll(myLabelSet); intersectionSet.retainAll(argLabelSet); // construct the GLB RecordToken Object[] labelArray = intersectionSet.toArray(); int size = labelArray.length; String[] labels = new String[size]; LatticeProperty[] properties = new LatticeProperty[size]; for (int i = 0; i < size; i++) { labels[i] = (String) labelArray[i]; LatticeProperty property1 = get(labels[i]); LatticeProperty property2 = RecordProperty.get(labels[i]); properties[i] = (LatticeProperty) _lattice.leastUpperBound( property1, property2); } return new RecordProperty(_lattice, labels, properties); } /////////////////////////////////////////////////////////////////// //// private methods //// // Test if the first RecordProperty is less than or equal to the second private boolean _isLessThanOrEqualTo(RecordProperty t1, RecordProperty t2) { Set labelSet1 = t1._fields.keySet(); Set labelSet2 = t2._fields.keySet(); if (!labelSet1.containsAll(labelSet2)) { return false; } // iterate over the labels of the second property Iterator iter = labelSet2.iterator(); while (iter.hasNext()) { String label = (String) iter.next(); Property property1 = t1.get(label); Property property2 = t2.get(label); int result = _lattice.compare(property1, property2); if (result == CPO.HIGHER || result == CPO.INCOMPARABLE) { return false; } } return true; } /////////////////////////////////////////////////////////////////// //// private variables //// /** Mapping from label to field information. */ private final Map _fields = new HashMap(); /** Mapping the representative record to lattice. */ private static Map<Object, RecordProperty> _representativeMap = new HashMap<Object, RecordProperty>(); }