Software documentation

Development tools

Code Structure

Techniques and Standards

How To

Functional Info

Background Info

JMRI: Patterns and Organization

JMRI has grown and evolved with time, and you can't always see the currently-preferred structure and patterns by looking at older code pieces.

This page attempts to describe the recommended structure and patterns, and point to examples of current best practices.

Names, NamedBeans, and Managers

The "NamedBean" concept is basic to JMRI. A NamedBean is a basic JMRI object that represents something, typically something like a specific Sensor or Turnout. Functionally, all the device object classes (Sensor, Turnout, ...) and their specific implementations (LnSensor, XNetTurnout, ...) inherit from the base NamedBean class.

To get access to a specific object (a NamedBean of a specific type with a specific name), you make requests of a manager: You ask a TurnoutManager for a specific Turnout. In turn, you access the managers through the common InstanceManager.

A user might want to reference a NamedBean via a user name, and in turn might want to change the specific NamedBean that user name refers to. "Yard East Turnout" might be "LT12" at one point, and later get moved to "CT5". To handle this, your code should use NamedBeanHandle objects to handle references to NamedBeans. They automate the process of renaming.

To do this, when you want to store a reference to a NamedBean, e.g. to remember a particular Sensor, Turnout, SignalMast, etc ask (through the InstanceManager) the NamedBeanHandlerManager to give you a NamedBeanHandle:


      NamedBeanHandle<Sensor> handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor);
      
where name is the String name that the user provided, either a system name or user name, and sensor is the particular Sensor object being stored. When you need to reference the sensor itself, just do

      sensor = handle.getBean();
      
Please use getBean() every time you need to access the bean. Don't cache the reference from getBean(). That way, if somebody does a "move" or "rename" operation, the NamedBeanHandle will get updated and you're next getBean() call will get the right reference.

Service Providers

Java provides a capability, using a "Service Provider Interface", that allows us to reduce the complexity of our code by having the code itself discover what pieces are available and need to be installed. For background on this, see the tutorial sections on "Creating Extensible Applications" and "Introduction to the Service Provider Interfaces".

For example, by annotating a class with


      @ServiceProvider(service = PreferencesManager.class)
      
the JMRI Preferences System automatically will discover that the class uses the preferences and should be hooked up. This means that we don't have to modify the Preferences classes to look up each new class using them, and that we can (eventually) more incrementally build and distribute JMRI.

Available patterns (links are to the JavaDoc for the interface or class specifying the functionality):

ConnectionTypeList
HttpServlet
(Note this is a Java-defined class, not a JMRI-defined interface)
InstanceInitializer
Provides a way for the JMRI InstanceManager to create an instance of the class when one is requested
JsonServiceFactory
PreferencesManager
PreferencesPanel
StartupActionFactory
StartupModelFactory
WebManifest
WebServerConfiguration

Classes provide SPI also have to be registered with the system so they can be found. JMRI does this with entries inside files in the target/classes/META-INF/services/ directory. These entries are created automatically during the JMRI build process from the annotations in the source files. JMRI then packages those into the appropriate level of jmri.jar file, where they will eventually be found and acted on.