001package jmri.managers.configurexml; 002 003import java.awt.GraphicsEnvironment; 004 005import java.util.ArrayList; 006import java.util.List; 007import java.util.SortedSet; 008 009import javax.annotation.Nonnull; 010 011import jmri.Conditional; 012import jmri.ConditionalAction; 013import jmri.ConditionalManager; 014import jmri.ConditionalVariable; 015import jmri.InstanceManager; 016import jmri.Logix; 017import jmri.implementation.DefaultConditional; 018import jmri.implementation.DefaultConditionalAction; 019import jmri.managers.DefaultConditionalManager; 020import jmri.util.swing.JmriJOptionPane; 021 022import org.jdom2.Element; 023 024/** 025 * Provides the functionality for configuring ConditionalManagers. 026 * 027 * @author Dave Duchamp Copyright (c) 2007 028 * @author Pete Cressman Copyright (C) 2009, 2011 029 */ 030public class DefaultConditionalManagerXml extends jmri.managers.configurexml.AbstractNamedBeanManagerConfigXML { 031 032 public DefaultConditionalManagerXml() { 033 } 034 035 /** 036 * Default implementation for storing the contents of a ConditionalManager 037 * 038 * @param o Object to store, of type ConditionalManager 039 * @return Element containing the complete info 040 */ 041 @Override 042 public Element store(Object o) { 043 // long numCond = 0; 044 // long numStateVars = 0; 045 Element conditionals = new Element("conditionals"); // NOI18N 046 setStoreElementClass(conditionals); 047 ConditionalManager cm = (ConditionalManager) o; 048 if (cm != null) { 049 SortedSet<Conditional> condList = cm.getNamedBeanSet(); 050 // don't return an element if there are no conditionals to include 051 if (condList.isEmpty()) { 052 return null; 053 } 054 for (Conditional c : condList) { 055 // store the conditionals 056 // numCond++; 057 // long condTime = System.currentTimeMillis(); 058 String cName = c.getSystemName(); 059 log.debug("conditional system name is {}", cName); // NOI18N 060 061 Element elem = new Element("conditional"); // NOI18N 062 063 // As a work-around for backward compatibility, store systemName and userName as attributes. 064 // TODO Remove this in e.g. JMRI 4.11.1 and then update all the loadref comparison files 065 elem.setAttribute("systemName", cName); // NOI18N 066 String uName = c.getUserName(); 067 if (uName != null && !uName.isEmpty()) { 068 elem.setAttribute("userName", uName); // NOI18N 069 } 070 071 elem.addContent(new Element("systemName").addContent(cName)); 072 073 // store common parts 074 storeCommon(c, elem); 075 elem.setAttribute("antecedent", c.getAntecedentExpression()); // NOI18N 076 elem.setAttribute("logicType", Integer.toString(c.getLogicType().getIntValue())); // NOI18N 077 if (c.getTriggerOnChange()) { 078 elem.setAttribute("triggerOnChange", "yes"); // NOI18N 079 } else { 080 elem.setAttribute("triggerOnChange", "no"); // NOI18N 081 } 082 083 // save child state variables 084 // Creating StateVariables gets very slow when more than c10,000 exist. 085 // creation time goes from less than 1ms to more than 5000ms. 086 // Don't need a clone for read-only use. 087// List <ConditionalVariable> variableList = c.getCopyOfStateVariables(); 088 List<ConditionalVariable> variableList = ((DefaultConditional) c).getStateVariableList(); 089 /* numStateVars += variableList.size(); 090 if (numCond>1190) { 091 partTime = System.currentTimeMillis() - partTime; 092 System.out.println("time to for getCopyOfStateVariables "+partTime+"ms. total stateVariable= "+numStateVars); 093 }*/ 094 for (ConditionalVariable variable : variableList) { 095 Element vElem = new Element("conditionalStateVariable"); // NOI18N 096 int oper = variable.getOpern().getIntValue(); 097 vElem.setAttribute("operator", Integer.toString(oper)); // NOI18N 098 if (variable.isNegated()) { 099 vElem.setAttribute("negated", "yes"); // NOI18N 100 } else { 101 vElem.setAttribute("negated", "no"); // NOI18N 102 } 103 vElem.setAttribute("type", Integer.toString(variable.getType().getIntValue())); // NOI18N 104 vElem.setAttribute("systemName", variable.getName()); // NOI18N 105 vElem.setAttribute("dataString", variable.getDataString()); // NOI18N 106 vElem.setAttribute("num1", Integer.toString(variable.getNum1())); // NOI18N 107 vElem.setAttribute("num2", Integer.toString(variable.getNum2())); // NOI18N 108 if (variable.doTriggerActions()) { 109 vElem.setAttribute("triggersCalc", "yes"); // NOI18N 110 } else { 111 vElem.setAttribute("triggersCalc", "no"); // NOI18N 112 } 113 elem.addContent(vElem); 114 } 115 // save action information 116 List<ConditionalAction> actionList = c.getCopyOfActions(); 117 for (ConditionalAction action : actionList) { 118 Element aElem = new Element("conditionalAction"); // NOI18N 119 aElem.setAttribute("option", Integer.toString(action.getOption())); // NOI18N 120 aElem.setAttribute("type", Integer.toString(action.getType().getIntValue())); // NOI18N 121 aElem.setAttribute("systemName", action.getDeviceName()); // NOI18N 122 aElem.setAttribute("data", Integer.toString(action.getActionData())); // NOI18N 123 // To allow regression of config files back to previous releases 124 // add "delay" attribute 125 try { 126 Integer.parseInt(action.getActionString()); 127 aElem.setAttribute("delay", action.getActionString()); // NOI18N 128 } catch (NumberFormatException nfe) { 129 aElem.setAttribute("delay", "0"); // NOI18N 130 } 131 aElem.setAttribute("string", action.getActionString()); // NOI18N 132 elem.addContent(aElem); 133 } 134 conditionals.addContent(elem); 135 /* condTime = System.currentTimeMillis() - condTime; 136 if (condTime>1) { 137 System.out.println(numCond+"th Conditional \""+sName+"\" took "+condTime+"ms to store."); 138 }*/ 139 } 140 } 141 // System.out.println("Elapsed time to store "+numCond+" Conditional "+(System.currentTimeMillis()-time)+"ms."); 142 return (conditionals); 143 } 144 145 /** 146 * Subclass provides implementation to create the correct top element, 147 * including the type information. Default implementation is to use the 148 * local class here. 149 * 150 * @param conditionals The top-level element being created 151 */ 152 public void setStoreElementClass(Element conditionals) { 153 conditionals.setAttribute("class", this.getClass().getName()); // NOI18N 154 } 155 156 /** 157 * Create a ConditionalManager object of the correct class, then register 158 * and fill it. 159 * 160 * @param sharedConditionals Shared top level Element to unpack. 161 * @param perNodeConditionals Per-node top level Element to unpack. 162 * @return true if successful 163 */ 164 @Override 165 public boolean load(@Nonnull Element sharedConditionals, Element perNodeConditionals) { 166 // create the master object 167 replaceConditionalManager(); 168 // load individual logixs 169 loadConditionals(sharedConditionals); 170 return true; 171 } 172 173 /** 174 * Utility method to load the individual Logix objects. If there's no 175 * additional info needed for a specific Logix type, invoke this with the 176 * parent of the set of Logix elements. 177 * 178 * @param conditionals Element containing the Logix elements to load. 179 */ 180 public void loadConditionals(Element conditionals) { 181 List<Element> conditionalList = conditionals.getChildren("conditional"); // NOI18N 182 log.debug("Found {} conditionals", conditionalList.size()); // NOI18N 183 ConditionalManager cm = InstanceManager.getDefault(jmri.ConditionalManager.class); 184 185 String systemNamePrefix = cm.getSystemNamePrefix(); 186 int namesChanged = 0; 187 188 for (Element condElem : conditionalList) { 189 String sysName = getSystemName(condElem); 190 if (sysName == null) { 191 log.warn("unexpected null in systemName {}", condElem); // NOI18N 192 break; 193 } 194 195 if (!sysName.startsWith(systemNamePrefix)) { 196 String old = sysName; 197 sysName = systemNamePrefix + ":" + sysName; 198 log.warn("Converting Conditional system name from {} to {}", old, sysName); 199 namesChanged++; 200 } 201 202 // omitted username is treated as empty, not null 203 String userName = getUserName(condElem); 204 if (userName == null) { 205 userName = ""; 206 } 207 208 log.debug("create conditional: ({})({})", sysName, userName); // NOI18N 209 210 // Try getting the conditional. This should fail 211 Conditional c = cm.getBySystemName(sysName); 212 if (c == null) { 213 // Check for parent Logix 214 Logix x = cm.getParentLogix(sysName); 215 if (x == null) { 216 log.warn("Conditional '{}' has no parent Logix", sysName); // NOI18N 217 continue; 218 } 219 220 // Found a potential parent Logix, check the Logix index 221 boolean inIndex = false; 222 for (int j = 0; j < x.getNumConditionals(); j++) { 223 String cName = x.getConditionalByNumberOrder(j); 224 if (sysName.equals(cName)) { 225 inIndex = true; 226 break; 227 } 228 } 229 if (!inIndex) { 230 log.warn("Conditional '{}' is not in the Logix index", sysName); // NOI18N 231 continue; 232 } 233 234 // Create the condtional 235 c = cm.createNewConditional(sysName, userName); 236 } 237 238 if (c == null) { 239 // Should never get here 240 log.error("Conditional '{}' cannot be created", sysName); // NOI18N 241 continue; 242 } 243 244 // conditional already exists 245 // load common parts 246 loadCommon(c, condElem); 247 248 String ant = ""; 249 int logicType = Conditional.ALL_AND; 250 if (condElem.getAttribute("antecedent") != null) { // NOI18N 251 String antTemp = condElem.getAttribute("antecedent").getValue(); // NOI18N 252 ant = jmri.jmrit.conditional.ConditionalEditBase.translateAntecedent(antTemp, true); 253 } 254 if (condElem.getAttribute("logicType") != null) { // NOI18N 255 logicType = Integer.parseInt( 256 condElem.getAttribute("logicType").getValue()); // NOI18N 257 } 258 c.setLogicType(Conditional.AntecedentOperator.getOperatorFromIntValue(logicType), ant); 259 260 // load state variables, if there are any 261 List<Element> conditionalVarList = condElem.getChildren("conditionalStateVariable"); // NOI18N 262 263 // Note: Because things like (R1 or R2) and R3) return to positions in the 264 // list of state variables, we can't just append when re-reading a conditional; 265 // we have to drop any existing ConditionalStateVariables and create a clean, new list. 266 267 if (conditionalVarList.size() == 0) { 268 log.warn("No state variables found for conditional {}", sysName); // NOI18N 269 } 270 ArrayList<ConditionalVariable> variableList = new ArrayList<>(); 271 for (Element cvar : conditionalVarList) { 272 ConditionalVariable variable = new ConditionalVariable(); 273 if (cvar.getAttribute("operator") == null) { // NOI18N 274 log.warn("unexpected null in operator {} {}", cvar, // NOI18N 275 cvar.getAttributes()); 276 } else { 277 int oper = Integer.parseInt(cvar 278 .getAttribute("operator").getValue()); // NOI18N 279 // Adjust old, lt 4.13.4, xml content 280 if (oper == 2) oper = 4; 281 if (oper == 3) oper = 1; 282 if (oper == 6) oper = 5; 283 Conditional.Operator operator = Conditional.Operator.getOperatorFromIntValue(oper); 284 variable.setOpern(operator); 285 } 286 if (cvar.getAttribute("negated") != null) { // NOI18N 287 // NOI18N 288 variable.setNegation("yes".equals(cvar.getAttribute("negated").getValue())); 289 } 290 variable.setType(Conditional.Type.getOperatorFromIntValue( 291 Integer.parseInt(cvar.getAttribute("type").getValue()))); // NOI18N 292 293 String tempName = cvar.getAttribute("systemName").getValue(); // NOI18N 294 variable.setName(tempName.equals("RTXINITIALIZER") ? systemNamePrefix + ":" + tempName : tempName); // NOI18N 295 296 if (cvar.getAttribute("dataString") != null) { // NOI18N 297 variable.setDataString(cvar 298 .getAttribute("dataString").getValue()); // NOI18N 299 } 300 if (cvar.getAttribute("num1") != null) { // NOI18N 301 variable.setNum1(Integer.parseInt(cvar 302 .getAttribute("num1").getValue())); // NOI18N 303 } 304 if (cvar.getAttribute("num2") != null) { // NOI18N 305 variable.setNum2(Integer.parseInt(cvar 306 .getAttribute("num2").getValue())); // NOI18N 307 } 308 variable.setTriggerActions(true); 309 if (cvar.getAttribute("triggersCalc") != null) { // NOI18N 310 if ("no".equals(cvar 311 .getAttribute("triggersCalc").getValue())) { // NOI18N 312 variable.setTriggerActions(false); 313 } 314 } 315 variableList.add(variable); 316 } 317 c.setStateVariables(variableList); 318 319 // load actions - there better be some 320 List<Element> conditionalActionList = condElem.getChildren("conditionalAction"); // NOI18N 321 322 // Really OK, since a user may use such conditionals to define a reusable 323 // expression of state variables. These conditions are then used as a 324 // state variable in other conditionals. (pwc) 325 //if (conditionalActionList.size() == 0) { 326 // log.warn("No actions found for conditional {}", sysName); 327 //} 328 List<ConditionalAction> actionList = ((DefaultConditional)c).getActionList(); 329 org.jdom2.Attribute attr; 330 for (Element cact : conditionalActionList) { 331 ConditionalAction action = new DefaultConditionalAction(); 332 attr = cact.getAttribute("option"); // NOI18N 333 if (attr != null) { 334 action.setOption(Integer.parseInt(attr.getValue())); 335 } else { 336 log.warn("unexpected null in option {} {}", cact, // NOI18N 337 cact.getAttributes()); 338 } 339 // actionDelay is removed. delay data is stored as a String to allow 340 // such data be referenced by internal memory. 341 // For backward compatibility, set delay "int" as a string 342 attr = cact.getAttribute("delay"); // NOI18N 343 if (attr != null) { 344 action.setActionString(attr.getValue()); 345 } 346 attr = cact.getAttribute("type"); // NOI18N 347 if (attr != null) { 348 action.setType(Conditional.Action.getOperatorFromIntValue(Integer.parseInt(attr.getValue()))); 349 } else { 350 log.warn("unexpected null in type {} {}", cact, 351 cact.getAttributes()); // NOI18N 352 } 353 attr = cact.getAttribute("systemName"); // NOI18N 354 if (attr != null) { 355 action.setDeviceName(attr.getValue()); 356 } else { 357 log.warn("unexpected null in systemName {} {}", cact, // NOI18N 358 cact.getAttributes()); 359 } 360 attr = cact.getAttribute("data"); // NOI18N 361 if (attr != null) { 362 action.setActionData(Integer.parseInt(attr.getValue())); 363 } else { 364 log.warn("unexpected null in action data {} {}", cact, // NOI18N 365 cact.getAttributes()); 366 } 367 attr = cact.getAttribute("string"); // NOI18N 368 if (attr != null) { 369 action.setActionString(attr.getValue()); 370 } else { 371 log.warn("unexpected null in action string {} {}", cact, // NOI18N 372 cact.getAttributes()); 373 } 374 if (!actionList.contains(action)) actionList.add(action); 375 } 376 c.setAction(actionList); 377 378 // 1/16/2011 - trigger for execution of the action list changed to execute each 379 // time state is computed. Formerly execution of the action list was done only 380 // when state changes. All conditionals are upgraded to this new policy. 381 // However, for conditionals with actions that toggle on change of state 382 // the old policy should be used. 383 boolean triggerOnChange = false; 384 if (condElem.getAttribute("triggerOnChange") != null) { // NOI18N 385 if ("yes".equals(condElem.getAttribute("triggerOnChange").getValue())) { // NOI18N 386 triggerOnChange = true; 387 } 388 } else { 389 /* Don't upgrade -Let old be as is 390 for (int k=0; k<actionList.size(); k++){ 391 ConditionalAction action = actionList.get(k); 392 if (action.getOption()==Conditional.ACTION_OPTION_ON_CHANGE){ 393 triggerOnChange = true; 394 break; 395 } 396 } 397 */ 398 triggerOnChange = true; 399 } 400 c.setTriggerOnChange(triggerOnChange); 401 } 402 403 if (namesChanged > 0) { 404 // TODO: replace the System property check with an in-application mechanism 405 // for notifying users of multiple changes that can be silenced as part of 406 // normal operations 407 if (!GraphicsEnvironment.isHeadless() && !Boolean.getBoolean("jmri.test.no-dialogs")) { 408 JmriJOptionPane.showMessageDialog(null, 409 Bundle.getMessage(namesChanged > 1 ? "ConditionalManager.SystemNamesChanged.Message" : "ConditionalManager.SystemNameChanged.Message", namesChanged), 410 Bundle.getMessage("Manager.SystemNamesChanged.Title", namesChanged, cm.getBeanTypeHandled(namesChanged > 1)), 411 JmriJOptionPane.WARNING_MESSAGE); 412 } 413 log.warn("System names for {} Conditionals changed; this may have operational impacts.", namesChanged); 414 } 415 } 416 417 /** 418 * Replace the current ConditionalManager, if there is one, with one newly 419 * created during a load operation. This is skipped if they are of the same 420 * absolute type. 421 */ 422 protected void replaceConditionalManager() { 423 if (InstanceManager.getDefault(jmri.ConditionalManager.class).getClass().getName() 424 .equals(DefaultConditionalManager.class.getName())) { 425 return; 426 } 427 // if old manager exists, remove it from configuration process 428 if (InstanceManager.getNullableDefault(jmri.ConditionalManager.class) != null) { 429 InstanceManager.getDefault(jmri.ConfigureManager.class).deregister( 430 InstanceManager.getDefault(jmri.ConditionalManager.class)); 431 } 432 // register new one with InstanceManager 433 DefaultConditionalManager pManager = InstanceManager.getDefault(DefaultConditionalManager.class); 434 InstanceManager.store(pManager, ConditionalManager.class); 435 InstanceManager.setDefault(ConditionalManager.class, pManager); 436 // register new one for configuration 437 InstanceManager.getDefault(jmri.ConfigureManager.class).registerConfig(pManager, jmri.Manager.CONDITIONALS); 438 } 439 440 @Override 441 public int loadOrder() { 442 return InstanceManager.getDefault(jmri.ConditionalManager.class).getXMLOrder(); 443 } 444 445 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalManagerXml.class); 446 447}