001package jmri.jmrit.logixng.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.beans.*;
006import java.io.PrintWriter;
007import java.util.*;
008
009import javax.annotation.Nonnull;
010import javax.annotation.OverridingMethodsMustInvokeSuper;
011
012import jmri.*;
013import jmri.jmrit.logixng.*;
014import jmri.managers.AbstractManager;
015import jmri.util.LoggingUtil;
016import jmri.util.ThreadingUtil;
017
018/**
019 * Class providing the basic logic of the GlobalVariable_Manager interface.
020 *
021 * @author Dave Duchamp       Copyright (C) 2007
022 * @author Daniel Bergqvist   Copyright (C) 2022
023 */
024public class DefaultGlobalVariableManager extends AbstractManager<GlobalVariable>
025        implements GlobalVariableManager {
026
027
028    public DefaultGlobalVariableManager() {
029        // The GlobalVariablePreferences class may load plugins so we must ensure
030        // it's loaded here.
031        InstanceManager.getDefault(LogixNGPreferences.class);
032    }
033
034    @Override
035    public int getXMLOrder() {
036        return LOGIXNG_GLOBAL_VARIABLES;
037    }
038
039    @Override
040    public char typeLetter() {
041        return 'Q';
042    }
043
044    /**
045     * Test if parameter is a properly formatted system name.
046     *
047     * @param systemName the system name
048     * @return enum indicating current validity, which might be just as a prefix
049     */
050    @Override
051    public NameValidity validSystemNameFormat(String systemName) {
052        return LogixNG_Manager.validSystemNameFormat(
053                getSubSystemNamePrefix(), systemName);
054    }
055
056    /**
057     * Method to create a new GlobalVariable if the GlobalVariable does not exist.
058     * <p>
059     * Returns null if a GlobalVariable with the same systemName or userName
060     * already exists, or if there is trouble creating a new GlobalVariable.
061     */
062    @Override
063    public GlobalVariable createGlobalVariable(String systemName, String userName)
064            throws IllegalArgumentException {
065
066        // Check that GlobalVariable does not already exist
067        GlobalVariable x;
068        if (userName != null && !userName.isEmpty()) {
069            x = getByUserName(userName);
070            if (x != null) {
071                return null;
072            }
073        }
074        x = getBySystemName(systemName);
075        if (x != null) {
076            return null;
077        }
078        // Check if system name is valid
079        if (this.validSystemNameFormat(systemName) != NameValidity.VALID) {
080            throw new IllegalArgumentException("SystemName " + systemName + " is not in the correct format");
081        }
082        // GlobalVariable does not exist, create a new GlobalVariable
083        x = new DefaultGlobalVariable(systemName, userName);
084        // save in the maps
085        register(x);
086
087        // Keep track of the last created auto system name
088        updateAutoNumber(systemName);
089
090        return x;
091    }
092
093    @Override
094    public GlobalVariable createGlobalVariable(String userName) throws IllegalArgumentException {
095        return createGlobalVariable(getAutoSystemName(), userName);
096    }
097
098    @Override
099    public GlobalVariable getGlobalVariable(String name) {
100        GlobalVariable x = getByUserName(name);
101        if (x != null) {
102            return x;
103        }
104        return getBySystemName(name);
105    }
106
107    @Override
108    public GlobalVariable getByUserName(String name) {
109        return _tuser.get(name);
110    }
111
112    @Override
113    public GlobalVariable getBySystemName(String name) {
114        return _tsys.get(name);
115    }
116
117    /** {@inheritDoc} */
118    @Override
119    public String getBeanTypeHandled(boolean plural) {
120        return Bundle.getMessage(plural ? "BeanNameGlobalVariables" : "BeanNameGlobalVariable");
121    }
122
123    /** {@inheritDoc} */
124    @Override
125    public void deleteGlobalVariable(GlobalVariable x) {
126        // delete the GlobalVariable
127        deregister(x);
128        x.dispose();
129    }
130
131    /** {@inheritDoc} */
132    @Override
133    public void printTree(Locale locale, PrintWriter writer, String indent) {
134        for (GlobalVariable globalVariable : getNamedBeanSet()) {
135            writer.append(String.format(
136                    "Global variable: System name: %s, User name: %s, Initial value type: %s, Initial value data: %s",
137                    globalVariable.getSystemName(), globalVariable.getUserName(),
138                    globalVariable.getInitialValueType().toString(), globalVariable.getInitialValueData()));
139            writer.println();
140        }
141        writer.println();
142    }
143
144    static volatile DefaultGlobalVariableManager _instance = null;
145
146    @InvokeOnGuiThread  // this method is not thread safe
147    public static DefaultGlobalVariableManager instance() {
148        if (!ThreadingUtil.isGUIThread()) {
149            LoggingUtil.warnOnce(log, "instance() called on wrong thread");
150        }
151
152        if (_instance == null) {
153            _instance = new DefaultGlobalVariableManager();
154        }
155        return (_instance);
156    }
157
158    /** {@inheritDoc} */
159    @Override
160    public Class<GlobalVariable> getNamedBeanClass() {
161        return GlobalVariable.class;
162    }
163
164    /**
165     * Inform all registered listeners of a vetoable change.If the propertyName
166     * is "CanDelete" ALL listeners with an interest in the bean will throw an
167     * exception, which is recorded returned back to the invoking method, so
168     * that it can be presented back to the user.However if a listener decides
169     * that the bean can not be deleted then it should throw an exception with
170     * a property name of "DoNotDelete", this is thrown back up to the user and
171     * the delete process should be aborted.
172     *
173     * @param p   The programmatic name of the property that is to be changed.
174     *            "CanDelete" will inquire with all listeners if the item can
175     *            be deleted. "DoDelete" tells the listener to delete the item.
176     * @param old The old value of the property.
177     * @throws java.beans.PropertyVetoException If the recipients wishes the
178     *                                          delete to be aborted (see above)
179     */
180    @OverridingMethodsMustInvokeSuper
181    public void fireVetoableChange(String p, Object old) throws PropertyVetoException {
182        PropertyChangeEvent evt = new PropertyChangeEvent(this, p, old, null);
183        for (VetoableChangeListener vc : vetoableChangeSupport.getVetoableChangeListeners()) {
184            vc.vetoableChange(evt);
185        }
186    }
187
188    /** {@inheritDoc} */
189    @Override
190    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
191            justification = "Further investigation is needed to handle this correctly")
192    public final void deleteBean(@Nonnull GlobalVariable globalVariable, @Nonnull String property) throws PropertyVetoException {
193        // throws PropertyVetoException if vetoed
194        fireVetoableChange(property, globalVariable);
195        if ( PROPERTY_DO_DELETE.equals(property)) {
196            deregister(globalVariable);
197            globalVariable.dispose();
198        }
199    }
200
201    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultGlobalVariableManager.class);
202
203}