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}