Files
2026-06-17 14:00:51 +02:00

283 lines
14 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="generator" content="HTML Tidy for HTML5 for Apple macOS version 5.8.0">
<title>JMRI: Patterns and Structure</title>
<meta name="author" content="Bob Jacobsen">
<meta name="keywords" content="JMRI technical code patterns structure">
<!--#include virtual="/help/en/parts/Style.shtml" -->
</head>
<body>
<!--#include virtual="/help/en/parts/Header.shtml" -->
<div id="mBody">
<!--#include virtual="Sidebar.shtml" -->
<div id="mainContent">
<h1>JMRI Code: Patterns and Organization</h1>
<p>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.</p>
<p>This page attempts to describe the recommended structure and patterns, and point to
examples of current best practices.</p>
<a id="namedbeans"></a>
<h2 id="NamedBean">Names, NamedBeans, and Managers</h2>
The "NamedBean" concept is basic to JMRI. A <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBean.html">NamedBean</a> is a basic JMRI object that
represents something, typically something like a specific Sensor or Turnout.
<ul>
<li>They're called a "Bean" because they're a unit of interaction: Multiple pieces of code
can work with one, it can be loaded and stored, etc.</li>
<li>They're "Named" to make sure they're unique and retrievable: There's only one Turnout
NamedBean with called "LT01", and it represents a specific addressed (named) layout object.
See the <a href="Names.shtml">page on Names</a> for more on this.
</li>
</ul>
Functionally, all the device object classes (Sensor, Turnout, ...) and their specific
implementations (LnSensor, XNetTurnout, ...) inherit from the base <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBean.html">NamedBean</a> class.
<h3 id="handles">Naming and Handles</h3>
<p>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 <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/TurnoutManager.html">TurnoutManager</a> for a specific
Turnout. In turn, you <a href="IntroStructure.shtml">access the managers through the common
InstanceManager</a>.</p>
<p>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 <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBeanHandle.html">NamedBeanHandle</a> objects to
handle references to NamedBeans. They automate the process of renaming.</p>
<p>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:</p>
<span class="wide">
<pre><code>
NamedBeanHandle&lt;Sensor&gt; handle =
InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor);
</code></pre></span>
<p>where <code>name</code> is the String name that the user provided, either a system name or
user name, and <code>sensor</code> is the particular <code>Sensor</code> object being stored.
When you need to reference the sensor itself, just do</p>
<pre><code>
sensor = handle.getBean();
</code></pre>
<p>Please use <code>getBean()</code> every time you need to access the bean. Don't
cache the reference from <code>getBean()</code>. That way, if somebody does a "move" or "rename"
operation, the <code>NamedBeanHandle</code> will get updated and your next <code>getBean()</code>
call will get the right reference.</p>
<h3>Bean properties</h3>
<p>NamedBeans usually have state, for example a <code>Sensor</code> may be Active or Inactive
(or Unknown or Inconsistent). This state is represented by one or more Java Bean properties.
Code in Java and Jython can use the <a href=
"https://docs.oracle.com/javase/tutorial/uiswing/events/propertychangelistener.html">PropertyChangeListener
pattern</a> to get notified when a given property changes. As an example, when a turnout is
configured for a feedback sensor, the <code>Turnout</code> object registers itself as a
change listener when the <code>Sensor</code>'s state property changes, and updates the
<code>Turnout</code>'s <code>"KnownState"</code> property.</p>
<p>The available Bean properties are defined in the abstract base class usually, for example
<code>AbstractTurnout</code> defines <code>"CommandedState"</code>,
<code>"KnownState"</code>, <code>"feedbackchange"</code>, <code>"locked"</code> and some more
at the time of this writing. These properties are not system-dependent. Some of the
properties are run-time only (e.g. state -- is the turnout thrown or closed?), while others
(e.g. turnout feedback mode) are configuration settings, selected by the user and saved
between sessions.</p>
<h3>Editing and saving NamedBeans</h3>
<p>NamedBeans are created and configured by the user using explicit actions. Most of the UI
for these actions is in the <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/beantable/package-summary.html">jmri.jmrit.beantable</a>
package, using the generic <code>BeanTable{Frame,Pane,Model}</code> classes specialized for
the particular type, for example in the <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/beantable/TurnoutTableAction.html"><code>TurnoutTableAction</code></a>
class. The configuration options present in the table and the edit dialog are specific to the
type (<code>Turnout</code>) but not the system.</p>
<p>The beans with the configured options are persisted into the Configuration (and Panel) XML
file when the user saves those. The persistence is handled by the system- and object-specific
ManagerXml class, for example <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrix/loconet/configurexml/LnTurnoutManagerXml.html">LnTurnoutManagerXml</a>
or <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrix/openlcb/configurexml/OlcbTurnoutManagerXml.html">OlcbTurnoutManagerXml</a>,
which heavily rely on shared code in <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/managers/configurexml/AbstractTurnoutManagerConfigXML.html">
AbstractTurnoutManagerConfigXML</a>, but can introduce system-specific functionality and work
together with the system- and object-specific manager (e.g. <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrix/openlcb/OlcbTurnoutManager.html">OlcbTurnoutManager</a>)
to achieve this.</p>
<p>The base class handles persisting the user settings that were entered via the
BeanTable.</p>
<h3>System-specific properties</h3>
<p>Adding a system-specific property requires using a generic API, because the code in the
<code>jmrit.beantable</code> package <a href="IntroStructure.shtml">cannot depend</a> on the
jmrix.system-specific packages. All NamedBeans have a <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBean.html#setProperty-java.lang.String-java.lang.Object-">
setProperty</a> and <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBean.html#getProperty-java.lang.String-">getProperty</a>
method where arbitrary values can be saved for any string key. These properties are persisted
into the XML file by the base class of the ManagerXml, so no code needs to be written for it.
A variety of basic types can be chosen for the property value, such as <code>Integer</code>
or <code>Boolean</code>, and will be correctly persisted and recovered upon load. Custom
types might work if they have a <code>toString()</code> method and a constructor that takes
only one <code>String</code> as argument and these correctly serialize and parse the data
value.</p>
<p>To allow the user to edit these system-specific properties, a specific
<code>Manager</code> can declare the set of supported properties by returning appropriately
filled <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/NamedBeanPropertyDescriptor.html">NamedBeanPropertyDescriptor</a>
objects from the <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/Manager.html#getKnownBeanProperties--">getKnownBeanProperties</a>
method. This descriptor tells the BeanTable that additional columns need to be created, what
type of data those columns will hold and what should be the column names (printed in the
header). The system-specific columns are hidden by default from the user; the user needs to
click a checkbox in the bottom row to show them; the checkbox only appears if there are
system-specific properties. The column name has to be filled with a localized string that
should come out of the respective <code>Manager</code>'s <code>Bundle</code>.</p>
<h2 id="SPI">Service Providers</h2>
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.<br>
For background on this, see the tutorial sections on "<a href=
"https://docs.oracle.com/javase/tutorial/ext/basics/spi.html">Creating Extensible
Applications</a>" and "<a href=
"https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html">Introduction to the Service
Provider Interfaces</a>".
<p>For example, by annotating a class with</p>
<pre><code>
@ServiceProvider(service = PreferencesManager.class)
</code></pre>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.
<p>Available patterns (links are to the JavaDoc for the interface or class specifying the
functionality):</p>
<dl>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/server/web/spi/WebManifest.html">ConnectionTypeList</a>
</dt>
<dt>
<a href=
"https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServlet.html">HttpServlet</a>
</dt>
<dd>(Note this is a Java-defined class, not a JMRI-defined interface)</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/InstanceInitializer.html">InstanceInitializer</a>
</dt>
<dd>Provides a way for the JMRI InstanceManager to create an instance of the class when one
is requested</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/spi/JsonServiceFactory.html">JsonServiceFactory</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/spi/PreferencesManager.html">PreferencesManager</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/swing/PreferencesPanel.html">PreferencesPanel</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/beantable/signalmast/SignalMastAddPane.html">SignalMastAddPane</a>
</dt>
<dd>Provide a type-specific pane used to add/edit the information in a SignalMast concrete
object</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/util/startup/StartupActionFactory.html">StartupActionFactory</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/util/startup/StartupModelFactory.html">StartupModelFactory</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/server/web/spi/WebManifest.html">WebManifest</a>
</dt>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/server/web/spi/WebServerConfiguration.html">WebServerConfiguration</a>
</dt>
</dl>
<p>To see more on the places where SPI can be used to insert functionality into
JMRI, see the
<a href="plugins.shtml">Plug-in page</a>.
<p>Classes that provide SPI also have to be registered with the system so they can be found.
JMRI does this with entries inside files in the
<code>target/classes/META-INF/services/</code> 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 <code>jmri.jar</code> file, where they will
eventually be found and acted on.</p>
<p>To access them:</p>
<div class="wide"><code>
<pre>
java.util.ServiceLoader.load(OurServiceClass.class).forEach((ourServiceObject) -&gt; {
// access the service object via ourServiceObject
});
</pre></code></div>
<a id="javascript"></a>
<a id="typescript"></a>
<h2>JavaScript and TypeScript Code</h2>
<p>JavaScript code for use by the web server should be put in the
<code>web/js</code> directory, which is where our web pages are served from via the
<a href="../../web/index.shtml">JMRI web server</a>.
<p>TypeScript code for use by the web server should be put in the
<code>web/ts</code> directory. This will be compiled as needed
by the <code>ant typescript</code> target. The results are put in the
<code>web/js</code> directory.</p>
<p>To run the <code>ant typescript</code> target and compile the TypeScript files,
a TypeScript compiler needs to be installed on the computer. For more information
on TypeScript and how to install it, see the
<a href="https://www.typescriptlang.org/">TypeScript web page</a>.</p>
<p>JavaScript code can also be used for scripting. By default, these should be put
into the <code>jython/</code> directory, though of course people can run scripts from
any location they choose.</p>
<!--#include virtual="/help/en/parts/Footer.shtml" -->
</div>
<!-- closes #mainContent-->
</div>
<!-- closes #mBody-->
<script src="/js/help.js"></script>
</body>
</html>