1 /* -------------------------------------------------------------------------- *
2 * OpenSim: Object.cpp *
3 * -------------------------------------------------------------------------- *
4 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
5 * See http://opensim.stanford.edu and the NOTICE file for more information. *
6 * OpenSim is developed at Stanford University and supported by the US *
7 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
8 * through the Warrior Web program. *
9 * *
10 * Copyright (c) 2005-2017 Stanford University and the Authors *
11 * Author(s): Frank C. Anderson *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 /* Note: This code was originally developed by Realistic Dynamics Inc.
25 * Author: Frank C. Anderson
26 */
27
28
29 //============================================================================
30 // INCLUDES
31 //============================================================================
32
33 #include "Object.h"
34 #include "XMLDocument.h"
35 #include "Exception.h"
36 #include "Property_Deprecated.h"
37 #include "PropertyTransform.h"
38 #include "IO.h"
39
40 #include <fstream>
41
42 using namespace OpenSim;
43 using namespace std;
44 using SimTK::Vec3;
45 using SimTK::Transform;
46
47 //=============================================================================
48 // STATICS
49 //=============================================================================
50 ArrayPtrs<Object> Object::_registeredTypes;
51 std::map<string,Object*> Object::_mapTypesToDefaultObjects;
52 std::map<string,string> Object::_renamedTypesMap;
53
54 bool Object::_serializeAllDefaults=false;
55 const string Object::DEFAULT_NAME(ObjectDEFAULT_NAME);
56 int Object::_debugLevel = 0;
57
58 //=============================================================================
59 // CONSTRUCTOR(S)
60 //=============================================================================
61 //_____________________________________________________________________________
62 /**
63 * Destructor.
64 */
~Object()65 Object::~Object()
66 {
67 delete _document;
68 }
69
70 //_____________________________________________________________________________
71 /**
72 * Default constructor.
73 */
Object()74 Object::Object()
75 {
76 setNull();
77 }
78
79 //_____________________________________________________________________________
80 /**
81 * Construct an object from file.
82 *
83 * The object is constructed from the root element of the XML document.
84 * The type of object is the tag name of the XML root element.
85 *
86 * @param aFileName File name of the document.
87 */
Object(const string & aFileName,bool aUpdateFromXMLNode)88 Object::Object(const string &aFileName, bool aUpdateFromXMLNode)
89 {
90 // INITIALIZATION
91 setNull();
92
93 // CREATE DOCUMENT
94 // Check file exists before trying to parse it. Is there a faster way to do this?
95 // This maybe slower than we like but definitely faster than
96 // going all the way down to the parser to throw an exception for null document!
97 // -Ayman 8/06
98 OPENSIM_THROW_IF(aFileName.empty(), Exception,
99 getClassName() +
100 ": Cannot construct from empty filename. No filename specified.");
101
102 OPENSIM_THROW_IF(!ifstream(aFileName.c_str(), ios_base::in).good(),
103 Exception,
104 getClassName() + ": Cannot open file " + aFileName +
105 ". It may not exist or you do not have permission to read it.");
106
107 _document = new XMLDocument(aFileName);
108
109 // GET DOCUMENT ELEMENT
110 SimTK::Xml::Element myNode = _document->getRootDataElement(); //either actual root or node after OpenSimDocument
111
112 // UPDATE OBJECT
113 // Set current working directory to directory in which we found
114 // the XML document so that contained file names will be interpreted
115 // relative to that directory. Make sure we switch back properly in case
116 // of an exception.
117 if (aUpdateFromXMLNode) {
118 const string saveWorkingDirectory = IO::getCwd();
119 const string directoryOfXMLFile = IO::getParentDirectory(aFileName);
120 IO::chDir(directoryOfXMLFile);
121 try {
122 updateFromXMLNode(myNode, _document->getDocumentVersion());
123 } catch (...) {
124 IO::chDir(saveWorkingDirectory);
125 throw; // re-issue the exception
126 }
127 IO::chDir(saveWorkingDirectory);
128 }
129 }
130 //_____________________________________________________________________________
131 /**
132 * Copy constructor.
133 *
134 * Copy constructors for all Object's only copy the non-XML variable
135 * members of the object; that is, the object's DOMnode and XMLDocument
136 * are not copied but set to NULL. The reason for this is that for the
137 * object and all its derived classes to establish the correct connection
138 * to the XML document nodes, the object would need to reconstruct based
139 * on the XML document not the values of the object's member variables.
140 *
141 * There are three proper ways to generate an XML document for an Object:
142 *
143 * 1) Construction based on XML file (@see Object(const char *aFileName)).
144 * In this case, the XML document is created by parsing the XML file.
145 *
146 * 2) Construction by Object(const XMLDocument *aDocument).
147 * This constructor explicitly requests construction based on an
148 * XML document. In this way the proper connection between an object's node
149 * and the corresponding node within the XML document is established.
150 * This constructor is a copy constructor of sorts because all essential
151 * Object member variables should be held within the XML document.
152 * The advantage of this style of construction is that nodes
153 * within the XML document, such as comments that may not have any
154 * associated Object member variable, are preserved.
155 *
156 * 3) A call to generateXMLDocument().
157 * This method generates an XML document for the Object from scratch.
158 * Only the essential document nodes are created (that is, nodes that
159 * correspond directly to member variables.).
160 *
161 * @param aObject Object to be copied.
162 * @see Object(const XMLDocument *aDocument)
163 * @see Object(const char *aFileName)
164 * @see generateXMLDocument()
165 */
Object(const Object & aObject)166 Object::Object(const Object &aObject)
167 {
168 setNull();
169
170 // Use copy assignment operator to copy simple data members and the
171 // property table; XML document is not copied and the new object is
172 // marked "inlined", meaning it is not associated with an XML document.
173 *this = aObject;
174 }
175
Object(SimTK::Xml::Element & aNode)176 Object::Object(SimTK::Xml::Element& aNode)
177 {
178 setNull();
179 updateFromXMLNode(aNode, -1);
180 }
181
182 //-----------------------------------------------------------------------------
183 // COPY ASSIGNMENT
184 //-----------------------------------------------------------------------------
185 /**
186 * Assign this object to the values of another. The XML-associated variable
187 * members are not copied-- the XML nodes and/or document must be generated
188 * anew for a copied object.
189 *
190 * @return Reference to this object.
191 * @see updateXMLNode()
192 */
operator =(const Object & source)193 Object& Object::operator=(const Object& source)
194 {
195 if (&source != this) {
196 _name = source._name;
197 _description = source._description;
198 _authors = source._authors;
199 _references = source._references;
200 _propertyTable = source._propertyTable;
201
202 delete _document; _document = NULL;
203 _inlined = true; // meaning: not associated to an XML document
204 }
205 return *this;
206 }
207
208
209 //=============================================================================
210 // CONSTRUCTION METHODS
211 //==============================================================================
212 //_____________________________________________________________________________
213 /**
214 * Set all non-static member variables to their null or default values.
215 */
setNull()216 void Object::setNull()
217 {
218 _propertySet.clear();
219 _propertyTable.clear();
220 _objectIsUpToDate = false;
221
222 _name = "";
223 _description = "";
224 _authors = "";
225 _references = "";
226
227 _document = NULL;
228 _inlined = true;
229 }
230
231 //-----------------------------------------------------------------------------
232 // EQUALITY
233 //-----------------------------------------------------------------------------
234 // Compare the base class mundane data members, and the properties. Concrete
235 // Objects should override this but they must make sure to invoke the base
236 // operator.
operator ==(const Object & other) const237 bool Object::operator==(const Object& other) const
238 {
239 auto printDiff = [](const std::string& name,
240 const std::string& thisValue,
241 const std::string& otherValue) {
242 if (Object::getDebugLevel() > 0) {
243 std::cout << "In Object::operator==(), differing " << name << ":\n"
244 << "left: " << thisValue
245 << "\nright: " << otherValue << std::endl;
246 }
247
248 };
249 if (getConcreteClassName() != other.getConcreteClassName()) {
250 printDiff("ConcreteClassName", getConcreteClassName(),
251 other.getConcreteClassName());
252 return false;
253 }
254 if (getName() != other.getName()) {
255 printDiff("name", getName(), other.getName());
256 return false;
257 }
258 if (getDescription() != other.getDescription()) {
259 printDiff("description", getDescription(), other.getDescription());
260 return false;
261 }
262 if (getAuthors() != other.getAuthors()) {
263 printDiff("authors", getAuthors(), other.getAuthors());
264 return false;
265 }
266 if (getReferences() != other.getReferences()) {
267 printDiff("references", getReferences(), other.getReferences());
268 return false;
269 }
270
271 // Must have the same number of properties, in the same order.
272 const int numProps = getNumProperties();
273 if (other.getNumProperties() != numProps) {
274 printDiff("number of properties", std::to_string(numProps),
275 std::to_string(other.getNumProperties()));
276 return false;
277 }
278
279 for (int px = 0; px < numProps; ++px) {
280 const AbstractProperty& myProp = getPropertyByIndex(px);
281 const AbstractProperty& otherProp = other.getPropertyByIndex(px);
282
283 if (!myProp.equals(otherProp)) {
284 printDiff("property '" + myProp.getName() + "'",
285 myProp.toString(), otherProp.toString());
286 return false;
287 }
288 }
289
290 return true;
291 }
292
293 //-----------------------------------------------------------------------------
294 // LESS THAN
295 //-----------------------------------------------------------------------------
296 // This Object is less than another if the name of this string is less
297 // than the name of the other Object. TODO: is that a unique ordering?
298 bool Object::
operator <(const Object & other) const299 operator<(const Object& other) const
300 {
301 return getName() < other.getName();
302 }
303
304
305 //=============================================================================
306 // GET AND SET
307 //=============================================================================
308 //-----------------------------------------------------------------------------
309 // NAME
310 //-----------------------------------------------------------------------------
311 //_____________________________________________________________________________
312 /**
313 * Set the name of this object.
314 */
315 void Object::
setName(const string & aName)316 setName(const string &aName)
317 {
318 _name = aName;
319 }
320 //_____________________________________________________________________________
321 /**
322 * Get the name of this object.
323 */
324 const string& Object::
getName() const325 getName() const
326 {
327 return(_name);
328 }
329
330 //_____________________________________________________________________________
331 /**
332 * Wrapper to be used on Java side to display objects in tree.
333 */
334 const string& Object::
toString() const335 toString() const
336 {
337 return(getName());
338 }
339
340 //-----------------------------------------------------------------------------
341 // DESCRIPTION
342 //-----------------------------------------------------------------------------
343 //_____________________________________________________________________________
344 /**
345 * Set the description of this object.
346 */
347 void Object::
setDescription(const string & aDescrip)348 setDescription(const string &aDescrip)
349 {
350 _description = aDescrip;
351 }
352 //_____________________________________________________________________________
353 /**
354 * Get the description of this object.
355 */
356 const string& Object::
getDescription() const357 getDescription() const
358 {
359 return(_description);
360 }
361
362 //=============================================================================
363 // PUBLIC PROPERTY ACCESS
364 //=============================================================================
365 // TODO: (sherm 20120315) These currently provide support for the deprecated
366 // PropertySet method of handling properties, not yet fully replaced by the
367 // PropertyTable approach. The interface here hides the fact that there are
368 // to different sets of properties -- instead, it will appear that there is
369 // a single set which will actually be all those from the PropertyTable
370 // followed by all those from the PropertySet, so that the property index of
371 // the first PropertySet property is one larger than that of the last
372 // PropertyTable property. Names will be looked up first in the PropertyTable
373 // and then in the PropertySet.
374
375 int Object::
getNumProperties() const376 getNumProperties() const {
377 return _propertyTable.getNumProperties()
378 + _propertySet.getSize(); // TODO: remove
379 }
380
381 const AbstractProperty& Object::
getPropertyByIndex(int propertyIndex) const382 getPropertyByIndex(int propertyIndex) const {
383 if (!(0 <= propertyIndex && propertyIndex < getNumProperties()))
384 throw Exception("Property index " + SimTK::String(propertyIndex)
385 + " out of range 0 <= index < "
386 + SimTK::String(getNumProperties())
387 + " for Object " + getName());
388
389 // TODO: remove deprecated code from here ...
390 if (propertyIndex >= _propertyTable.getNumProperties()) {
391 const int setIndex = propertyIndex-_propertyTable.getNumProperties();
392 return *_propertySet.get(setIndex);
393 }
394 // ... to here.
395
396 return _propertyTable.getAbstractPropertyByIndex(propertyIndex);
397 }
398
399 AbstractProperty& Object::
updPropertyByIndex(int propertyIndex)400 updPropertyByIndex(int propertyIndex) {
401 if (!(0 <= propertyIndex && propertyIndex < getNumProperties()))
402 throw Exception("Property index " + SimTK::String(propertyIndex)
403 + " out of range 0 <= index < "
404 + SimTK::String(getNumProperties())
405 + " for Object " + getName());
406
407 // A property is being modified.
408 _objectIsUpToDate = false;
409
410 // TODO: remove deprecated code from here ...
411 if (propertyIndex >= _propertyTable.getNumProperties()) {
412 const int setIndex = propertyIndex-_propertyTable.getNumProperties();
413 return *_propertySet.get(setIndex);
414 }
415 // ... to here.
416
417 return _propertyTable.updAbstractPropertyByIndex(propertyIndex);
418 }
419
420 bool Object::
hasProperty(const std::string & name) const421 hasProperty(const std::string& name) const {
422 if (name.empty())
423 throw OpenSim::Exception
424 ("Object::hasProperty(name): name cannot be empty. For looking up a "
425 "one-object, nameless property by object class name, use the other "
426 " signature hasProperty<T>() with T the expected object type.");
427
428 if (_propertyTable.hasProperty(name))
429 return true;
430
431 // TODO: remove deprecated code from here ...
432 if (_propertySet.contains(name))
433 return true;
434 // ... to here.
435
436 return false;
437 }
438
439 const AbstractProperty& Object::
getPropertyByName(const std::string & name) const440 getPropertyByName(const std::string& name) const {
441 const AbstractProperty* p = _propertyTable.getPropertyPtr(name);
442 if (p) return *p;
443
444 // TODO: remove deprecated code from here ...
445 p = _propertySet.contains(name);
446 if (p) return *p;
447 // ... to here.
448
449 throw Exception("Property '" + name + "' not present in Object "
450 + getName());
451 return *p; //NOT REACHED
452 }
453
454 AbstractProperty& Object::
updPropertyByName(const std::string & name)455 updPropertyByName(const std::string& name) {
456 // A property is being modified.
457 _objectIsUpToDate = false;
458
459 AbstractProperty* p = _propertyTable.updPropertyPtr(name);
460 if (p) return *p;
461
462 // TODO: remove deprecated code from here ...
463 p = _propertySet.contains(name);
464 if (p) return *p;
465 // ... to here.
466
467 throw Exception("Property '" + name + "' not present in Object "
468 + getName());
469 return *p; //NOT REACHED
470 }
471
472 //=============================================================================
473 // REGISTRATION
474 //=============================================================================
475 //-----------------------------------------------------------------------------
476 // REGISTER TYPE
477 //-----------------------------------------------------------------------------
478
479 //_____________________________________________________________________________
480 /*
481 * Register a supported object type. A global list of all supported objects
482 * (i.e., objects derived from Object) is kept mainly for two purposes:
483 *
484 * ---- Object Deserialization ----
485 * Once a type T is registered, that type can be read from XML files
486 * assuming that the type has implemented the following methods:
487 * 1) copy constructor
488 * 2) virtual T* clone() const
489 * 3) static const char* getClassName()
490 * 4) T& operator=()
491 *
492 * ---- Initialization by Default Object ----
493 * When objects are deserialized, they are constructed based on the registered
494 * type and receive all of the registered type's property values. These
495 * values are over-ridden only if there is an element within an XML file that
496 * overrides a default element.
497 *
498 * Because this method is static, registration of object types needs to be
499 * done only once per process and an object does not need to be
500 * instantiated to do so.
501 *
502 * This method makes a copy of the specified object.
503 *
504 * @param aObject Object of the type to be registered. If the type is
505 * already registered, the current object is replaced by a copy of
506 * the specified object.
507 * @see isValidDefault()
508 */
509 /*static*/ void Object::
registerType(const Object & aObject)510 registerType(const Object& aObject)
511 {
512 // GET TYPE
513 const string& type = aObject.getConcreteClassName();
514 if(type.empty()) {
515 printf("Object.registerType: ERR- no type name has been set.\n");
516 return;
517 }
518 if (_debugLevel>=2) {
519 cout << "Object.registerType: " << type << " .\n";
520 }
521
522 // REPLACE IF A MATCHING TYPE IS ALREADY REGISTERED
523 for(int i=0; i <_registeredTypes.size(); ++i) {
524 Object *object = _registeredTypes.get(i);
525 if(object->getConcreteClassName() == type) {
526 if(_debugLevel>=2) {
527 cout<<"Object.registerType: replacing registered object of type ";
528 cout<<type;
529 cout<<"\n\twith a new default object of the same type."<<endl;
530 }
531 Object* defaultObj = aObject.clone();
532 defaultObj->setName(DEFAULT_NAME);
533 _registeredTypes.set(i,defaultObj);
534 _mapTypesToDefaultObjects[type]= defaultObj;
535 return;
536 }
537 }
538
539 // REGISTERING FOR THE FIRST TIME -- APPEND
540 Object* defaultObj = aObject.clone();
541 defaultObj->setName(DEFAULT_NAME);
542 _registeredTypes.append(defaultObj);
543 _mapTypesToDefaultObjects[type]= defaultObj;
544 }
545
546 /*static*/ void Object::
renameType(const std::string & oldTypeName,const std::string & newTypeName)547 renameType(const std::string& oldTypeName, const std::string& newTypeName)
548 {
549 if(oldTypeName == newTypeName)
550 return;
551
552 std::map<std::string,Object*>::const_iterator p =
553 _mapTypesToDefaultObjects.find(newTypeName);
554
555 if (p == _mapTypesToDefaultObjects.end())
556 throw OpenSim::Exception(
557 "Object::renameType(): illegal attempt to rename object type "
558 + oldTypeName + " to " + newTypeName + " which is unregistered.",
559 __FILE__, __LINE__);
560
561 _renamedTypesMap[oldTypeName] = newTypeName;
562 }
563
564 /*static*/ const Object* Object::
getDefaultInstanceOfType(const std::string & objectTypeTag)565 getDefaultInstanceOfType(const std::string& objectTypeTag) {
566 std::string actualName = objectTypeTag;
567 bool wasRenamed = false; // for a better error message
568
569 // First apply renames if any.
570
571 // Avoid an infinite loop if there is a cycle in the rename table.
572 const int MaxRenames = (int)_renamedTypesMap.size();
573 int renameCount = 0;
574 while(true) {
575 std::map<std::string,std::string>::const_iterator newNamep =
576 _renamedTypesMap.find(actualName);
577 if (newNamep == _renamedTypesMap.end())
578 break; // actualName has not been renamed
579
580 if (++renameCount > MaxRenames) {
581 throw OpenSim::Exception(
582 "Object::getDefaultInstanceOfType(): cycle in rename table "
583 "found when looking for '" + objectTypeTag + "'.");
584 }
585
586 actualName = newNamep->second;
587 wasRenamed = true;
588 }
589
590 // Look up the "actualName" default object and return it.
591 std::map<std::string,Object*>::const_iterator p =
592 _mapTypesToDefaultObjects.find(actualName);
593 if (p != _mapTypesToDefaultObjects.end())
594 return p->second;
595
596 // The requested object was not registered. That's OK normally but is
597 // a bug if we went through the rename table since you are only allowed
598 // to rename things to registered objects.
599 if (wasRenamed) {
600 throw OpenSim::Exception(
601 "Object::getDefaultInstanceOfType(): '" + objectTypeTag
602 + "' was renamed to '" + actualName
603 + "' which is not the name of a registered object.");
604 }
605
606 return NULL;
607 }
608
609 /*
610 * Create a new instance of the type indicated by objectTypeTag.
611 * The instance is initialized to the default Object of corresponding type.
612 * Note that renaming of old types may occur; the returned object will have
613 * the current type tag.
614 */
615 /*static*/ Object* Object::
newInstanceOfType(const std::string & objectTypeTag)616 newInstanceOfType(const std::string& objectTypeTag)
617 {
618 const Object* defaultObj = getDefaultInstanceOfType(objectTypeTag);
619 if (defaultObj)
620 return defaultObj->clone();
621
622 cerr << "Object::newInstanceOfType(): object type '" << objectTypeTag
623 << "' is not a registered Object!" << endl;
624
625 return NULL;
626 }
627
628 /*
629 * getRegisteredTypenames() is a utility to retrieve all the typenames
630 * registered so far. This is done by traversing the registered objects map,
631 * so only concrete classes are dealt with. The result returned in rTypeNames
632 * should not be cached while more dlls are loaded as they get stale
633 * instead the list should be constructed whenever in doubt.
634 */
635 /*static*/ void Object::
getRegisteredTypenames(Array<std::string> & rTypeNames)636 getRegisteredTypenames(Array<std::string>& rTypeNames)
637 {
638 std::map<string,Object*>::const_iterator p =
639 _mapTypesToDefaultObjects.begin();
640 for (; p != _mapTypesToDefaultObjects.end(); ++p)
641 rTypeNames.append(p->first);
642 // Renamed type names don't appear in the registeredTypes map, unless
643 // they were separately registered.
644 }
645
646 //=============================================================================
647 // XML
648 //=============================================================================
649 //-----------------------------------------------------------------------------
650 // LOCAL STATIC UTILITY FUNCTIONS
651 //-----------------------------------------------------------------------------
652 template<class T> static void
UpdateFromXMLNodeSimpleProperty(Property_Deprecated * aProperty,SimTK::Xml::Element & aNode,const string & aName)653 UpdateFromXMLNodeSimpleProperty(Property_Deprecated* aProperty,
654 SimTK::Xml::Element& aNode,
655 const string& aName)
656 {
657 aProperty->setValueIsDefault(true);
658
659 SimTK::Xml::element_iterator iter = aNode.element_begin(aName);
660 if (iter == aNode.element_end()) return; // Not found
661
662 T value;
663 iter->getValueAs(value); // fails for Nan, infinity, -infinity, true/false
664 aProperty->setValue(value);
665 aProperty->setValueIsDefault(false);
666 }
667
668 template<class T> static void
UpdateFromXMLNodeArrayProperty(Property_Deprecated * aProperty,SimTK::Xml::Element & aNode,const string & aName)669 UpdateFromXMLNodeArrayProperty(Property_Deprecated* aProperty,
670 SimTK::Xml::Element& aNode,
671 const string& aName)
672 {
673 aProperty->setValueIsDefault(true);
674
675 SimTK::Xml::element_iterator iter = aNode.element_begin(aName);
676 if (iter == aNode.element_end()) return; // Not found
677
678 SimTK::Array_<T> value;
679 iter->getValueAs(value);
680
681 OpenSim::Array<T> osimValue;
682 osimValue.setSize(value.size());
683 for(unsigned i=0; i< value.size(); i++) osimValue[i]=value[i];
684 aProperty->setValue(osimValue);
685 aProperty->setValueIsDefault(false);
686 }
687
688 //------------------------------------------------------------------------------
689 // OBJECT XML METHODS
690 //------------------------------------------------------------------------------
691 // Populate this Object from XML element corresponding to an Object Property.
692 // We check for a file="xxx" attribute and read the object from that file
693 // if it is present. Otherwise we punt to updateFromXMLNode() and read the
694 // object directly from the supplied element.
readObjectFromXMLNodeOrFile(SimTK::Xml::Element & objectElement,int versionNumber)695 void Object::readObjectFromXMLNodeOrFile
696 (SimTK::Xml::Element& objectElement,
697 int versionNumber)
698 {
699 // If object is from non-inlined, detect it and set attributes
700 // However we need to do that on the finalized object as copying
701 // does not keep track of XML related issues
702 const std::string file =
703 objectElement.getOptionalAttributeValueAs<std::string>("file", "");
704
705 // otherwise object is described in file and it has root element
706 const bool inlinedObject = (file == "");
707
708 if (inlinedObject) {
709 // This object comes from the main XML document.
710 updateFromXMLNode(objectElement, versionNumber);
711 return;
712 }
713
714 // This object specifies an external file from which it should be read.
715
716 // When including contents from another file it's assumed file path is
717 // relative to the current working directory, which is usually set to be
718 // the directory that contained the top-level XML file.
719 XMLDocument* newDoc=0;
720 try {
721 std::cout << "reading object from file [" << file <<"] cwd ="
722 << IO::getCwd() << std::endl;
723 newDoc = new XMLDocument(file);
724 _document = newDoc;
725 } catch(const std::exception& ex){
726 std::cout << "failure reading object from file [" << file <<"] cwd ="
727 << IO::getCwd() << "Error:" << ex.what() << std::endl;
728 return;
729 }
730 _inlined=false;
731 SimTK::Xml::Element e = newDoc->getRootDataElement();
732 updateFromXMLNode(e, newDoc->getDocumentVersion());
733 }
734
735 template<class T> static void
UpdateXMLNodeSimpleProperty(const Property_Deprecated * aProperty,SimTK::Xml::Element & dParentNode,const string & aName)736 UpdateXMLNodeSimpleProperty(const Property_Deprecated* aProperty,
737 SimTK::Xml::Element& dParentNode,
738 const string& aName)
739 {
740 const T &value = aProperty->getValue<T>();
741 if(!aProperty->getValueIsDefault()||Object::getSerializeAllDefaults()) {
742 SimTK::Xml::Element elt(aProperty->getName(), value);
743 dParentNode.insertNodeAfter(dParentNode.node_end(), elt);
744 }
745 }
746
747 template<class T> static void
UpdateXMLNodeArrayProperty(const Property_Deprecated * aProperty,SimTK::Xml::Element & dParentNode,const string & aName)748 UpdateXMLNodeArrayProperty(const Property_Deprecated* aProperty,
749 SimTK::Xml::Element& dParentNode,
750 const string& aName)
751 {
752
753 const Array<T> &value = aProperty->getValueArray<T>();
754
755 if(!aProperty->getValueIsDefault()||Object::getSerializeAllDefaults()) {
756 SimTK::Xml::Element elt(aProperty->getName(), value);
757 dParentNode.insertNodeAfter(dParentNode.node_end(), elt);
758 }
759 }
760
761 static void
UpdateXMLNodeVec(const Property_Deprecated * aProperty,SimTK::Xml::Element & dParentNode,const string & aName)762 UpdateXMLNodeVec(const Property_Deprecated* aProperty,
763 SimTK::Xml::Element& dParentNode,
764 const string& aName)
765 {
766 const Array<double> &value = aProperty->getValueArray<double>();
767
768 if(!aProperty->getValueIsDefault()||Object::getSerializeAllDefaults()) {
769 SimTK::Xml::Element elt(aProperty->getName(), value);
770 dParentNode.insertNodeAfter(dParentNode.node_end(), elt);
771 }
772
773 }
774
775 static void
UpdateXMLNodeTransform(const Property_Deprecated * aProperty,SimTK::Xml::Element & dParentNode,const string & aName)776 UpdateXMLNodeTransform(const Property_Deprecated* aProperty,
777 SimTK::Xml::Element& dParentNode,
778 const string& aName)
779 {
780
781 // Get 6 raw numbers into an array and then use those to update the node
782 OpenSim::Array<double> arr(0, 6);
783 ((PropertyTransform *)aProperty)->getRotationsAndTranslationsAsArray6(&arr[0]);
784 if(!aProperty->getValueIsDefault()||Object::getSerializeAllDefaults()) {
785 SimTK::Xml::Element elt(aProperty->getName(), arr);
786 dParentNode.insertNodeAfter(dParentNode.node_end(), elt);
787 }
788 }
789
790
791 //-----------------------------------------------------------------------------
792 // UPDATE OBJECT
793 //-----------------------------------------------------------------------------
updateFromXMLNode(SimTK::Xml::Element & aNode,int versionNumber)794 void Object::updateFromXMLNode(SimTK::Xml::Element& aNode, int versionNumber)
795 {
796 try {
797 // NAME
798 const string dName =
799 aNode.getOptionalAttributeValueAs<std::string>("name", "");
800
801 // Set the name of this object.
802 setName(dName);
803
804 // UPDATE DEFAULT OBJECTS
805 updateDefaultObjectsFromXMLNode(); // May need to pass in aNode
806
807 // LOOP THROUGH PROPERTIES
808 for(int i=0; i < _propertyTable.getNumProperties(); ++i) {
809 AbstractProperty& prop = _propertyTable.updAbstractPropertyByIndex(i);
810 prop.readFromXMLParentElement(aNode, versionNumber);
811 }
812
813 // LOOP THROUGH DEPRECATED PROPERTIES
814 // TODO: get rid of this
815 for(int i=0;i<_propertySet.getSize();i++) {
816
817 Property_Deprecated* property = _propertySet.get(i);
818
819 // TYPE
820 Property_Deprecated::PropertyType type = property->getType();
821
822 // NAME
823 string name = property->getName();
824 SimTK::String valueString;
825 SimTK::String lowerCaseValueString;
826 SimTK::Xml::element_iterator iter;
827 SimTK::Array_<SimTK::String> value;
828 OpenSim::Array<bool> osimValue;
829 // VALUE
830 switch(type) {
831
832 // Bool
833 case(Property_Deprecated::Bool) :
834 property->setValueIsDefault(true);
835 iter= aNode.element_begin(name);
836 if (iter == aNode.element_end()) break; // Not found
837 iter->getValueAs(valueString); // true/false
838 lowerCaseValueString = valueString.toLower();
839 property->setValue(lowerCaseValueString=="true"?true:false);
840 //UpdateFromXMLNodeSimpleProperty<bool>(property, aNode, name);
841 property->setValueIsDefault(false);
842 break;
843 // Int
844 case(Property_Deprecated::Int) :
845 UpdateFromXMLNodeSimpleProperty<int>(property, aNode, name);
846 break;
847 // Double
848 case(Property_Deprecated::Dbl) :
849 property->setValueIsDefault(true);
850 iter= aNode.element_begin(name);
851 if (iter == aNode.element_end()) continue; // Not found
852 iter->getValueAs(valueString); // special values
853 lowerCaseValueString = valueString.toLower();
854 if (lowerCaseValueString=="infinity" || lowerCaseValueString=="inf")
855 property->setValue(SimTK::Infinity);
856 else if (lowerCaseValueString=="-infinity" || lowerCaseValueString=="-inf")
857 property->setValue(-SimTK::Infinity);
858 else if (lowerCaseValueString=="nan")
859 property->setValue(SimTK::NaN);
860 else
861 UpdateFromXMLNodeSimpleProperty<double>(property, aNode, name);
862 property->setValueIsDefault(false);
863 break;
864 // Str
865 case(Property_Deprecated::Str) :
866 UpdateFromXMLNodeSimpleProperty<string>(property, aNode, name);
867 break;
868 // BoolArray
869 case(Property_Deprecated::BoolArray) :
870 // Parse as a String array then map true/false to boolean values
871 property->setValueIsDefault(true);
872 iter = aNode.element_begin(name);
873 if (iter == aNode.element_end()) continue; // Not found
874 iter->getValueAs(value);
875 //cout << value << endl;
876 osimValue.setSize(value.size());
877 for(unsigned i=0; i< value.size(); i++) osimValue[i]=(value[i]=="true");
878 property->setValue(osimValue);
879 property->setValueIsDefault(false);
880 break;
881 // IntArray
882 case(Property_Deprecated::IntArray) :
883 UpdateFromXMLNodeArrayProperty<int>(property,aNode,name);
884 break;
885 // DblArray
886 case(Property_Deprecated::DblArray) :
887 case(Property_Deprecated::DblVec) :
888 case(Property_Deprecated::Transform) :
889 UpdateFromXMLNodeArrayProperty<double>(property,aNode,name);
890 break;
891 // StrArray
892 case(Property_Deprecated::StrArray) :
893 UpdateFromXMLNodeArrayProperty<string>(property,aNode,name);
894 break;
895
896 // Obj
897 case(Property_Deprecated::Obj) : {
898 property->setValueIsDefault(true);
899 Object &object = property->getValueObj();
900 SimTK::Xml::element_iterator iter =
901 aNode.element_begin(object.getConcreteClassName());
902 if (iter == aNode.element_end())
903 continue; // No element of this object type found.
904
905 // If matchName is set, search through elements of this type to find
906 // one that has a "name" attribute that matches the name of this
907 // object.
908 if (property->getMatchName()) {
909 while(object.getName() !=
910 iter->getOptionalAttributeValueAs<std::string>("name", dName)
911 && iter != aNode.element_end())
912 {
913 ++iter;
914 }
915 if (iter != aNode.element_end())
916 object.readObjectFromXMLNodeOrFile(*iter, versionNumber);
917 property->setValueIsDefault(false);
918 }
919 else {
920 object.readObjectFromXMLNodeOrFile(*iter, versionNumber);
921 property->setValueIsDefault(false);
922 }
923 break;
924 }
925
926 // ObjArray AND ObjPtr (handled very similarly)
927 case(Property_Deprecated::ObjArray) :
928 case(Property_Deprecated::ObjPtr) : {
929 property->setValueIsDefault(true);
930
931 // FIND THE PROPERTY ELEMENT (in aNode)
932 const SimTK::Xml::element_iterator propElementIter = aNode.element_begin(name);
933 if (propElementIter==aNode.element_end())
934 break;
935
936 if(type==Property_Deprecated::ObjArray) {
937 // CLEAR EXISTING OBJECT ARRAY
938 // Eran: Moved after elmt check above so that values set by constructor are kept if
939 // property is not specified in the xml file
940 property->clearObjArray();
941 }
942
943 property->setValueIsDefault(false);
944
945 // LOOP THROUGH PROPERTY ELEMENT'S CHILD ELEMENTS
946 // Each element is expected to be an Object of some type given
947 // by the element's tag.
948 Object *object =NULL;
949 int objectsFound = 0;
950 SimTK::Xml::element_iterator iter = propElementIter->element_begin();
951 while(iter != propElementIter->element_end()){
952 // Create an Object of the element tag's type.
953 object = newInstanceOfType(iter->getElementTag());
954 if (!object) {
955 std::cerr << "Object type " << iter->getElementTag() << " not recognized"
956 << std::endl;
957 iter++;
958 continue;
959 }
960 objectsFound++;
961
962 if(type==Property_Deprecated::ObjPtr) {
963 if(objectsFound > 1){
964 //throw XMLParsingException("Found multiple objects under "+name+" tag, but expected only one.",objElmt,__FILE__,__LINE__);
965 }
966 else{
967 property->setValue(object);
968 }
969 } else {
970 property->appendValue(object);
971 }
972 object->updateFromXMLNode(*iter, versionNumber);
973 iter++;
974 }
975
976 break; }
977
978 // NOT RECOGNIZED
979 default :
980 cout<<"Object.UpdateObject: WARN- unrecognized property type."<<endl;
981 break;
982 }
983 }
984
985
986 } catch (const Exception &ex) {
987 // Important to catch exceptions here so we can restore current working directory...
988 // And then we can re-throw the exception
989 throw(ex);
990 }
991
992 }
993
994 //-----------------------------------------------------------------------------
995 // UPDATE DEFAULT OBJECTS FROM XML NODE
996 //-----------------------------------------------------------------------------
997 //_____________________________________________________________________________
998 /**
999 * Update the registered default objects based on an object's XML node.
1000 *
1001 * This method looks for an element with a tag name "defaults" and reads
1002 * the objects in that element and registers them using the method
1003 * Object::registerType().
1004 */
1005 void Object::
updateDefaultObjectsFromXMLNode()1006 updateDefaultObjectsFromXMLNode()
1007 {
1008
1009 // MUST BE ROOT ELEMENT
1010 if(_document==NULL) return;
1011
1012 // GET DEFAULTS ELEMENT
1013 SimTK::Xml::element_iterator iterDefault =
1014 _document->getRootDataElement().element_begin("defaults");
1015 if (iterDefault==_document->getRootDataElement().element_end() ||
1016 !iterDefault->isValid()) return; // No defaults, skip over
1017
1018 if (_document->hasDefaultObjects()) return; // Could be processed by base class, if so skip.
1019
1020 SimTK::Array_<SimTK::Xml::Element> elts = iterDefault->getAllElements();
1021 for(unsigned it = 0; it < elts.size(); it++) {
1022 SimTK::String stg = elts[it].getElementTag();
1023
1024 // GET DEFAULT OBJECT
1025 const Object *defaultObject = getDefaultInstanceOfType(stg);
1026 if(defaultObject==NULL) continue;
1027
1028 // GET ELEMENT
1029 const string& type = defaultObject->getConcreteClassName();
1030 SimTK::Xml::element_iterator iterDefaultType=
1031 iterDefault->element_begin(type);
1032 if(iterDefaultType==iterDefault->element_end()) continue;
1033
1034 // CONSTRUCT AND REGISTER DEFAULT OBJECT
1035 // Used to call a special copy method that took DOMElement* but
1036 // that ended up causing XML to be parsed twice. Got rid of that
1037 // copy method! - Eran, Feb/07
1038 Object *object = defaultObject->clone();
1039 object->updateFromXMLNode(*iterDefaultType,
1040 _document->getDocumentVersion());
1041 object->setName(DEFAULT_NAME);
1042 registerType(*object);
1043 _document->addDefaultObject(object); // object will be owned by _document
1044 }
1045 }
1046
1047 //-----------------------------------------------------------------------------
1048 // UPDATE XML NODE
1049 //-----------------------------------------------------------------------------
1050 //_____________________________________________________________________________
1051
updateXMLNode(SimTK::Xml::Element & aParent,const AbstractProperty * prop) const1052 void Object::updateXMLNode(SimTK::Xml::Element& aParent,
1053 const AbstractProperty* prop) const
1054 {
1055 // Handle non-inlined object
1056 if(!getInlined()) {
1057 // If object is not inlined we don't want to generate node in original document
1058 // Handle not-inlined objects first.
1059 if (!aParent.isValid()) {
1060 cout<<"Root node must be inlined"<<*this<<endl;
1061 }
1062 else {
1063 // Can we make this more efficient than recreating the node again?
1064 // We can possibly check when setInlined() is invoked if we need to do it or not
1065 // Create a new document and write object to it
1066 string offlineFileName = getDocumentFileName();
1067 if(IO::GetPrintOfflineDocuments()) {
1068 // The problem is that generateChildXMLDocument makes a root which allows print
1069 // to do its job but root is duplicated. If we don't create the node then generateXMLDocument
1070 // is invoked which messes up the whole _childDocument mechanism as _document is overwritten.
1071 _inlined=true;
1072 print(offlineFileName);
1073 _inlined=false;
1074 SimTK::Xml::Element myObjectElement(getConcreteClassName());
1075 myObjectElement.setAttributeValue("file", offlineFileName);
1076 aParent.insertNodeAfter(aParent.node_end(), myObjectElement);
1077 }
1078 /*
1079 if (!_refNode) _refNode = XMLNode::AppendNewElementWithComment(aParent,getType(),getName());
1080 XMLNode::SetAttribute(_refNode,"file",offlineFileName);
1081 XMLNode::RemoveAttribute(_refNode,"name"); // Shouldn't have a name attribute in the reference document
1082 XMLNode::RemoveChildren(_refNode); // Shouldn't have any children in the reference document*/
1083 }
1084 return;
1085 }
1086
1087 // GENERATE XML NODE for object
1088 SimTK::Xml::Element myObjectElement(getConcreteClassName());
1089
1090 // if property is provided and it is not of unnamed type, use the property name
1091 if(prop && prop->isOneObjectProperty() && !prop->isUnnamedProperty()) {
1092 myObjectElement.setAttributeValue("name", prop->getName());
1093 } // otherwise if object has a name use it as the name value
1094 else if (!getName().empty()) {
1095 myObjectElement.setAttributeValue("name", getName());
1096 }
1097
1098 aParent.insertNodeAfter(aParent.node_end(), myObjectElement);
1099
1100 // DEFAULT OBJECTS
1101 //updateDefaultObjectsXMLNode(aParent);
1102 if (_document) _document->writeDefaultObjects(myObjectElement);
1103
1104
1105 // LOOP THROUGH PROPERTIES
1106 bool wroteAnyProperties = false;
1107 for(int i=0; i < _propertyTable.getNumProperties(); ++i) {
1108 const AbstractProperty& prop = _propertyTable.getAbstractPropertyByIndex(i);
1109
1110 // Don't write out if this is just a default value.
1111 if (!prop.getValueIsDefault() || Object::getSerializeAllDefaults()) {
1112 prop.writeToXMLParentElement(myObjectElement);
1113 wroteAnyProperties = true;
1114 }
1115 }
1116
1117 // LOOP THROUGH DEPRECATED PROPERTIES
1118 // TODO: get rid of this
1119 for(int i=0;i<_propertySet.getSize();i++) {
1120
1121 const Property_Deprecated *prop = _propertySet.get(i);
1122 if (prop->getValueIsDefault() && !Object::getSerializeAllDefaults())
1123 continue;
1124
1125 wroteAnyProperties = true;
1126
1127 // Add comment if any
1128 if (!prop->getComment().empty())
1129 myObjectElement.insertNodeAfter(myObjectElement.node_end(),
1130 SimTK::Xml::Comment(prop->getComment()));
1131
1132 // TYPE
1133 Property_Deprecated::PropertyType type = prop->getType();
1134
1135 // NAME
1136 string name = prop->getName();
1137
1138 string stringValue="";
1139 // VALUE
1140 switch(type) {
1141
1142 // Bool
1143 case(Property_Deprecated::Bool) :
1144 UpdateXMLNodeSimpleProperty<bool>(prop, myObjectElement, name);
1145 break;
1146 // Int
1147 case(Property_Deprecated::Int) :
1148 UpdateXMLNodeSimpleProperty<int>(prop, myObjectElement, name);
1149 break;
1150 // Dbl
1151 case(Property_Deprecated::Dbl) :
1152 if (SimTK::isFinite(prop->getValueDbl()))
1153 UpdateXMLNodeSimpleProperty<double>(prop, myObjectElement, name);
1154 else {
1155 if (prop->getValueDbl() == SimTK::Infinity)
1156 stringValue="Inf";
1157 else if (prop->getValueDbl() == -SimTK::Infinity)
1158 stringValue="-Inf";
1159 else if (SimTK::isNaN(prop->getValueDbl()))
1160 stringValue="NaN";
1161 if(!prop->getValueIsDefault()) {
1162 SimTK::Xml::Element elt(prop->getName(), stringValue);
1163 myObjectElement.insertNodeAfter(myObjectElement.node_end(), elt);
1164 }
1165 }
1166 break;
1167 // Str
1168 case(Property_Deprecated::Str) :
1169 UpdateXMLNodeSimpleProperty<string>(prop, myObjectElement, name);
1170 break;
1171 // BoolArray
1172 case(Property_Deprecated::BoolArray) :
1173 // print array as String and add it as such to element
1174 //UpdateXMLNodeArrayProperty<bool>(prop,myObjectElement,name); BoolArray Handling on Write
1175 stringValue = "";
1176 {
1177 //int n = prop->getArraySize();
1178 const Array<bool> &valueBs = prop->getValueArray<bool>();
1179 for (int i=0; i<valueBs.size(); ++i)
1180 stringValue += (valueBs[i]?"true ":"false ");
1181
1182 SimTK::Xml::Element elt(prop->getName(), stringValue);
1183 myObjectElement.insertNodeAfter(myObjectElement.node_end(), elt);
1184 }
1185 break;
1186 // IntArray
1187 case(Property_Deprecated::IntArray) :
1188 UpdateXMLNodeArrayProperty<int>(prop,myObjectElement,name);
1189 break;
1190 // DblArray
1191 case(Property_Deprecated::DblArray) :
1192 UpdateXMLNodeArrayProperty<double>(prop,myObjectElement,name);
1193 break;
1194 // DblVec3
1195 case(Property_Deprecated::DblVec) :
1196 UpdateXMLNodeVec(prop,myObjectElement,name);
1197 break;
1198 // Transform
1199 case(Property_Deprecated::Transform) :
1200 UpdateXMLNodeTransform(prop,myObjectElement,name);
1201 break;
1202 // StrArray
1203 case(Property_Deprecated::StrArray) :
1204 UpdateXMLNodeArrayProperty<string>(prop,myObjectElement,name);
1205 break;
1206
1207 // Obj
1208 case(Property_Deprecated::Obj) : {
1209 //PropertyObj *propObj = (PropertyObj*)prop;
1210 const Object &object = prop->getValueObj();
1211 object.updateXMLNode(myObjectElement);
1212 break; }
1213
1214 // ObjArray AND ObjPtr (handled very similarly)
1215 case(Property_Deprecated::ObjArray) :
1216 case(Property_Deprecated::ObjPtr) : {
1217 if(type==Property_Deprecated::ObjArray) {
1218 // Set all the XML nodes to NULL, and then update them all
1219 // in order, with index=0 so each new one is added to the end
1220 // of the list (more efficient than inserting each one into
1221 // the proper slot).
1222 SimTK::Xml::Element objectArrayElement(prop->getName());
1223 myObjectElement.insertNodeAfter(myObjectElement.node_end(), objectArrayElement);
1224 for(int j=0;j<prop->getArraySize();j++)
1225 prop->getValueObjPtr(j)->updateXMLNode(objectArrayElement);
1226 } else { // ObjPtr
1227 const Object *object = prop->getValueObjPtr();
1228 SimTK::Xml::Element objectBaseElement(prop->getName());
1229 myObjectElement.insertNodeAfter(myObjectElement.node_end(), objectBaseElement);
1230 if(object) { // Add node for base classHEREHEREHERE
1231 object->updateXMLNode(objectBaseElement);
1232 }
1233 }
1234 }
1235 break;
1236
1237 // NOT RECOGNIZED
1238 default :
1239 cout<<"Object.UpdateObject: WARN- unrecognized property type."<<endl;
1240 break;
1241 }
1242 }
1243
1244 if (!wroteAnyProperties) {
1245 myObjectElement.insertNodeAfter(myObjectElement.node_end(),
1246 SimTK::Xml::Comment
1247 ("All properties of this object have their default values."));
1248 }
1249 }
1250
1251 //_____________________________________________________________________________
1252 /**
1253 * Update the XML node for defaults object.
1254 */
1255 void Object::
updateDefaultObjectsXMLNode(SimTK::Xml::Element & aParent)1256 updateDefaultObjectsXMLNode(SimTK::Xml::Element& aParent)
1257 {
1258 if (_document==NULL || !_document->hasDefaultObjects())
1259 return;
1260 string defaultsTag = "defaults";
1261 SimTK::Xml::element_iterator elmt = aParent.element_begin(defaultsTag);
1262 // Not root element- remove defaults
1263 //if(elmt==aParent.element_end());
1264 // Root element- write valid defaults
1265
1266
1267 }
1268 //-----------------------------------------------------------------------------
1269 // NODE
1270 //-----------------------------------------------------------------------------
1271
1272 //-----------------------------------------------------------------------------
1273 // DOCUMENT
1274 //-----------------------------------------------------------------------------
1275 //_____________________________________________________________________________
1276 // getDocument(), updDocument() are inline.
1277
1278 //_____________________________________________________________________________
1279 /**
1280 * Get the document's filename
1281 *
1282 * @return Document's filename for this object.
1283 */
getDocumentFileName() const1284 string Object::getDocumentFileName() const
1285 {
1286 return _document ? _document->getFileName() : "";
1287 }
1288
1289
getDocumentFileVersion() const1290 int Object::getDocumentFileVersion() const
1291 {
1292 return _document ? _document->getDocumentVersion() : -1;
1293 }
1294
1295
1296 //-----------------------------------------------------------------------------
1297 // GENERATE XML DOCUMENT
1298 //-----------------------------------------------------------------------------
1299 //_____________________________________________________________________________
1300 /**
1301 * Generate a new XML document with this object as the root node.
1302 */
1303 void Object::
generateXMLDocument()1304 generateXMLDocument()
1305 {
1306 // CREATE NEW DOCUMENT
1307 if (_document==NULL)
1308 _document = new XMLDocument();
1309 }
1310
1311 //=============================================================================
1312 // XML support for inlining/offlining objects
1313 //=============================================================================
1314 /**
1315 * Get the value of the inlined flag
1316 */
1317 bool Object::
getInlined() const1318 getInlined() const
1319 {
1320 return _inlined;
1321 }
1322
1323 void Object::
setInlined(bool aInlined,const std::string & aFileName)1324 setInlined(bool aInlined, const std::string &aFileName)
1325 {
1326 // Wipe out the previously associated document if we weren't inline.
1327 if (!_inlined && _document) {
1328 delete _document;
1329 _document = NULL;
1330 }
1331
1332 _inlined = aInlined; // set new inline status
1333
1334 if(!_inlined) {
1335 _document = new XMLDocument();
1336 _document->setFileName(aFileName);
1337 }
1338 }
1339
1340 //-----------------------------------------------------------------------------
1341 // setAllPropertiesUseDefault
1342 //-----------------------------------------------------------------------------
1343 void Object::
setAllPropertiesUseDefault(bool aUseDefault)1344 setAllPropertiesUseDefault(bool aUseDefault)
1345 {
1346 // LOOP THROUGH PROPERTIES
1347 const int numProps = getNumProperties();
1348 for (int px = 0; px < numProps; ++px) {
1349 AbstractProperty& myProp = updPropertyByIndex(px);
1350 myProp.setAllPropertiesUseDefault(aUseDefault);
1351 }
1352 }
1353
1354 //=============================================================================
1355 // IO
1356 //=============================================================================
1357 //-----------------------------------------------------------------------------
1358 // PRINT OBJECT
1359 //-----------------------------------------------------------------------------
1360 //_____________________________________________________________________________
1361 /**
1362 * Print the object.
1363 *
1364 * @param aFileName File name. If the file name is NULL, which is the
1365 * default, the object is printed to standard out.
1366 */
1367 bool Object::
print(const string & aFileName) const1368 print(const string &aFileName) const
1369 {
1370 // Default to strict exception to avoid creating bad files
1371 // but for debugging allow users to be more lenient.
1372 if (_debugLevel >= 1) {
1373 try {
1374 warnBeforePrint();
1375 } catch (...) {}
1376 }
1377 else
1378 warnBeforePrint();
1379 // Temporarily change current directory so that inlined files are written to correct relative directory
1380 std::string savedCwd = IO::getCwd();
1381 IO::chDir(IO::getParentDirectory(aFileName));
1382 try {
1383 XMLDocument* oldDoc = NULL;
1384 if (_document != NULL){
1385 oldDoc = _document;
1386 }
1387 _document = new XMLDocument();
1388 if (oldDoc){
1389 _document->copyDefaultObjects(*oldDoc);
1390 delete oldDoc;
1391 oldDoc = 0;
1392 }
1393 SimTK::Xml::Element e = _document->getRootElement();
1394 updateXMLNode(e);
1395 } catch (const Exception &ex) {
1396 // Important to catch exceptions here so we can restore current working directory...
1397 // And then we can re-throw the exception
1398 IO::chDir(savedCwd);
1399 throw(ex);
1400 }
1401 IO::chDir(savedCwd);
1402 if(_document==NULL) return false;
1403 _document->print(aFileName);
1404 return true;
1405 }
1406
1407 //-----------------------------------------------------------------------------
1408 // PRINT PROPERTY INFORMATION
1409 //-----------------------------------------------------------------------------
1410 // Print property information for registered classes. This is used by OpenSim
1411 // tools to provide a nice "help" capability for objects.
1412
1413 // This signature accepts "className.propertyName", splits out the individual
1414 // segments and calls the other signature.
1415 bool Object::
PrintPropertyInfo(ostream & aOStream,const string & aClassNameDotPropertyName,bool printFlagInfo)1416 PrintPropertyInfo(ostream &aOStream,
1417 const string &aClassNameDotPropertyName,
1418 bool printFlagInfo)
1419 {
1420 // PARSE NAMES
1421 string compoundName = aClassNameDotPropertyName;
1422
1423 string::size_type delimPos = compoundName.find(".");
1424 string className = compoundName.substr(0,delimPos);
1425 string propertyName = "";
1426 if(delimPos!=string::npos) {
1427 propertyName = compoundName.substr(delimPos+1);
1428 }
1429
1430 return PrintPropertyInfo(aOStream, className, propertyName, printFlagInfo);
1431 }
1432
1433 // This is the real method.
1434 bool Object::
PrintPropertyInfo(ostream & aOStream,const string & aClassName,const string & aPropertyName,bool printFlagInfo)1435 PrintPropertyInfo(ostream &aOStream,
1436 const string &aClassName, const string &aPropertyName,
1437 bool printFlagInfo)
1438 {
1439 if(aClassName=="") {
1440 // NO CLASS
1441 int size = _registeredTypes.getSize();
1442 aOStream<<"REGISTERED CLASSES ("<<size<<")\n";
1443 Object *obj;
1444 for(int i=0;i<size;i++) {
1445 obj = _registeredTypes.get(i);
1446 if(obj==NULL) continue;
1447 aOStream<<obj->getConcreteClassName()<<endl;
1448 }
1449 if (printFlagInfo) {
1450 aOStream<<"\n\nUse '-PropertyInfo ClassName' to list the properties of a particular class.\n\n";
1451 }
1452 return true;
1453 }
1454
1455 // FIND CLASS
1456 const Object* object = getDefaultInstanceOfType(aClassName);
1457 if(object==NULL) {
1458 if (printFlagInfo) {
1459 aOStream<<"\nA class with the name '"<<aClassName<<"' was not found.\n";
1460 aOStream<<"\nUse '-PropertyInfo' without specifying a class name to print a listing of all registered classes.\n";
1461 }
1462 return false;
1463 }
1464
1465 PropertySet propertySet = object->getPropertySet();
1466 const Property_Deprecated* prop;
1467 const AbstractProperty* abstractProperty;
1468 if((aPropertyName=="")||(aPropertyName=="*")) {
1469 // NO PROPERTY
1470 int propertySetSize = propertySet.getSize();
1471 int propertyTableSize = object->_propertyTable.getNumProperties();
1472 int size = propertySetSize + propertyTableSize;
1473 aOStream<<"\nPROPERTIES FOR "<<aClassName<<" ("<<size<<")\n";
1474 string comment;
1475 int i;
1476 for(i=0;i<propertyTableSize;i++) {
1477 abstractProperty =
1478 &object->_propertyTable.getAbstractPropertyByIndex(i);
1479 if(abstractProperty==NULL) continue;
1480 if(aPropertyName=="") {
1481 aOStream<<i+1<<". "<<abstractProperty->getName()<<endl;
1482 } else {
1483 aOStream<<"\n"<<i+1<<". "<<abstractProperty->getName()<<"\n";
1484 comment = abstractProperty->getComment();
1485 if(!comment.empty()) {
1486 string formattedComment = IO::formatText(comment,"\t",80);
1487 aOStream<<"\t"<<formattedComment<<"\n";
1488 }
1489 }
1490 }
1491
1492 for(;i<size;i++) {
1493 prop = object->_propertySet.get(i-propertyTableSize);
1494 if(prop==NULL) continue;
1495 if(aPropertyName=="") {
1496 aOStream<<i+1<<". "<<prop->getName()<<endl;
1497 } else {
1498 aOStream<<"\n"<<i+1<<". "<<prop->getName()<<"\n";
1499 comment = prop->getComment();
1500 if(!comment.empty()) {
1501 string formattedComment = IO::formatText(comment,"\t",80);
1502 aOStream<<"\t"<<formattedComment<<"\n";
1503 }
1504 }
1505 }
1506
1507 if (printFlagInfo) {
1508 aOStream << "\n\nUse '-PropertyInfo ClassName.PropertyName' to print "
1509 "info for a particular property.\n";
1510 if(aPropertyName!="*") {
1511 aOStream << "Use '-PropertyInfo ClassName.*' to print info for all "
1512 "properties in a class.\n";
1513 }
1514 }
1515 return true;
1516 }
1517
1518 // FIND PROPERTY
1519 try {
1520 prop = propertySet.get(aPropertyName);
1521 // OUTPUT
1522 //aOStream<<"\nPROPERTY INFO FOR "<<aClassName<<"\n";
1523 aOStream << "\n" << aClassName << "." << aPropertyName << "\n"
1524 << prop->getComment() << "\n";
1525 return true;
1526 } catch(...) {
1527 try {
1528 abstractProperty = object->_propertyTable.getPropertyPtr(aPropertyName);
1529 if (abstractProperty == nullptr) {
1530 throw Exception("No property '" + aPropertyName +
1531 "' class '" + aClassName + "'.");
1532 }
1533 // OUTPUT
1534 //aOStream<<"\nPROPERTY INFO FOR "<<aClassName<<"\n";
1535 aOStream << "\n" <<aClassName << "." << aPropertyName <<"\n"
1536 << abstractProperty->getComment()<<"\n";
1537 return true;
1538 } catch (...) {
1539 if (printFlagInfo) {
1540 aOStream << "\nPrintPropertyInfo: no property with the name "
1541 << aPropertyName;
1542 aOStream << " was found in class " << aClassName << ".\n";
1543 aOStream << "Omit the property name to get a listing of all "
1544 "properties in a class.\n";
1545 }
1546 return false;
1547 }
1548 }
1549 }
1550
1551
1552 //=============================================================================
1553 // Utilities, factory methods
1554 //=============================================================================
1555 /**
1556 * makeObjectFromFile creates an OpenSim object based on the type of the object at the root
1557 * node of the XML file passed in. This is useful since the constructor of Object doesn't have the
1558 * proper type info. This works by using the defaults table so that "Object" does not need to know about
1559 * derived classes, however it uses the defaults table to get an instance, so only "Registered" types will
1560 * be considered.
1561 *
1562 * Note: The object created is "New" so whoever makes the call also takes ownership of the object
1563 */
1564 Object* Object::
makeObjectFromFile(const std::string & aFileName)1565 makeObjectFromFile(const std::string &aFileName)
1566 {
1567 /**
1568 * Open the file and get the type of the root element
1569 */
1570 try{
1571 XMLDocument *doc = new XMLDocument(aFileName);
1572 // Here we know the fie exists and is good, chdir to where the file lives
1573 string rootName = doc->getRootTag();
1574 bool newFormat=false;
1575 if (rootName == "OpenSimDocument"){ // New format, get child node instead
1576 rootName = doc->getRootElement().element_begin()->getElementTag();
1577 newFormat=true;
1578 }
1579 Object* newObject = newInstanceOfType(rootName);
1580 if(!newObject) throw Exception("Unrecognized XML element '"+rootName+"' and root of file '"+aFileName+"'",__FILE__,__LINE__);
1581 // Here file is deemed legit, chdir to where the file lives here and restore at the end so offline objects are handled properly
1582 const string saveWorkingDirectory = IO::getCwd();
1583 const string directoryOfXMLFile = IO::getParentDirectory(aFileName);
1584 IO::chDir(directoryOfXMLFile);
1585 //cout << "File name = "<< aFileName << "Cwd is now "<< directoryOfXMLFile << endl;
1586 try {
1587 newObject->_document=doc;
1588 if (newFormat)
1589 newObject->updateFromXMLNode(*doc->getRootElement().element_begin(), doc->getDocumentVersion());
1590 else {
1591 SimTK::Xml::Element e = doc->getRootElement();
1592 newObject->updateFromXMLNode(e, 10500);
1593 }
1594 } catch (...) {
1595 IO::chDir(saveWorkingDirectory);
1596 throw; // re-issue the exception
1597 }
1598 IO::chDir(saveWorkingDirectory);
1599 return (newObject);
1600 }
1601
1602 catch(const std::exception& x) {
1603 cout << x.what() << endl;
1604 return nullptr;
1605 }
1606 catch(...){ // Document couldn't be opened, or something went really bad
1607 return nullptr;
1608 }
1609 assert(!"Shouldn't be here");
1610 return nullptr;
1611 }
1612
makeObjectNamesConsistentWithProperties()1613 void Object::makeObjectNamesConsistentWithProperties()
1614 {
1615 // Cycle through this object's Object properties and make sure those
1616 // that are objects have names that are consistent with object property.
1617 for (int i = 0; i < getNumProperties(); ++i) {
1618 auto& prop = updPropertyByIndex(i);
1619 // check if property is of type Object
1620 if (prop.isObjectProperty()) {
1621 // a property is a list so cycle through its contents
1622 for (int j = 0; j < prop.size(); ++j) {
1623 Object& obj = prop.updValueAsObject(j);
1624 // If a single object property, set the object's name to the
1625 // property's name, otherwise it will be inconsistent with
1626 // what is serialized (property name).
1627 if (!prop.isUnnamedProperty() && prop.isOneObjectProperty()) {
1628 obj.setName(prop.getName());
1629 }
1630 // In any case, any objects that are properties of this object
1631 // also need to be processed
1632 obj.makeObjectNamesConsistentWithProperties();
1633 }
1634 }
1635 }
1636 }
1637
setObjectIsUpToDateWithProperties()1638 void Object::setObjectIsUpToDateWithProperties()
1639 {
1640 _objectIsUpToDate = true;
1641 }
1642
updateFromXMLDocument()1643 void Object::updateFromXMLDocument()
1644 {
1645 assert(_document!= 0);
1646
1647 SimTK::Xml::Element e = _document->getRootDataElement();
1648 const string saveWorkingDirectory = IO::getCwd();
1649 string parentFileName = _document->getFileName();
1650 const string directoryOfXMLFile = IO::getParentDirectory(parentFileName);
1651 IO::chDir(directoryOfXMLFile);
1652 updateFromXMLNode(e, _document->getDocumentVersion());
1653 IO::chDir(saveWorkingDirectory);
1654 }
1655
dump() const1656 std::string Object::dump() const {
1657 SimTK::String outString;
1658 XMLDocument doc;
1659 Object::setSerializeAllDefaults(true);
1660 SimTK::Xml::Element elem = doc.getRootElement();
1661 updateXMLNode(elem);
1662 Object::setSerializeAllDefaults(false);
1663 doc.getRootElement().node_begin()->writeToString(outString);
1664 return outString;
1665 }
1666
1667 /**
1668 * The following code accounts for an object made up to call
1669 * RegisterTypes_osimCommon function on entry to the DLL in a cross platform manner
1670 *
1671 */
1672 // Excluding this from Doxygen until it has better documentation! -Sam Hamner
1673 /// @cond
1674 class osimCommonInstantiator
1675 {
1676 public:
1677 osimCommonInstantiator();
1678 private:
1679 void registerDllClasses();
1680 };
1681
osimCommonInstantiator()1682 osimCommonInstantiator::osimCommonInstantiator()
1683 {
1684 registerDllClasses();
1685 }
1686
1687 extern "C" OSIMCOMMON_API void RegisterTypes_osimCommon();
registerDllClasses()1688 void osimCommonInstantiator::registerDllClasses()
1689 {
1690 RegisterTypes_osimCommon();
1691 }
1692
1693 static osimCommonInstantiator instantiator;
1694 /// @endcond
1695