-
Notifications
You must be signed in to change notification settings - Fork 331
/
Patterns.shtml
235 lines (207 loc) · 12.7 KB
/
Patterns.shtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta name="generator" content=
"HTML Tidy for Mac OS X (vers 31 October 2006 - Apple Inc. build 15.17), see www.w3.org">
<title>JMRI: Patterns and Structure</title>
<meta name="author" content="Bob Jacobsen">
<meta name="keywords" content=
"JMRI technical code patterns structure"><!-- Style -->
<meta http-equiv="Content-Type" content=
"text/html; charset=us-ascii">
<link rel="stylesheet" type="text/css" href="/css/default.css"
media="screen">
<link rel="stylesheet" type="text/css" href="/css/print.css"
media="print">
<link rel="icon" href="/images/jmri.ico" type="image/png">
<link rel="home" title="Home" href="/"><!-- /Style -->
</head>
<body>
<!--#include virtual="/Header.shtml" -->
<div id="mBody">
<!--#include virtual="Sidebar" -->
<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. <a id="namedbeans" name="namedbeans"></a></p>
<a id="NamedBean" name="NamedBean"></a>
<h2>Names, NamedBeans, and Managers</h2>
The "NamedBean"
concept is basic to JMRI. A <a href=
"http://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=
"http://jmri.org/JavaDoc/doc/jmri/NamedBean.html">NamedBean</a>
class.
<h3>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=
"http://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=
"http://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:
<pre><code>
NamedBeanHandle<Sensor> handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor);
</code></pre>
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
<pre><code>
sensor = handle.getBean();
</code></pre>
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.
<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="http://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="http://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="http://jmri.org/JavaDoc/doc/jmri/jmrix/loconet/configurexml/LnTurnoutManagerXml.html">LnTurnoutManagerXml</a>
or <a href="http://jmri.org/JavaDoc/doc/jmri/jmrix/openlcb/configurexml/OlcbTurnoutManagerXml.html">OlcbTurnoutManagerXml</a>,
which heavily rely on shared code
in <a href="http://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="http://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="http://jmri.org/JavaDoc/doc/jmri/NamedBean.html#setProperty-java.lang.String-java.lang.Object-">setProperty</a>
and
<a href="http://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="http://jmri.org/JavaDoc/doc/jmri/NamedBeanPropertyDescriptor.html">NamedBeanPropertyDescriptor</a>
objects from
the <a href="http://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>
<a id="SPI" name="SPI"></a>
<h2>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
<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):
<dl>
<dt><a href="http://jmri.org/JavaDoc/doc/jmri/server/web/spi/WebManifest.html">ConnectionTypeList</a></dt>
<dt><a href="http://docs.oracle.com/javaee/6/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="http://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="http://jmri.org/JavaDoc/doc/jmri/spi/JsonServiceFactory.html">JsonServiceFactory</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/jmri/spi/PreferencesManager.html">PreferencesManager</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/jmri/swing/PreferencesPanel.html">PreferencesPanel</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/apps/startup/StartupActionFactory.html">StartupActionFactory</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/apps/startup/StartupModelFactory.html">StartupModelFactory</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/jmri/server/web/spi/WebManifest.html">WebManifest</a></dt>
<dt><a href="http://jmri.org/JavaDoc/doc/jmri/server/web/spi/WebServerConfiguration.html">WebServerConfiguration</a></dt>
</dl>
<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>
<!--#include virtual="/Footer.shtml" -->
</div><!-- closes #mainContent-->
</div><!-- closes #mBody-->
</body>
</html>