001package jmri.jmrit.logixng.implementation.configurexml;
002
003import java.lang.reflect.Constructor;
004import java.lang.reflect.InvocationTargetException;
005import java.util.*;
006
007import jmri.ConfigureManager;
008import jmri.InstanceManager;
009import jmri.configurexml.JmriConfigureXmlException;
010import jmri.jmrit.logixng.*;
011import jmri.jmrit.logixng.implementation.DefaultAnalogActionManager;
012import jmri.jmrit.logixng.implementation.DefaultMaleAnalogActionSocket;
013import jmri.managers.configurexml.AbstractNamedBeanManagerConfigXML;
014import jmri.util.ThreadingUtil;
015
016import org.jdom2.Element;
017
018/**
019 * Provides the functionality for configuring ActionManagers
020 *
021 * @author Dave Duchamp Copyright (c) 2007
022 * @author Daniel Bergqvist Copyright (c) 2018
023 */
024public class DefaultAnalogActionManagerXml extends AbstractManagerXml {
025
026    private final Map<String, Class<?>> xmlClasses = new HashMap<>();
027
028    public DefaultAnalogActionManagerXml() {
029    }
030
031    /**
032     * Default implementation for storing the contents of a AnalogActionManager
033     *
034     * @param o Object to store, of type AnalogActionManager
035     * @return Element containing the complete info
036     */
037    @Override
038    public Element store(Object o) {
039        Element actions = new Element("LogixNGAnalogActions");
040        setStoreElementClass(actions);
041        AnalogActionManager tm = (AnalogActionManager) o;
042        if (tm != null) {
043            if (tm.getNamedBeanSet().isEmpty()) return null;
044            for (MaleAnalogActionSocket action : tm.getNamedBeanSet()) {
045                log.debug("action system name is {}", action.getSystemName() );  // NOI18N
046                try {
047                    List<Element> elements = new ArrayList<>();
048                    // The male socket may be embedded in other male sockets
049                    MaleAnalogActionSocket a = action;
050                    elements.add(storeMaleSocket(a));
051                    while (!(a instanceof DefaultMaleAnalogActionSocket)) {
052                        a = (MaleAnalogActionSocket) a.getObject();
053                    }
054                    Element e = jmri.configurexml.ConfigXmlManager.elementFromObject(a.getObject());
055                    if (e != null) {
056                        for (Element ee : elements) e.addContent(ee);
057                        actions.addContent(e);
058                    } else {
059                        throw new RuntimeException("Cannot load xml configurator for " + a.getObject().getClass().getName());
060                    }
061                } catch (RuntimeException e) {
062                    log.error("Error storing action: {}", e, e);
063                }
064            }
065        }
066        return (actions);
067    }
068
069    /**
070     * Subclass provides implementation to create the correct top element,
071     * including the type information. Default implementation is to use the
072     * local class here.
073     *
074     * @param actions The top-level element being created
075     */
076    public void setStoreElementClass(Element actions) {
077        actions.setAttribute("class", this.getClass().getName());  // NOI18N
078    }
079
080    /**
081     * Create a AnalogActionManager object of the correct class, then register
082     * and fill it.
083     *
084     * @param sharedAction  Shared top level Element to unpack.
085     * @param perNodeAction Per-node top level Element to unpack.
086     * @return true if successful
087     */
088    @Override
089    public boolean load(Element sharedAction, Element perNodeAction) {
090        // create the master object
091        replaceActionManager();
092        // load individual sharedAction
093        loadActions(sharedAction);
094        return true;
095    }
096
097    /**
098     * Utility method to load the individual AnalogActionBean objects. If
099     * there's no additional info needed for a specific action type, invoke
100     * this with the parent of the set of AnalogActionBean elements.
101     *
102     * @param actions Element containing the AnalogActionBean elements to load.
103     */
104    public void loadActions(Element actions) {
105
106        List<Element> actionList = actions.getChildren();  // NOI18N
107        log.debug("Found {} actions", actionList.size());  // NOI18N
108
109        for (int i = 0; i < actionList.size(); i++) {
110
111            String className = actionList.get(i).getAttribute("class").getValue();
112//            log.error("className: " + className);
113
114            Class<?> clazz = xmlClasses.get(className);
115
116            if (clazz == null) {
117                try {
118                    className = jmri.configurexml.ConfigXmlManager.currentClassName(className);
119                    clazz = Class.forName(className);
120                    xmlClasses.put(className, clazz);
121                } catch (ClassNotFoundException ex) {
122                    log.error("cannot load class {}", className, ex);
123                }
124            }
125
126            if (clazz != null) {
127                Constructor<?> c = null;
128                try {
129                    c = clazz.getConstructor();
130                } catch (NoSuchMethodException | SecurityException ex) {
131                    log.error("cannot create constructor", ex);
132                }
133
134                if (c != null) {
135                    try {
136                        AbstractNamedBeanManagerConfigXML o = (AbstractNamedBeanManagerConfigXML)c.newInstance();
137
138                        MaleSocket oldLastItem = InstanceManager.getDefault(AnalogActionManager.class).getLastRegisteredMaleSocket();
139                        o.load(actionList.get(i), null);
140
141                        // Load male socket data if a new bean has been registered
142                        MaleSocket newLastItem = InstanceManager.getDefault(AnalogActionManager.class).getLastRegisteredMaleSocket();
143                        if (newLastItem != oldLastItem) loadMaleSocket(actionList.get(i), newLastItem);
144                        else throw new RuntimeException("No new bean has been added. This class: "+getClass().getName());
145                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
146                        log.error("cannot create object", ex);
147                    } catch (JmriConfigureXmlException ex) {
148                        log.error("cannot load action", ex);
149                    }
150                }
151            }
152        }
153    }
154
155    /**
156     * Replace the current AnalogActionManager, if there is one, with one newly
157     * created during a load operation. This is skipped if they are of the same
158     * absolute type.
159     */
160    protected void replaceActionManager() {
161        if (InstanceManager.getDefault(jmri.jmrit.logixng.AnalogActionManager.class).getClass().getName()
162                .equals(DefaultAnalogActionManager.class.getName())) {
163            return;
164        }
165        // if old manager exists, remove it from configuration process
166        if (InstanceManager.getNullableDefault(jmri.jmrit.logixng.AnalogActionManager.class) != null) {
167            ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
168            if (cmOD != null) {
169                cmOD.deregister(InstanceManager.getDefault(jmri.jmrit.logixng.AnalogActionManager.class));
170            }
171
172        }
173
174        ThreadingUtil.runOnGUI(() -> {
175            // register new one with InstanceManager
176            DefaultAnalogActionManager pManager = DefaultAnalogActionManager.instance();
177            InstanceManager.store(pManager, AnalogActionManager.class);
178            // register new one for configuration
179            ConfigureManager cmOD = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
180            if (cmOD != null) {
181                cmOD.registerConfig(pManager, jmri.Manager.LOGIXNG_ANALOG_ACTIONS);
182            }
183        });
184    }
185
186    @Override
187    public int loadOrder() {
188        return InstanceManager.getDefault(jmri.jmrit.logixng.AnalogActionManager.class).getXMLOrder();
189    }
190
191    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultAnalogActionManagerXml.class);
192}