JMRI: XML Persistance
JMRI uses XML for persisting internal structures, especially when storing the preferences and panel files.
XML persistance is done via some explicitly written code. Basically, certain classes register themselves with a instance of the "ConfigureManager". The only implementation of one of those we have is the one for loading and storing XML files: jmri.configurexml. ConfigXmlManager When it's time to store, the ConfigureXmlManager is told to do it. It goes through the registered objects and finds the persisting class responsible for storing the object. E.g. class a.b.Foo will have the class a.b.configurexml.FooXml located. If that class is found, it's told to store the Foo object, and it adds Xml content to a JDOM document to do that. If it's not located, an error message is issued.
On load, an XML file is read by the manager. Each element is examined for a "class" attribute. If found, that class is loaded and handed the element to process. Etc.
It's quite hackish, and not very cleanly factored. There's a fair amount of code duplication, but that might be because it comes from a C++ background and the developer keeps wishing for templates and mixin classes. But it works!
Example
A LightManager knows about Lights.There are lots of concrete classes implementing the Light interface:
- jmri.jmrix.loconet.LnLight
- jmri.jmrix.cmri.serial.SerialLight
- jmri.jmrix.powerline.SerialLight
There are also multiple LightManager concrete classes to handle them:
- jmri.jmrix.loconet.LnLightManager
- jmri.jmrix.cmri.serial.SerialLightManager
- jmri.jmrix.powerline.SerialLightManager
Each type of manager is stored and loaded via a persistance class, who is found by looking the a class with "Xml" appended to the name, in a "configurexml" direct subpackage:
- jmri.jmrix.loconet.configurexml.LnLightManagerXml
- jmri.jmrix.cmri.serial.configurexml.SerialLightManagerXml
- jmri.jmrix.powerline.configurexml.SerialLightManagerXml
In the case of Light concrete classes, the code for persisting the managers directly stores and loads the individual lights. This is because (so far) a given manager only has one type of Light (e.g. LnLightManager only has to worry about LnLight). In cases where this is not true, e.g. SignalHeads which have multiple classes, there are persistance classes for the individual objects in addition to the manager.
Added More Information To A Class
If you want to store more state information, find the persisting class and add code to it to create and read attributes or elements.Perhaps the easiest way to do this is to create a sample panel file with the objects you want to store in it:
<sensors class="jmri.jmrix.cmri.serial.configurexml.SerialSensorManagerXml" />
<sensor systemName="CS3001" />
</sensor>
<sensors class="jmri.managers.configurexml.InternalSensorManagerXml" />
<sensor systemName="IS21" />
</sensors>
<signalheads class="jmri.configurexml.AbstractSignalHeadManagerXml">
<signalhead class="jmri.configurexml.DoubleTurnoutSignalHeadXml" systemName="IH1P">
<turnout systemName="CT10" userName="1-bit pulsed green" />
<turnout systemName="CT2" userName="1-bit pulsed red" />
</signalhead>
</signalheads>
Note the "class" attributes. They give the fully-qualified name of the class that can load or store that particular element. In the case of Sensors, we see there are two managers in use: One for C/MRI, and one for internal Sensors. For SignalHeads, there's only one manager, jmri.configurexml.AbstractSignalHeadManager persisted by jmri.configurexml.AbstractSignalHeadManager, but each particular SignalHead implementing class has it's own persisting class.
To e.g. add more data to a sensor, the jmri.jmrix.cmri.serial.configurexml.SerialSensorManagerXml and jmri.managers.configurexml.InternalSensorManagerXml classes would have to be modified.
Note that in some cases, there's an inheritance relationship amoung the persisting classes that can help. For example, the LocoNet LnSensorManagerXml class inherits from jmri.configurexml.AbstractSensorManagerConfigXML, which does almost all the work of storing and loading sensors.
If you do add new attributes or elements, don't forget to update
the format definition, see below.
DTD Management
JMRI controls XML semantics usings
DTDs.
For example, layout information is stored in XML files as a "layout-config" element, whose contents are then defined via a DTD file. These files are kept in the xml/DTD directory.
A rudimentary version control system is provided by DTD naming, i.e.
- layout-config.dtd - the original, not to be changed any more.
- layout-config-2-1-5.dtd - manually created as part of the release of JMRI 2.1.4, this accumulates changes until 2.1.5 is released.
- Copy the existing DTD to one with the next version name (e.g. after 2.1.5 is released, copy layout-config-2-1-5.dtd to layout-config-2-1-6.dtd) and commit to CVS.
- Update the comment on the previous file.
- Update the references in the jmri.configurexml.ConfigXmlManager class definition.