001package jmri;
002
003import javax.annotation.Nonnull;
004import jmri.beans.PropertyChangeSupport;
005import jmri.implementation.AbstractTurnout;
006
007/**
008 * Framework for automating reliable turnout operation. This interface allows a
009 * particular style (e.g. retries) to be implemented and then to have multiple
010 * instances for variations in parameters if required
011 * <p>
012 * This mechanism is designed to extensible to allow new operation types (e.g.
013 * for Tortoise-style point machines) and to allow individual system types to
014 * change it, for example to allow operation with alternative feedback
015 * arrangements.
016 * <p>
017 * The TurnoutOperation class is at the heart of things, although there are
018 * several other classes, partly to fit in with JMRI's package structure. Each
019 * specific retry scheme has its own concrete subclass of TurnoutOperation. One
020 * instance of each such class is created at startup. It has the same name as
021 * the prefix to the class, and is called the "defining instance". Further
022 * instances can exist with different parameter values (e.g. number of retries).
023 * <p>
024 * The TurnoutOperationManager class (only one instance) keeps track of the
025 * instances and can retrieve them by name. It can also supply a suitable
026 * TurnoutOperation for a given turnout, based on the feedback type, if the
027 * turnout does not identify one for itself.
028 * <p>
029 * Each AbstractTurnout may have a reference to a TurnoutOperation class, which
030 * may be unique to this turnout or may be shared. When the turnout is thrown,
031 * if it has its own TurnoutOperation, this is used (unless the turnout has
032 * selected no automation). Otherwise, the TurnoutOperationManager is asked to
033 * find one.
034 * <p>
035 * The TurnoutOperation has a factory method (getOperation) which is called when
036 * a turnout is operated, to supply the operator. Each subclass of
037 * TurnoutOperation has a corresponding subclass of TurnoutOperator, which
038 * contains the logic for the retry scheme. Each operator runs in its own
039 * thread, which terminates when the operation is complete. If another operation
040 * of the same turnout is made before the first one completes, the older thread
041 * terminates itself when it realises it is no longer the active operation for
042 * the turnout.
043 * <p>
044 * The parameters of a TurnoutOperation can be edited. Each subclass has its own
045 * xxxTurnoutOperationConfig class, which knows how to display the parameters in
046 * a JPanel and gather them up again and store them afterwards.
047 * <p>
048 * Each subclass also has its own xxxTurnoutOperationXml class, which knows how
049 * to store the information in an XML element, and restore it.
050 * <p>
051 * The current code defines three operations, NoFeedback, Raw and Sensor.
052 * Because these have so much in common
053 * (only the xxxTurnoutOperator class has any differences),
054 * most of them are implemented in the CommonTurnout... classes.
055 * This family is not part of the general structure, although it can be reused
056 * if it helps.
057 * <p>
058 * <b>Extensibility</b>
059 * <p>
060 * To write a new type of operation:
061 * <ol>
062 *  <li>Create the xxxTurnoutOperation class</li>
063 *  <li>Create the xxxTurnoutOperator class, including the logic for what
064 *  you're trying to do</li>
065 *  <li>Create the xxxTurnoutOperationConfig class - the
066 *  CommonTurnoutOperationConfig class can be used as a reference</li>
067 *  <li>Create the xxxTurnoutOperationXml class - again the Common... class can
068 *  be used as a reference</li>
069 *  <li>Add the prefix to the class name (e.g. "Tortoise") to the list
070 *  AbstractTurnoutManager.validOperationTypes, otherwise it will not be
071 *  instantiated at startup and hence will not be available</li>
072 * </ol>
073 * <p>
074 * To change the behavior for a particular system type:
075 * <p>
076 * There are some functions which can be overridden in the system-specific
077 * subclasses to change default behaviour if desired. These mechanisms are
078 * orthogonal to the operation subclasses.
079 * <ol>
080 *  <li>Override AbstractTurnoutManager.getValidOperationTypes to change the
081 *  operation types allowed for this system</li>
082 *  <li>Override AbstractTurnout.getFeedbackModeForOperation to map
083 *  system-specific feedback modes into modes that the general classes know
084 *  about</li>
085 *  <li>Override AbstractTurnout.getTurnoutOperator if you want to do
086 *  something <i>really</i> different</li>
087 * </ol>
088 *
089 * @author John Harper Copyright 2005
090 */
091public abstract class TurnoutOperation extends PropertyChangeSupport implements Comparable<Object> {
092
093    String name;
094    int feedbackModes = 0;
095    boolean nonce = false;  // created just for one turnout and not reusable 
096
097    TurnoutOperation(@Nonnull String n) {
098        name = n;
099    }
100
101    /**
102     * Factory to make a copy of an operation identical in all respects except
103     * the name.
104     *
105     * @param n name for new copy
106     * @return TurnoutOperation of same concrete class as this
107     */
108    public abstract TurnoutOperation makeCopy(@Nonnull String n);
109
110    /**
111     * Set feedback modes - part of construction but done separately for
112     * ordering problems.
113     *
114     * @param fm valid feedback modes for this class
115     */
116    protected void setFeedbackModes(int fm) {
117        feedbackModes = fm;
118    }
119
120    /**
121     * Get the descriptive name of the operation.
122     *
123     * @return name
124     */
125    @Nonnull
126    public String getName() {
127        return name;
128    }
129
130    /**
131     * Ordering by name so operations can be sorted on name.
132     *
133     * @param other other TurnoutOperation object
134     * @return usual compareTo return values
135     */
136    @Override
137    public int compareTo(Object other) {
138        return name.compareTo(((TurnoutOperation) other).name);
139    }
140
141    /**
142     * The identity of an operation is its name.
143     */
144    @Override
145    public boolean equals(Object ro) {
146        if (ro == null) return false;
147        if (ro instanceof TurnoutOperation)
148            return name.equals(((TurnoutOperation)ro).name);
149        else 
150            return false;
151    }
152    
153    @Override
154    public int hashCode() {
155        return name.hashCode();
156    }
157    
158    /**
159     *
160     * @param other another TurnoutOperation
161     * @return true iff the two operations are equivalent, i.e. same subclass
162     *         and same parameters
163     */
164    public abstract boolean equivalentTo(TurnoutOperation other);
165
166    /**
167     * Rename an operation.
168     *
169     * @param newName new name to use for rename attempt
170     * @return true if the name was changed to the new value - otherwise name
171     *         is unchanged
172     */
173    public boolean rename(@Nonnull String newName) {
174        boolean result = false;
175        TurnoutOperationManager mgr = InstanceManager.getDefault(TurnoutOperationManager.class);
176        if (!isDefinitive() && mgr.getOperation(newName) == null) {
177            mgr.removeOperation(this);
178            name = newName;
179            setNonce(false);
180            mgr.addOperation(this);
181            result = true;
182        }
183        return result;
184    }
185
186    /**
187     * Get the definitive operation for this parameter variation.
188     *
189     * @return definitive operation
190     */
191    public TurnoutOperation getDefinitive() {
192        String[] myClass = this.getClass().getName().split("\\.");
193        String finalClass = myClass[myClass.length - 1];
194        String mySubclass = finalClass.substring(0, finalClass.indexOf("TurnoutOperation"));
195        return InstanceManager.getDefault(TurnoutOperationManager.class).getOperation(mySubclass);
196    }
197
198    /**
199     *
200     * @return true if this is the "defining instance" of the class, which we
201     *         determine by the name of the instance being the same as the
202     *         prefix of the class
203     */
204    public boolean isDefinitive() {
205        String[] classNames = this.getClass().getName().split("\\.");
206        String className = classNames[classNames.length - 1];
207        String opName = getName() + "TurnoutOperation";
208        return (className.equalsIgnoreCase(opName));
209    }
210
211    /**
212     * Get an instance of the operator for this operation type, set up and
213     * started to do its thing in a private thread for the specified turnout.
214     *
215     * @param t the turnout to apply the operation to
216     * @return the operator
217     */
218    public abstract TurnoutOperator getOperator(@Nonnull AbstractTurnout t);
219
220    /**
221     * Delete all knowledge of this operation. Reset any turnouts using it to
222     * the default.
223     */
224    public void dispose() {
225        if (!isDefinitive()) {
226            InstanceManager.getDefault(TurnoutOperationManager.class).removeOperation(this);
227            name = "*deleted";
228            firePropertyChange("Deleted", null, null);  // this will remove all dangling references
229        }
230    }
231
232    public boolean isDeleted() {
233        return (name.equals("*deleted"));
234    }
235
236    /**
237     * See if operation is in use (needed by the UI).
238     *
239     * @return true if any turnouts are using it
240     */
241    public boolean isInUse() {
242        TurnoutManager tm = InstanceManager.turnoutManagerInstance();
243        for (Turnout t : tm.getNamedBeanSet()) {
244            if (t != null && t.getTurnoutOperation() == this) {
245                return true;
246            }
247        }
248        return false;
249    }
250
251    /**
252     * Nonce support. A nonce is a TurnoutOperation created specifically for one
253     * turnout, which can't be directly referred to by name. It does have a
254     * name, which is the turnout it was created for, prefixed by "*"
255     *
256     * @return true if this object is a nonce
257     */
258    public boolean isNonce() {
259        return nonce;
260    }
261
262    public void setNonce(boolean n) {
263        nonce = n;
264        InstanceManager.getDefault(TurnoutOperationManager.class).firePropertyChange("Content", null, null);
265    }
266
267    public TurnoutOperation makeNonce(Turnout t) {
268        TurnoutOperation op = makeCopy("*" + t.getSystemName());
269        op.setNonce(true);
270        return op;
271    }
272
273    /**
274     * @param mode feedback mode for a turnout
275     * @return true if this operation's feedback mode is one we know how to
276     *         deal with
277     */
278    public boolean matchFeedbackMode(int mode) {
279        return (mode & feedbackModes) != 0;
280    }
281
282}