001package jmri.jmrit.logixng.implementation;
002
003import java.io.PrintWriter;
004import java.util.*;
005import java.util.concurrent.ConcurrentHashMap;
006import java.util.concurrent.CopyOnWriteArrayList;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.OverridingMethodsMustInvokeSuper;
010
011import jmri.*;
012import jmri.implementation.AbstractNamedBean;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.SymbolTable.InitialValueType;
015
016import org.apache.commons.lang3.mutable.MutableInt;
017
018/**
019 * The default implementation of GlobalVariable.
020 *
021 * @author Daniel Bergqvist Copyright 2018
022 * @author Dave Sand        Copyright 2021
023 */
024public class DefaultGlobalVariable extends AbstractNamedBean
025        implements GlobalVariable {
026
027    private Object _value;
028    private InitialValueType _initialValueType = InitialValueType.None;
029    private String _initialValueData;
030
031
032    public DefaultGlobalVariable(String sys, String user) throws BadUserNameException, BadSystemNameException  {
033        super(sys, user);
034
035        // Do this test here to ensure all the tests are using correct system names
036        Manager.NameValidity isNameValid = InstanceManager.getDefault(GlobalVariableManager.class).validSystemNameFormat(mSystemName);
037        if (isNameValid != Manager.NameValidity.VALID) {
038            throw new IllegalArgumentException("system name is not valid");
039        }
040    }
041
042    /** {@inheritDoc} */
043    @Override
044    @OverridingMethodsMustInvokeSuper
045    public void setUserName(@CheckForNull String s) throws BadUserNameException {
046        if ((s == null) || !SymbolTable.validateName(s)) {
047            throw new BadUserNameException(
048                    Bundle.getMessage(Locale.ENGLISH, "VariableNameIsNotValid", s),
049                    Bundle.getMessage(Locale.getDefault(), "VariableNameIsNotValid", s));
050        }
051        super.setUserName(s);
052    }
053
054    /** {@inheritDoc} */
055    @Override
056    @SuppressWarnings({"unchecked", "rawtypes"})    // Checked cast is not possible due to type erasure
057    public void initialize() throws JmriException {
058        SymbolTable symbolTable = new DefaultSymbolTable();
059
060        Object value;
061
062        switch (_initialValueType) {
063
064            case Array:
065                var newArray = SymbolTable.getInitialValue(
066                        SymbolTable.Type.Global,
067                        getUserName(),
068                        _initialValueType,
069                        _initialValueData,
070                        symbolTable,
071                        symbolTable.getSymbols());
072
073                // Convert the array to a thread safe array
074                // https://howtodoinjava.com/java/collections/arraylist/synchronize-arraylist/
075                value = new CopyOnWriteArrayList<>((List)newArray);
076                break;
077
078            case Map:
079                // https://crunchify.com/hashmap-vs-concurrenthashmap-vs-synchronizedmap-how-a-hashmap-can-be-synchronized-in-java/
080                value = new ConcurrentHashMap<>();
081                break;
082
083            default:
084                value = SymbolTable.getInitialValue(
085                        SymbolTable.Type.Global,
086                        getUserName(),
087                        _initialValueType,
088                        _initialValueData,
089                        symbolTable,
090                        symbolTable.getSymbols());
091        }
092
093        if (value != _value) setValue(value);
094    }
095
096    /** {@inheritDoc} */
097    @Override
098    public void setValue(Object value) {
099        Object old = _value;
100        LogixNGPreferences prefs = InstanceManager.getDefault(LogixNGPreferences.class);
101        if (prefs.getStrictTypingLocalVariables()) {
102            _value = SymbolTable.validateStrictTyping(_initialValueType, _value, value);
103        } else {
104            _value = value;
105        }
106        // notify
107        firePropertyChange("value", old, _value);
108    }
109
110    /** {@inheritDoc} */
111    @Override
112    public Object getValue() {
113        return _value;
114    }
115
116    /** {@inheritDoc} */
117    @Override
118    public void setInitialValueType(InitialValueType type) {
119        _initialValueType = type;
120    }
121
122    /** {@inheritDoc} */
123    @Override
124    public InitialValueType getInitialValueType() {
125        return _initialValueType;
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    public void setInitialValueData(String value) {
131        _initialValueData = value;
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public String getInitialValueData() {
137        return _initialValueData;
138    }
139
140    @Override
141    public String getBeanType() {
142        return Bundle.getMessage("BeanNameGlobalVariable");
143    }
144
145    @Override
146    public void setState(int s) throws JmriException {
147        log.warn("Unexpected call to setState in DefaultGlobalVariable.");  // NOI18N
148    }
149
150    @Override
151    public int getState() {
152        log.warn("Unexpected call to getState in DefaultGlobalVariable.");  // NOI18N
153        return UNKNOWN;
154    }
155
156    @Override
157    public String getShortDescription(Locale locale) {
158        return "GlobalVariable";
159    }
160
161    @Override
162    public String getLongDescription(Locale locale) {
163        return "GlobalVariable: "+getDisplayName();
164    }
165
166    @Override
167    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
168        throw new UnsupportedOperationException("Not supported.");
169    }
170
171    @Override
172    public int getChildCount() {
173        throw new UnsupportedOperationException("Not supported.");
174    }
175
176    @Override
177    public Category getCategory() {
178        throw new UnsupportedOperationException("Not supported.");
179    }
180
181    /** {@inheritDoc} */
182    @Override
183    public boolean isActive() {
184        return true;
185    }
186
187    /** {@inheritDoc} */
188    @Override
189    public Base getParent() {
190        return null;
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public void setParent(Base parent) {
196        throw new UnsupportedOperationException("A GlobalVariable cannot have a parent");
197    }
198
199    /** {@inheritDoc} */
200    @Override
201    public boolean setParentForAllChildren(List<String> errors) {
202        throw new UnsupportedOperationException("A GlobalVariable cannot have a parent");
203    }
204
205    /** {@inheritDoc} */
206    @Override
207    public LogixNG getLogixNG() {
208        return null;
209    }
210
211    /** {@inheritDoc} */
212    @Override
213    public ConditionalNG getConditionalNG() {
214        return null;
215    }
216
217    /** {@inheritDoc} */
218    @Override
219    public final Base getRoot() {
220        return this;
221    }
222
223    /** {@inheritDoc} */
224    @Override
225    public void registerListeners() {
226    }
227
228    /** {@inheritDoc} */
229    @Override
230    public void unregisterListeners() {
231    }
232
233    /** {@inheritDoc} */
234    @Override
235    public void printTree(
236            PrintTreeSettings settings,
237            PrintWriter writer,
238            String indent,
239            MutableInt lineNumber) {
240
241        printTree(settings, Locale.getDefault(), writer, indent, "", lineNumber);
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public void printTree(
247            PrintTreeSettings settings,
248            Locale locale,
249            PrintWriter writer,
250            String indent,
251            MutableInt lineNumber) {
252
253        printTree(settings, locale, writer, indent, "", lineNumber);
254    }
255
256    /** {@inheritDoc} */
257    @Override
258    public void printTree(
259            PrintTreeSettings settings,
260            Locale locale,
261            PrintWriter writer,
262            String indent,
263            String currentIndent,
264            MutableInt lineNumber) {
265
266        throw new UnsupportedOperationException("Not supported");
267    }
268
269    @Override
270    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
271        throw new UnsupportedOperationException("Not supported yet.");
272    }
273
274    @Override
275    public Base deepCopyChildren(Base original, Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
276        throw new UnsupportedOperationException("Not supported");
277    }
278
279    @Override
280    public void setup() {
281        throw new UnsupportedOperationException("Not supported");
282    }
283
284    /** {@inheritDoc} */
285    @Override
286    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
287        throw new UnsupportedOperationException("Not supported");
288    }
289
290    /** {@inheritDoc} */
291    @Override
292    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT",
293                                                        justification="Specific log message format")
294    public void getUsageTree(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
295        log.debug("** {} :: {}", level, this.getClass().getName());
296
297        throw new UnsupportedOperationException("Not supported");
298    }
299
300    /** {@inheritDoc} */
301    @Override
302    public void getUsageDetail(int level, NamedBean bean, List<jmri.NamedBeanUsageReport> report, NamedBean cdl) {
303        throw new UnsupportedOperationException("Not supported");
304    }
305
306    /** {@inheritDoc} */
307    @Override
308    public void getListenerRefsIncludingChildren(List<String> list) {
309        list.addAll(getListenerRefs());
310    }
311
312    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultGlobalVariable.class);
313}