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 if 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. 253 * A nonce is a TurnoutOperation created specifically for one 254 * turnout, which can't be directly referred to by name. 255 * It does have a 256 * name, which is the turnout it was created for, prefixed by "*" 257 * 258 * @return true if this object is a nonce 259 */ 260 public boolean isNonce() { 261 return nonce; 262 } 263 264 public void setNonce(boolean n) { 265 nonce = n; 266 InstanceManager.getDefault(TurnoutOperationManager.class).firePropertyChange("Content", null, null); 267 } 268 269 public TurnoutOperation makeNonce(Turnout t) { 270 TurnoutOperation op = makeCopy("*" + t.getSystemName()); 271 op.setNonce(true); 272 return op; 273 } 274 275 /** 276 * @param mode feedback mode for a turnout 277 * @return true if this operation's feedback mode is one we know how to 278 * deal with 279 */ 280 public boolean matchFeedbackMode(int mode) { 281 return (mode & feedbackModes) != 0; 282 } 283 284 /** 285 * Get the ToolTip for the Turnout Operator. 286 * @return String or null. 287 */ 288 public String getToolTip(){ 289 return null; 290 } 291 292}