This page is the top-level introduction to how the JMRI software is organized.
At the highest level, we separate test source code from distributed source code by putting both in separate directories within the development directory: "java/test" and "java/src". This makes it easy to compile a version without test code, to apply different tools to the two types of code, etc.
In addition to the source code, JMRI expects to find directories within the current directory:
plus specific files used by the build process, to control logging, and containing the JMRI license.
For interfaces in the jmri
package, which might be implemented by lots of
different layout hardware types,
jmri.InstanceManager
satisfies requests for implemented objects by providing access to a hardware-specific
Manager,
from which you can get the
hardware-specific items that represent what you want to access on the layout.
Because we have different hardware implementations in subpackages of the
jmrix
package, those are also accessed via the
SystemConnectionMemo
classes, which provide access to generally-defined objects.
Other code should, in general, not reference those specific implementations directly.
(The help/en/package section of the in-program help files are also organized this way)
More specifically:
jmri.*
and externals. It must not depend on
jmrix.*
This is an exception to the idea that packages should not directly reference classes across the tree: these utility classes are generally available for use.
Although it's not always honored, the tree structure here is important: Packages should
not reference across the tree. Code in jmri.jmrit
is welcome to
reference classes in the jmri
package, but should not reference directly to
classes in jmri.jmrix
. Classes should reference the interfaces in
jmri
, not the specific details of classes in jmri.managers
and
jmri.implementations
. It can be tempting to violate this rule in the interest of
expediency, but in the long run it makes it much harder for JMRI to be maintained and
improved by a large group of people. We just can't allow lots of tiny little special cases to
make the code impossible to understand. We're developing ArchUnit tests for this which can be run via
./runtest.csh jmri.ArchitectureCheck
as part of developer
unit testing (there is also a PowerShell script available, see the developer unit testing page for more information). This will highlight many
existing structure violations in addition to new ones because we haven't yet cleaned up some
historical issues. There's also jmri.ArchitectureTest
which is run as part of
our CI tests, and catches new violations of a
subset of the constraints.
Similarly, we have conventions for where to locate Swing GUI code and persistance (store/load) code that limits how embedded in them main code they become.
For many JMRI objects, once they have been created they can be accessed either by type, i.e. "The default configuration manager" via the InstanceManager, or by name, i.e. "The East Yard Entrance turnout" via a type-specific Manager. More information on how things (e.g. objects representing items on the layout) are named is available on a separate page.
The "InstanceManager" is a key central point for this navigation.
JMRI makes extensive use of the Factory pattern via objects we call "Manager" objects.
To get a specific Turnout instance that represents something on the layout, you make a request of a TurnoutManager. This is also an interface, with a similar implementation pattern. In turn, you got that TurnoutManager from the InstanceManager.
TurnoutManager manager = InstanceManager.getDefault(TurnoutManager.class); Turnout turnout = manager.getTurnout("IT12");
The generic Manager<T> class provides the basic interface. which is then extended for specifics i.e. TurnoutManager. That then has specific implementations.
In many cases, there's one underlying implementation for a manager. For example, there's one LogixManager, one MemoryManager, etc.
In other cases, you might have multiple system-specific managers: LnTurnoutManager for a LocoNet connection, NceTurnoutManager for an NCE connection, and an InternalTurnoutManager for the internal turnouts. These are handled by making them clients to a single ProxyManager, subclass i.e. ProxyTurnoutManager which uses name lookup to work with the individual managers.
Code can hold a direct java reference to a specific object. In some cases, typically GUIs or persistance, you'll want to refer to an object by its name. JMRI NamedBean objects have both system names that can't change and user names that can. For example, the user name "East Yard" for a turnout might refer to "LI1" at some point and later refer to "NT12". The NamedBeanHandle class takes care of this by remembering which name is being used for an object, and keeping track of how that name might be moved from one object to another. For more information, see the description on the Patterns page.