001package jmri; 002 003import java.lang.reflect.InvocationTargetException; 004import java.util.Collection; 005import java.util.Iterator; 006import java.util.LinkedList; 007import java.util.List; 008import java.util.Objects; 009import java.util.SortedMap; 010import java.util.TreeMap; 011import javax.annotation.Nonnull; 012 013/** 014 * class to look after the collection of TurnoutOperation subclasses Unlike the 015 * other xxxManager, this does not inherit from AbstractManager since the 016 * resources it deals with are not DCC system resources but rather purely 017 * internal state. 018 * 019 * @author John Harper Copyright 2005 020 * 021 */ 022public class TurnoutOperationManager implements InstanceManagerAutoDefault { 023 024 private final SortedMap<String, TurnoutOperation> turnoutOperations = new TreeMap<>(); 025 private List<TurnoutOperation> operationTypes = new LinkedList<>(); // array of the defining instances of each class, held in order of appearance 026 boolean doOperations = false; // global on/off switch 027 028 public TurnoutOperationManager() { 029 } 030 031 private boolean initialized = false; 032 033 /** 034 * Does deferred initialization. 035 * <p> 036 * This is deferred because it invokes 037 * loadOperationTypes, which gets the current turnout manager, often the 038 * proxy manager, which in turn can invoke loadOperationTypes again. 039 */ 040 private void initialize() { 041 if (!initialized) { 042 initialized = true; 043 // create the default instances of each of the known operation types 044 loadOperationTypes(); 045 } 046 } 047 048 public void dispose() { 049 } 050 051 public TurnoutOperation[] getTurnoutOperations() { 052 synchronized (this) { 053 initialize(); 054 Collection<TurnoutOperation> entries = turnoutOperations.values(); 055 return entries.toArray(new TurnoutOperation[0]); 056 } 057 } 058 059 /** 060 * add a new operation Silently replaces any existing operation with the 061 * same name 062 * 063 * @param op {@link TurnoutOperation} to add/replace 064 */ 065 protected void addOperation(@Nonnull TurnoutOperation op) { 066 Objects.requireNonNull(op, "TurnoutOperations cannot be null"); 067 TurnoutOperation previous; 068 synchronized (this) { 069 initialize(); 070 previous = turnoutOperations.put(op.getName(), op); 071 if (op.isDefinitive()) { 072 updateTypes(op); 073 } 074 } 075 if (previous != null) { 076 log.debug("replaced existing operation called {}", previous.getName()); 077 } 078 firePropertyChange("Content", null, null); 079 } 080 081 protected void removeOperation(@Nonnull TurnoutOperation op) { 082 Objects.requireNonNull(op, "TurnoutOperations cannot be null"); 083 synchronized (this) { 084 initialize(); 085 turnoutOperations.remove(op.getName()); 086 } 087 firePropertyChange("Content", null, null); 088 } 089 090 /** 091 * Find a TurnoutOperation by its name. 092 * 093 * @param name name of {@link TurnoutOperation} to retrieve. 094 * @return the operation 095 */ 096 public TurnoutOperation getOperation(@Nonnull String name) { 097 synchronized (this) { 098 initialize(); 099 return turnoutOperations.get(name); 100 } 101 } 102 103 /** 104 * update the list of types to include a new or updated definitive instance. 105 * since order is important we retain the existing order, placing a new type 106 * at the end if necessary 107 * 108 * @param op new or updated operation 109 */ 110 private void updateTypes(@Nonnull TurnoutOperation op) { 111 initialize(); 112 LinkedList<TurnoutOperation> newTypes = new LinkedList<>(); 113 Iterator<TurnoutOperation> iter = operationTypes.iterator(); 114 boolean found = false; 115 while (iter.hasNext()) { 116 TurnoutOperation item = iter.next(); 117 if (item.getClass() == op.getClass()) { 118 newTypes.add(op); 119 found = true; 120 log.debug("replacing definitive instance of {}", item.getClass()); 121 } else { 122 newTypes.add(item); 123 } 124 } 125 if (!found) { 126 newTypes.add(op); 127 log.debug("adding definitive instance of {}", op.getClass()); 128 } 129 operationTypes = newTypes; 130 } 131 132 /** 133 * Load the operation types given by the current TurnoutManager instance, in 134 * the order given. 135 * <p> 136 * The order is important because the acceptable feedback modes may overlap. 137 * All we do is instantiate the classes. The constructors take care of 138 * putting everything in the right places. We allow multiple occurrences of 139 * the same name without complaining so the Proxy stuff works. 140 * 141 * There's a threading problem here, because this invokes gets the current 142 * turnout manager, often the proxy manager, which in turn invokes 143 * loadOperationTypes again. This is bad. It's not clear why it even works. 144 * 145 */ 146 public void loadOperationTypes() { 147 String[] validTypes = InstanceManager.turnoutManagerInstance().getValidOperationTypes(); 148 for (int i = 0; i < validTypes.length; ++i) { 149 String thisClassName = "jmri." + validTypes[i] + "TurnoutOperation"; 150 if (validTypes[i] == null) { 151 log.warn("null operation name in loadOperationTypes"); 152 } else if (getOperation(validTypes[i]) == null) { 153 try { 154 Class<?> thisClass = Class.forName(thisClassName); 155 // creating the instance invokes the TurnoutOperation ctor, 156 // which calls addOperation here, which adds it to the 157 // turnoutOperations map. 158 thisClass.getDeclaredConstructor().newInstance(); 159 log.debug("loaded TurnoutOperation class {}", thisClassName); 160 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e1) { 161 log.error("during loadOperationTypes", e1); 162 } 163 } 164 } 165 } 166 167 /** 168 * Find a suitable operation for this turnout, based on its feedback type. 169 * The mode is passed separately so the caller can transform it 170 * 171 * @param t turnout 172 * @param apparentMode Turnout Feedback mode(s) to be used when finding a matching operation 173 * @return the turnout operation 174 */ 175 public TurnoutOperation getMatchingOperationAlways(@Nonnull Turnout t, int apparentMode) { 176 initialize(); 177 Iterator<TurnoutOperation> iter = operationTypes.iterator(); 178 TurnoutOperation currentMatch = null; 179 /* The loop below always returns the LAST operation 180 that matches. In the standard feedback modes, 181 This currently results in returning the NoFeedback 182 operation, since it is the last one added to 183 operationTypes */ 184 while (iter.hasNext()) { 185 TurnoutOperation oper = iter.next(); 186 if (oper.matchFeedbackMode(apparentMode)) { 187 currentMatch = oper; 188 } 189 } 190 if (currentMatch != null) { 191 return currentMatch; 192 } else { 193 return null; 194 } 195 } 196 197 /** 198 * Find the correct operation for this turnout. 199 * If operations are globally disabled, return null. 200 * 201 * @param t turnout 202 * @param apparentMode mode(s) to be used when finding a matching operation 203 * @return operation 204 */ 205 public TurnoutOperation getMatchingOperation(@Nonnull Turnout t, int apparentMode) { 206 initialize(); 207 if (doOperations) { 208 return getMatchingOperationAlways(t, apparentMode); 209 } 210 return null; 211 } 212 213 public TurnoutOperation getMatchingOperationAlways(@Nonnull Turnout t) { 214 return getMatchingOperationAlways(t, t.getFeedbackMode()); 215 } 216 217 /** 218 * Get ( potentially update ) status of whether operations are in use. 219 * @return true if in use, else false. 220 */ 221 public boolean getDoOperations() { 222 initialize(); 223 return doOperations; 224 } 225 226 /** 227 * Set that Turnout Operations are in use. 228 * @param b true to use, else false to disable. 229 */ 230 public void setDoOperations(boolean b) { 231 initialize(); 232 boolean oldValue = doOperations; 233 doOperations = b; 234 firePropertyChange("doOperations", oldValue, b); 235 } 236 237 /** 238 * Proxy support. Take a concatenation of operation type lists from multiple 239 * systems and turn it into a single list, by eliminating duplicates and 240 * ensuring that NoFeedback - which matches anything - comes at the end if 241 * it is present at all. 242 * 243 * @param types list of types possibly containing duplicates 244 * @return list reduced as described above 245 */ 246 static public String[] concatenateTypeLists(@Nonnull String[] types) { 247 List<String> outTypes = new LinkedList<>(); 248 boolean noFeedbackWanted = false; 249 for (String type : types) { 250 if ("NoFeedback".equals(type)) { 251 noFeedbackWanted = true; 252 } else if (type == null || type.isEmpty()) { 253 log.warn("null or empty operation name returned from turnout manager"); 254 } else if (!outTypes.contains(type)) { 255 outTypes.add(type); 256 } 257 } 258 if (noFeedbackWanted) { 259 outTypes.add("NoFeedback"); 260 } 261 return outTypes.toArray(new String[0]); 262 } 263 264 /** 265 * Property change support. 266 */ 267 java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this); 268 269 public synchronized void addPropertyChangeListener(@Nonnull java.beans.PropertyChangeListener l) { 270 pcs.addPropertyChangeListener(l); 271 } 272 273 public synchronized void removePropertyChangeListener(@Nonnull java.beans.PropertyChangeListener l) { 274 pcs.removePropertyChangeListener(l); 275 } 276 277 protected void firePropertyChange(@Nonnull String p, Object old, Object n) { 278 pcs.firePropertyChange(p, old, n); 279 } 280 281 /** 282 * Get a ToolTip or descriptive comment for the Operator. 283 * @param operatorName name of the Turnout Operator 284 * @param t The Turnout that the Operator would operate 285 * @return Descriptive String, or null. 286 */ 287 public String getTooltipForOperator(String operatorName, Turnout t){ 288 if (operatorName == null){ 289 return null; 290 } 291 if ( operatorName.equals(Bundle.getMessage("TurnoutOperationOff"))) { 292 return Bundle.getMessage("TurnoutOperationOffTip"); 293 } 294 if ( t != null && operatorName.equals(Bundle.getMessage("TurnoutOperationDefault"))) { 295 return Bundle.getMessage("UseGlobal", getMatchingOperationAlways(t).getName()); 296 } 297 for ( TurnoutOperation to : getTurnoutOperations() ) { 298 if (operatorName.equals(to.getName())) { 299 return to.getToolTip(); 300 } 301 } 302 return null; 303 } 304 305 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutOperationManager.class); 306}