1 #ifndef OPENSIM_ABSTRACT_PROPERTY_H_ 2 #define OPENSIM_ABSTRACT_PROPERTY_H_ 3 /* -------------------------------------------------------------------------- * 4 * OpenSim: AbstractProperty.h * 5 * -------------------------------------------------------------------------- * 6 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * 7 * See http://opensim.stanford.edu and the NOTICE file for more information. * 8 * OpenSim is developed at Stanford University and supported by the US * 9 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * 10 * through the Warrior Web program. * 11 * * 12 * Copyright (c) 2005-2017 Stanford University and the Authors * 13 * Author(s): Frank C. Anderson, Cassidy Kelly, Ajay Seth, Michael A. Sherman * 14 * * 15 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 16 * not use this file except in compliance with the License. You may obtain a * 17 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 18 * * 19 * Unless required by applicable law or agreed to in writing, software * 20 * distributed under the License is distributed on an "AS IS" BASIS, * 21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 22 * See the License for the specific language governing permissions and * 23 * limitations under the License. * 24 * -------------------------------------------------------------------------- */ 25 26 // INCLUDES 27 #include <assert.h> 28 #include <string> 29 #include <typeinfo> 30 #include "osimCommonDLL.h" 31 #include "Exception.h" 32 #include "SimTKcommon/internal/Xml.h" 33 34 35 namespace OpenSim { 36 37 class Object; 38 template <class T> class Property; 39 40 //============================================================================== 41 /// Property Exceptions 42 //============================================================================== 43 class InvalidPropertyValue : public Exception { 44 public: InvalidPropertyValue(const std::string & file,size_t line,const std::string & func,const Object & obj,const std::string & propertyName,const std::string & errorMsg)45 InvalidPropertyValue(const std::string& file, 46 size_t line, 47 const std::string& func, 48 const Object& obj, 49 const std::string& propertyName, 50 const std::string& errorMsg) : 51 Exception(file, line, func, obj) { 52 std::string msg = "Property '" + propertyName; 53 msg += "' has an invalid value.\n(details: " + errorMsg + ").\n"; 54 addMessage(msg); 55 } 56 }; 57 58 59 //============================================================================== 60 // ABSTRACT PROPERTY 61 //============================================================================== 62 /** An abstract property is a serializable (name,value) pair, for which we 63 do not know the type of the value. Values may be simple types like int or 64 string, or may be serializable objects derived from the %OpenSim Object class. 65 66 %AbstractProperty is an abstract base class that provides the functionality 67 common to all properties that does not involve knowledge of the value 68 type. Property\<T> derives from %AbstractProperty to represent properties 69 where the type is known. 70 71 @see Property, Object 72 73 @author Cassidy Kelly, Ajay Seth, Michael Sherman 74 **/ 75 class OSIMCOMMON_API AbstractProperty { 76 public: 77 // Constructors are protected. 78 79 /** Require that the number of values n in the value list of this property 80 be in the range aMin <= n <= aMax. */ setAllowableListSize(int aMin,int aMax)81 void setAllowableListSize(int aMin, int aMax) 82 { assert(0 <= aMin && aMin <= aMax); 83 _minListSize = aMin; _maxListSize = aMax; } 84 85 /** Require that the number of values n in the value list of this property 86 be exactly n=aNum values. **/ setAllowableListSize(int aNum)87 void setAllowableListSize(int aNum) 88 { assert(aNum >= 1); _minListSize = _maxListSize = aNum; } 89 90 // Default copy constructor and copy assignment operator. 91 92 /** Return all heap space used by this property. **/ ~AbstractProperty()93 virtual ~AbstractProperty() {} 94 95 /** Return a new instance of this concrete property object, containing 96 new copies of this property's values. The new property object is 97 allocated on the heap and it is up to the caller to delete it when done. **/ 98 virtual AbstractProperty* clone() const = 0; 99 100 /** For relatively simple types, return the current value of this property 101 in a string suitable for displaying to a user in the GUI (i.e., this number 102 may be rounded and not an exact representation of the actual value being 103 used). Objects just return something like "(Object)". 104 For `Property`s, This function calls `toStringForDisplay()` with 105 `precision = 6`.**/ 106 virtual std::string toString() const = 0; 107 108 /** For relatively simple types, return the current value of this property 109 in a string suitable for displaying to a user in the GUI (i.e., this number 110 may be rounded and not an exact representation of the actual value being 111 used). Objects just return something like "(Object)". This differs from 112 `toString()` as it has an argument, `precision`, for controlling the number 113 of digits printed to string for floats. If this function is not overridden 114 in a derived class, this function uses `toString()` and the `precision` 115 argument is ignored. 116 For `Property`s, in general, this means that floats will 117 be represented with the number of significant digits denoted by the 118 `precision` argument, and the default formatting of `stringstream` 119 determines whether or not exponential notation is used. **/ toStringForDisplay(const int precision)120 virtual std::string toStringForDisplay(const int precision) const 121 { return toString(); } 122 123 /** This returns a string representation of this property's value type 124 which will be the same as T::getClassName() for Object-derived types T, and 125 some reasonably nice name for simple types, including at least "bool", 126 "int", "double", "string", "Vec3", "Vector", and "Transform". **/ 127 virtual std::string getTypeName() const = 0; 128 129 /** Return true if this is an "object property", meaning that its values 130 are all concrete objects of types that ultimately derive from the %OpenSim 131 serializable base class Object. If this returns true then it is safe to 132 call getValueAsObject(). Otherwise this property contains only simple types 133 like "int" or "std::string", and you'll need to know the actual type in 134 order to access the values. **/ 135 virtual bool isObjectProperty() const = 0; 136 137 /** An unnamed property is a one-object property whose name was given as 138 null or as the contained object's type tag. In that case getName() will 139 return the object type tag, and the XML representation will just be the 140 object, with name attribute ignored if there is one. **/ 141 virtual bool isUnnamedProperty() const = 0; 142 143 /** Compare this property with another one; this is primarily used 144 for testing. The properties must be of the identical concrete type, and 145 their names and other base class attributes must 146 be identical (including the comment). If they both have the "use default" 147 flag set then we consider the values identical without looking. Otherwise, 148 we delegate to the concrete property to determine if the values are equal; 149 the meaning is determined by the concrete property depending on its type. 150 Floating point values should be compared to a tolerance, and should be 151 considered equal if both are the same infinity or both are NaN (the latter 152 in contrast to normal IEEE floating point behavior, where NaN!=NaN). **/ equals(const AbstractProperty & other)153 bool equals(const AbstractProperty& other) const 154 { if (!isSamePropertyClass(other)) return false; 155 if (getName() != other.getName()) return false; 156 if (getComment() != other.getComment()) return false; 157 if (getMinListSize() != other.getMinListSize()) return false; 158 if (getMaxListSize() != other.getMaxListSize()) return false; 159 160 if (size() != other.size()) return false; 161 // Note: we're delegating comparison of the "use default" flags 162 // because only the new Property system copies them correctly, so 163 // only it should compare them. 164 return isEqualTo(other); // delegate to concrete property 165 } 166 167 /** Return true if the \a other property is an object of exactly the same 168 concrete class as this one. **/ isSamePropertyClass(const AbstractProperty & other)169 bool isSamePropertyClass(const AbstractProperty& other) const 170 { return typeid(*this) == typeid(other); } 171 172 173 #ifndef SWIG 174 /** See the equals() method for the meaning of this operator. **/ 175 bool operator==(const AbstractProperty& other) const 176 { return equals(other); } 177 #endif 178 179 /**@name Container interface 180 A property can be viewed as a random-access container of values. These 181 methods provide a subset of the usual container methods modeled after 182 std::vector. Note that any methods involving the actual property value type 183 T must be templatized; they will be delegated to the concrete Property\<T> 184 for resolution. **/ 185 /**@{**/ 186 /** Return the number of values currently in this property's value list. **/ size()187 int size() const {return getNumValues();} 188 /** Return true if this property's value list is currently empty. **/ empty()189 bool empty() const {return size()==0;} 190 /** Empty the value list for this property; fails if zero is not an 191 allowable size for this property. **/ 192 void clear(); 193 194 /** For an object property, the values can be obtained as references to 195 the abstract base class Object from which all the objects derive. If the 196 property can hold a list of values you must provide an index to select 197 the value, otherwise it is optional but if supplied must be 0. This will 198 throw an exception if this is not an object property, that is, if it is 199 a simple property, because its values can't be represented as an Object in 200 that case. 201 202 @param[in] index If supplied must be 0 <= index < getNumValues(). 203 @returns const reference to the value as an Object 204 @see updValueAsObject(), getValue\<T>() **/ 205 virtual const Object& getValueAsObject(int index=-1) const = 0; 206 /** Get writable access to an existing object value. Note that you can't 207 use this to install a different concrete object; see setValueAsObject() 208 if you want to do that. 209 @param[in] index If supplied must be 0 <= index < getNumValues(). 210 @returns writable reference to the value as an Object 211 @see getValueAsObject(), updValue\<T>() **/ 212 virtual Object& updValueAsObject(int index=-1) = 0; 213 /** %Set the indicated value element to a new copy of the supplied object. 214 If you already have a heap-allocated object you're willing to give up and 215 want to avoid the extra copy, use adoptValueObject(). **/ 216 virtual void setValueAsObject(const Object& obj, int index=-1) = 0; 217 // Implementation of these non-virtual templatized methods must be 218 // deferred until the concrete property declarations are known. 219 // See Object.h. 220 221 /** Return one of the values in this property as type T; this works only 222 if the underlying concrete property stores type T and if the indexed 223 element is present, otherwise throws an exception. **/ 224 template <class T> const T& getValue(int index=-1) const; 225 /** Return a writable reference to one of the values in this property as 226 type T; this works only if the underlying concrete property is actually of 227 type T and the indexed element is present. Otherwise it throws an exception. **/ 228 template <class T> T& updValue(int index=-1); 229 /** Append a new value of type T to the end of the list of values currently 230 contained in this property. This works only if the underlying concrete 231 property is of type T, the property holds a variable-length list, and the 232 list isn't already of maximum size. 233 @returns The index assigned to this value in the list. **/ 234 template <class T> int appendValue(const T& value); 235 236 /** Assign (copy) property *that* to this object. */ 237 virtual void assign(const AbstractProperty& that) = 0; 238 /**@}**/ 239 240 /** This method sets the "use default" flag for this property and the 241 properties of any objects it contains to the given value. **/ 242 void setAllPropertiesUseDefault(bool shouldUseDefault); 243 244 /** Given an XML parent element expected to contain a value for this 245 property as an immediate child element, find that property element and set 246 the property value from it. If no such property element can be found, the 247 "use default value" attribute of this property will be set on return; that 248 is not an error. However, if the property element is found but is 249 malformed or unsuitable in some way, an exception will be thrown with 250 a message explaining what is wrong. **/ 251 void readFromXMLParentElement(SimTK::Xml::Element& parent, 252 int versionNumber); 253 254 /** Given an XML parent element, append a single child element representing 255 the serialized form of this property. **/ 256 void writeToXMLParentElement(SimTK::Xml::Element& parent) const; 257 258 259 /** %Set the property name. **/ setName(const std::string & name)260 void setName(const std::string& name){ _name = name; } 261 262 /** %Set a user-friendly comment to be associated with property. This will 263 be displayed in XML and in "help" output for %OpenSim Objects. **/ setComment(const std::string & aComment)264 void setComment(const std::string& aComment){ _comment = aComment; } 265 266 /** %Set flag indicating whether the value of this property was simply 267 taken from a default object and thus should not be written out when 268 serializing. **/ setValueIsDefault(bool isDefault)269 void setValueIsDefault(bool isDefault) { _valueIsDefault = isDefault; } 270 271 /** Get the property name. **/ getName()272 const std::string& getName() const { return _name; } 273 /** Get the comment associated with this property. **/ getComment()274 const std::string& getComment() const { return _comment; } 275 /** Get the flag indicating whether the current value is just the default 276 value for this property (in which case it doesn't need to be written 277 out). **/ getValueIsDefault()278 bool getValueIsDefault() const { return _valueIsDefault; } 279 280 /** Get the minimum number of values allowed in this property's value 281 list. Will be zero for optional properties, zero for list properties 282 (unless explicitly changed), and one for one-value properties. **/ getMinListSize()283 int getMinListSize() const { return _minListSize; } 284 /** Get the maximum number of values allowed in this property's value 285 list. Will be unlimited for list properties (unless explicitly changed), 286 and one for optional and one-value properties. **/ getMaxListSize()287 int getMaxListSize() const { return _maxListSize; } 288 289 /** This is an "optional" property if its value list can contain at most 290 one value. This is the kind of property created by the 291 Object::addOptionalProperty\<T> method, for any type T. **/ isOptionalProperty()292 bool isOptionalProperty() const 293 { return getMinListSize()==0 && getMaxListSize()==1; } 294 295 /** This is a "list" property if its value list can contain more than 296 one value. This is the kind of property created by the 297 Object::addListProperty\<T> method, for any type T. **/ isListProperty()298 bool isListProperty() const 299 { return getMaxListSize()>1; } 300 301 /** This is a "one-value" property if its value list must always contain 302 exactly one value. This is the kind of property created by the 303 Object::addProperty\<T> method, for any type T. **/ isOneValueProperty()304 bool isOneValueProperty() const 305 { return getMinListSize()==1 && getMaxListSize()==1; } 306 307 /** This is a "one-object" property if it is a "one-value" property and 308 it contains an Object-derived value. This is the kind of property created 309 by the Object::addProperty\<T> method when T is a type derived from 310 %OpenSim's Object serializable base class. One-object properties have 311 a special, compact representation in XML. **/ isOneObjectProperty()312 bool isOneObjectProperty() const 313 { return isOneValueProperty() && isObjectProperty(); } 314 315 316 protected: 317 AbstractProperty(); 318 AbstractProperty(const std::string& name, 319 const std::string& comment); 320 AbstractProperty(const AbstractProperty&) = default; 321 AbstractProperty(AbstractProperty&&) = default; 322 AbstractProperty& operator=(const AbstractProperty&) = default; 323 AbstractProperty& operator=(AbstractProperty&&) = default; 324 325 // This is the remainder of the interface that a concrete Property class 326 // must implement, hidden from AbstractProperty users. 327 //-------------------------------------------------------------------------- 328 /** The base class equals() method will have already done a lot of checking 329 prior to calling this method, including verifying that both values are 330 non-default and that the value lists are the same size; the concrete 331 property need only compare the values.**/ 332 virtual bool isEqualTo(const AbstractProperty& other) const = 0; 333 334 /** Read in a new value for this property from the XML element 335 \a propertyElement. The element is expected to have the form 336 @code 337 <propertyName> value(s) </propertyName> 338 @endcode 339 where the values may be simple (like int or double) or may be objects 340 contained in child elements for which the object type name serves as 341 the element tag. Note that although the XML file may contain an abbreviated 342 representation for one-object properties, it will have been canonicalized 343 into the above form for the purpose of reading, so concrete properties may 344 assume the above form always. 345 346 The format for the property value (and any of its contained objects) is 347 assumed to be the one that was in use when the given ".osim" file version 348 number was current; if necessary the in-memory version will be updated to 349 the now-current format. 350 351 If this is an object property, the contained objects will be asked 352 recursively to read themselves in from the same document. However, any 353 object that has the "file" attribute will read in its contents from that 354 file rather than from the supplied XML document, and the version number 355 will be taken from that file rather than the argument supplied here. **/ 356 virtual void readFromXMLElement 357 (SimTK::Xml::Element& propertyElement, 358 int versionNumber) = 0; 359 360 /** Output a serialized representation of this property by writing its 361 value to the given XML property element. If the "use default value" 362 attribute is set for this property (meaning we don't have a meaningful 363 value for it) then you should not call this method unless you are trying 364 to serialize the defaults. Note that this method unconditionally 365 serializes the property; it does not check to see whether it should. 366 367 This method is not called for the special case of a one-object property, 368 in which case only the object is written to the XML file (without the 369 property element). In all other cases (simple property or property 370 containing an array of objects), the format is 371 @code 372 <propertyName> value(s) </propertyName> 373 @endcode 374 and that is the only format produced here since the empty-valued property 375 element is supplied (with the property name as its tag). **/ 376 virtual void writeToXMLElement 377 (SimTK::Xml::Element& propertyElement) const = 0; 378 379 380 /** How may values are currently stored in this property? If this is an 381 object property you can use this with getValueAsObject() to iterate over 382 the contained objects. **/ 383 virtual int getNumValues() const = 0; 384 385 /** If the concrete property allows it, clear the value list. **/ 386 virtual void clearValues() = 0; 387 388 389 /** Return true if the given string is the XML tag name for one of the 390 Object-derived types that is allowed by this property. If so, we expect that 391 an element with that tag could be deserialized into a value element of this 392 property. This always returns false for a simple property. **/ 393 virtual bool isAcceptableObjectTag 394 (const std::string& objectTypeTag) const = 0; 395 396 //-------------------------------------------------------------------------- 397 398 private: 399 void setNull(); 400 401 std::string _name; 402 std::string _comment; 403 bool _valueIsDefault; // current value is just the default 404 405 int _minListSize; // minimum # values for property 406 int _maxListSize; // maximum # value for property 407 }; 408 409 410 }; //namespace 411 //============================================================================= 412 //============================================================================= 413 414 #endif // OPENSIM_ABSTRACT_PROPERTY_H_ 415