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

368 lines
17 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: Plug-in mechanisms</title>
<meta name="author" content="Bob Jacobsen">
<meta name="keywords" content="JMRI technical code plugins extension plug-in">
<!--#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: Extending the JMRI Programs</h1>
<p>The original goal of the JMRI project was to produce a library upon which people could use
to build their own applications. Although some people do that, more use the existing
applications such as DecoderPro and PanelPro.<br>
We want to make this more flexible by providing a way to extend those programs without having
to rebuild them from scratch.</p>
<p>There are three supported mechanisms that can be used to plug additional capabilities into
JMRI:</p>
<ul>
<li>
<a href="#script">Script JMRI</a>
</li>
<li>
<a href="#service">Implement a Service Provider</a>
</li>
</ul>
See also the separate pages on <a href="NewSystem.shtml">adding a new system</a> (i.e.
another set of hardware that implements Turnouts, Sensors, clocks, etc) and <a href=
"NewType.shtml">adding a new type</a> (e.g. something in addition to Turnouts, Sensors,
clocks, etc).
<h2 id="script">Script JMRI</h2>
<p><a href="../../tools/scripting">Scripting JMRI</a> is often the easiest way to extend
JMRI, however there are limitations to that which are covered by the other mechanisms.</p>
<p>The principal limitations to scripting JMRI are:</p>
<ul>
<li>scripts can only be run late in the application start process</li>
<li>scripts cannot be used to define new connection types</li>
<li>scripts cannot be used to add items to the preferences window</li>
</ul>
<p>The details of scripting are <a href="../../tools/scripting">covered elsewhere</a>.</p>
<p>Examples of scripts that modify JMRI behavior are:</p>
<ul>
<li>
<a href="https://www.jmri.org/jython/AddButton.py">AddButton.py</a> sample script adds a
script button to the main window.
</li>
<li>
<a href="https://www.jmri.org/jython/DisableOpsMode.py">DisableOpsMode.py</a> shows how how
to modify the main window to remove the ops-mode programming button.
</li>
<li>
<a href="https://www.jmri.org/jython/ReporterFontControl.py">ReporterFontControl.py</a>
sample script is an even more advanced example that changes the appearance of items on
panel screens.
</li>
</ul>
<h2 id="add">Adding Java Code</h2>
If you want to add a function that'll need significant code, ideally eventually as a part of
JMRI itself, the usual sequence is to write Java code
<ol>
<li>that creates objects to run as part of the <a href="IntroStructure.shtml">usual JMRI
structures</a>
</li>
<li>which are stored and loaded via <a href="XmlPersistance.shtml">configurexml</a> classes
that load and store those objects into standard panel files
</li>
<li>optionally has a GUI that starts from an <a href="Swing.shtml">action class</a> fired
from some button or menu item,
</li>
<li>optionally can fire that action at startup to open the GUI by selecting it under
"Peform action.." in the Startup pane in Preferences,</li>
<li>optionally can have its own preferences pane to store more info, and</li>
<li>eventually has <a href="JUnit.shtml">CI unit tests</a>, <a href=
"Javadoc.shtml">documentation</a> and <a href="Help.shtml">help pages</a>.
</li>
</ol>
Operationally, that's often the best order to develop new function: First, write the code
(item 1) so that it runs inside JMRI, and use a script to create and start those objects.
There are two places to put it:
<ul>
<li>In a top level package, i.e. a new <code>java/src/mycooltool</code> directory alongside
<code>java/src/jmri</code> and <code>java/src/apps</code>. Your Java files will start with
"import mycooltool;" as a package declaration.</li>
<li>In a new tools package within the <a href="IntroStructure.shtml">JMRI code
structure</a>, i.e. a <code>java/src/jmri/jmrit/cooltool</code> directory with your java
files starting with <code>import jmri.jmrit.coolltool;</code>.
</li>
</ul>
<p>Next, write the <a href="XmlPersistance.shtml">configurexml</a> load and store classes, so
that once you've got the objects, you can store and reactivate them. You still need the
script (or an XML editor if the info is simple enough) to create them the first time, though,
so as a third step <a href="Swing.shtml">write a GUI</a> to create that. That can be invoked
by a one-line script at first, and eventually attached to a menu button.</p>
<p>Once those first three steps are working and you've created a <a href=
"Swing.shtml#display">GUI action class</a>, you can connect that to "Peform action.." and
"Add button to main window .." in the Startup pane in Preferences by having it extend
<a href="#StartupActionFactory"><code>apps.startup.StartupActionFactory</code></a>.</p>
<p>The <a href=
"https://github.com/JMRI/JMRI/tree/master/java/src/jmri/jmrit/sample">jmri.jmrit.sample</a>
package is an example of this. (See <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/sample/package-summary.html">Javadoc</a>) If
contains:</p>
<ul>
<li>A single functional class, <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/sample/SampleFunctionalClass.java">
SampleFunctionalClass</a> who's only role is to save a sample string. Classes like this
would be built out to do the work of your project.
</li>
<li>A <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/sample/configurexml/SampleFunctionalClassXml.java">
configurexml.SampleFunctionalClassXml</a> class that stores and loads the
SampleFunctionalClass object contents to a panel file.
</li>
<li>A <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/sample/swing/SampleConfigPane.java">
swing.SampleConfigPane</a> class to provide the basis of a GUI configuration pane. This
one just shows a label in its window, but you can build it out with whatever else is
needed. It's connected to the rest of JMRI so that you can access configure connections
to it in the Preferences.
</li>
<li>A complete set of basic test classes. They just check the constructors now, but can be
built out as needed.</li>
</ul>
We encourage you to <a href="gitdeveloper.shtml">contribute your code to for inclusion in
JMRI</a>. That way, lots of people benefit. But if you don't want to do that, you can package
it up as a separate .jar file which can just be dropped into the JMRI lib directory
in the user's settings directory. By
using the approach listed above (and the services listed below), JMRI will automatically pick
it up and use it. For more on this mechanism, see the
<a href="Patterns.shtml#SPI">discussion on the Patterns page</a>.
<h2 id="service">Implement a Service Provider</h2>
Sometimes what you want to add provides a very specific technical function. Many of those can
be (though historically, perhaps weren't) written as Service Provider classes. When they can
be done that way, they should be, because it simplifies their connection to the rest of the
code.
<p>Java contains a <a href=
"https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html">Service Loader</a>
that allows classes implementing a specific API to provide a service to a Java application
without requiring that the application have prior dependencies defined for that service.</p>
<p>Services are provided by creating a JAR for that service and appending it to the JMRI
classpath. See <a href="StartUpScripts.shtml">Startup Scripts</a> for details on appending a
JAR to the classpath and the <a href=
"https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html">Service Loader</a>
documentation concerning what needs to be in that JAR.</p>
<p>JMRI uses Service Loaders to allow a JMRI application to be extended in specific ways:</p>
<dl>
<dt id="StartupActionFactory">
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/util/startup/StartupActionFactory.html">StartupActionFactory</a>
</dt>
<dd>
<img src="images/StartUpActionExample.png" alt="startup action"><br>
Startup Actions can be run at application start or via a button attached to the application's
main window. Implementations of this factory class appear as possible selections
for the Perform Action... and Attach Action to Button... selections in the Add... button on
the Startup pane in JMRI Preferences.
<p>One example is <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/roster/swing/RosterFrameStartupActionFactory.java">
the RosterFrameStartupActionFactory class</a> which opens the DecoderPro roster window.
They can also expose additional startup actions that can be selected by the user, i.e. to
select one of several possible connections to act on.</p>
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/util/startup/StartupModelFactory.html">StartupModelFactory</a>
</dt>
<dd>
<img src="images/StartUpModelExample.png" alt="startup model"><br>
Startup Models provide a mechanism to define optional items to be automatically run
during the startup process itself. They can take user-specified arguments.
Implementations of this class appear under the "Add" button in the Startup pane of the
Preferences.
<p>One example is <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/util/startup/PerformActionModelFactory.java">
the PerformActionModelFactory class</a> which provides the Perform Action... item.
PerformActionModelFactory makes the <a href=
"#StartupActionFactory">StartupActionFactory</a> implementations available for the user
to select. A PerformActionModelFactory object then remembers that selection, and during
JMRI startup invokes that StartupActionFactory item to do that particular thing.
Similarly, <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/apps/startup/CreateButtonModelFactory.java">
the CreateButtonModelFactory class</a> will take a user StartupActionFactory selection
and attach it to a button at startup, for execution later.</p>
<p>Implementations of this factory class provide the hooks so that the Startup
preferences can allow a user to set the parameters for a given action.</p>
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrix/ConnectionTypeList.html">ConnectionTypeList</a>
</dt>
<dd>
Every manufacturer selectable when creating a configuration is defined by a
ConnectionTypeList service. Implement this (and other required classes) to create a new
system connection type. See <a href="NewSystem.shtml">Adding a New System</a> for
details.
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/InstanceInitializer.html">InstanceInitializer</a>
</dt>
<dd>
Add new factories for creating default instances of objects managed by the <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/InstanceManager.html">InstanceManager</a>.
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/spi/JsonServiceFactory.html">JsonServiceFactory</a>
</dt>
<dd>
The JMRI JSON services used in the JMRI web services can be extended using service
implementations of this class. See the <a href=
"https://www.jmri.org/JavaDoc/doc/jmri/spi/JsonServiceFactory.html">JsonServiceFactory
Javadocs</a> for details.
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/swing/PreferencesPanel.html">PreferencesPanel</a>
</dt>
<dd>Additional preferences can be displayed in the preferences window by providing an
implementation of this class.</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/spi/PreferencesManager.html">PreferencesManager</a>
</dt>
<dd>Add a new preferences manager to JMRI. Preferences managers store, retrieve, and
validate preferences within a JMRI configuration profile. If a plugin needs to take action
very early in the JMRI application startup sequence, it would need to provide a
PreferencesManager service.</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/swing/ToolsMenuAction.html">
ToolsMenuAction</a>
</dt>
<dd>
Let you add a new item at the bottom of the main Tools menu.
<p>Implementing <code>ToolsMenuAction</code> in a class that inherits from
<code>Action</code>,
<code>AbstractAction</code>, or
<code>JMenuItem</code>
and providing the appropriate service provider annotation will
result in your item being added at the bottom of the main Tools menu.
<p>For an example, see the SampleToolsMenuItem class
<a href="https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/sample/SampleToolsMenuItem.java">
here</a>.
Note that this sample has some indicated lines commented out so that
it doesn't actually appear in the menu. Remove those comments
and rebuild to see how it works.</p>
</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/jmrit/beantable/signalmast/SignalMastAddPane.SignalMastAddPaneProvider.html">
SignalMastAddPaneProvider</a>
</dt>
<dd>
Provides the Add/Edit pane for a new type of SignalMast.
<p>If you define a new type of SignalMast in your code, also define a service class of
this type. It will automatically be used to add or edit signals of your new type in the
<a href="../../../package/jmri/jmrit/beantable/SignalMastTable.shtml">SignalMast
Table</a>.</p>
<p>See the SignalMastAddPaneProvider class nested within the <a href=
"https://github.com/JMRI/JMRI/blob/master/java/src/jmri/jmrit/beantable/signalmast/DccSignalMastAddPane.java">
DccSignalMastAddPane</a> class for an example.</p>
</dd>
<dt>
<a href=
"https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html">HttpServlet</a>
with <a href=
"https://docs.oracle.com/javaee/7/api/javax/servlet/annotation/WebServlet.html">WebServlet</a>
annotation
</dt>
<dd>Additional servlets in the web server can be added using these mechanisms. Note that
the WebServlet annotation needs to provide a name and urlPatterns.</dd>
<dt>
<a href=
"https://www.jmri.org/JavaDoc/doc/jmri/server/web/spi/WebServerConfiguration.html">WebServerConfiguration</a>
</dt>
<dd>Additional file paths, redirections, explicitly blocked paths in the JMRI web server
can be specified by providing a service that implements this.</dd>
</dl>
<h2 id="install">Distributing and Installing the Plug In</h2>
Once you have Java code that you'd like to distribute to JMRI,
you have to tell users how to install it.
Prior to JMRI 5.7.1, you should have them place your jar file
in the lib/ directory within the JMRI program directory. On Linux and macOS
this will have to be done every time the user updates or reinstalls JMRI.
Starting with JMRI 5.7.1, you should have them place your jar file
in the lib/ directory that's found within their
<a href="ProfileFileStructure.shtml">JMRI settings directory</a>.
One easy way to locate this directory is to open "File Locations"
in the JMRI Help menu and click "Open Settings Location". The lib/
directory should be available in the file manager window that opens.
<!--#include virtual="/help/en/parts/Footer.shtml" -->
</div>
<!-- closes #mainContent-->
</div>
<!-- closes #mBody-->
<script src="/js/help.js"></script>
</body>
</html>