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