001package jmri.jmrit.logixng.implementation;
002
003import java.util.*;
004
005import javax.annotation.Nonnull;
006
007import jmri.*;
008import jmri.jmrit.logixng.*;
009import jmri.jmrit.logixng.Module;
010import jmri.jmrit.logixng.Stack;
011import jmri.jmrit.logixng.util.LogixNG_Thread;
012import jmri.util.*;
013
014/**
015 * The default implementation of ConditionalNG.
016 *
017 * @author Daniel Bergqvist Copyright 2019
018 */
019public class DefaultConditionalNG extends AbstractBase
020        implements ConditionalNG, FemaleSocketListener {
021
022    private final LogixNG_Thread _thread;
023    private int _startupThreadId;
024    private Base _parent = null;
025    private String _socketSystemName = null;
026    private final FemaleDigitalActionSocket _femaleSocket;
027    private boolean _enabled = true;
028    private boolean _executeAtStartup = true;
029    private final ExecuteLock _executeLock = new ExecuteLock();
030    private boolean _runDelayed = true;
031    private final Stack _stack = new DefaultStack();
032    private SymbolTable _symbolTable;
033
034
035    public DefaultConditionalNG(String sys, String user)
036            throws BadUserNameException, BadSystemNameException  {
037        this(sys, user, LogixNG_Thread.DEFAULT_LOGIXNG_THREAD);
038    }
039
040    public DefaultConditionalNG(String sys, String user, int threadID)
041            throws BadUserNameException, BadSystemNameException  {
042        super(sys, user);
043
044        _startupThreadId = threadID;
045        _thread = LogixNG_Thread.getThread(threadID);
046        _thread.setThreadInUse();
047
048        // Do this test here to ensure all the tests are using correct system names
049        Manager.NameValidity isNameValid = InstanceManager.getDefault(ConditionalNG_Manager.class).validSystemNameFormat(mSystemName);
050        if (isNameValid != Manager.NameValidity.VALID) {
051            throw new IllegalArgumentException("system name is not valid");
052        }
053        _femaleSocket = new DefaultFemaleDigitalActionSocket(this, this, "A");
054    }
055
056    /** {@inheritDoc} */
057    @Override
058    public LogixNG_Thread getCurrentThread() {
059        return _thread;
060    }
061
062    /** {@inheritDoc} */
063    @Override
064    public int getStartupThreadId() {
065        return _startupThreadId;
066    }
067
068    /** {@inheritDoc} */
069    @Override
070    public void setStartupThreadId(int threadId) {
071        int oldStartupThreadId = _startupThreadId;
072        _startupThreadId = threadId;
073        firePropertyChange("Thread", oldStartupThreadId, _startupThreadId);
074    }
075
076    /** {@inheritDoc} */
077    @Override
078    public Base getParent() {
079        return _parent;
080    }
081
082    /** {@inheritDoc} */
083    @Override
084    public void setParent(Base parent) {
085        _parent = parent;
086
087        if (isActive()) registerListeners();
088        else unregisterListeners();
089    }
090
091    /** {@inheritDoc} */
092    @Override
093    public FemaleDigitalActionSocket getFemaleSocket() {
094        return _femaleSocket;
095    }
096
097    /** {@inheritDoc} */
098    @Override
099    public void setRunDelayed(boolean value) {
100        _runDelayed = value;
101    }
102
103    /** {@inheritDoc} */
104    @Override
105    public boolean getRunDelayed() {
106        return _runDelayed;
107    }
108
109    private void runOnLogixNG_Thread(
110            @Nonnull ThreadingUtil.ThreadAction ta,
111            boolean allowRunDelayed) {
112
113        if (_runDelayed && allowRunDelayed) {
114            _thread.runOnLogixNGEventually(ta);
115        } else {
116            _thread.runOnLogixNG(ta);
117        }
118    }
119
120    /** {@inheritDoc} */
121    @Override
122    public void execute() {
123        if (_executeAtStartup || !getLogixNG().isStartup()) {
124            if (_executeLock.once()) {
125                runOnLogixNG_Thread(new ExecuteTask(this, _executeLock, null), true);
126            }
127        }
128    }
129
130    /** {@inheritDoc} */
131    @Override
132    public void execute(boolean allowRunDelayed) {
133        if (_executeLock.once()) {
134            runOnLogixNG_Thread(new ExecuteTask(this, _executeLock, null), allowRunDelayed);
135        }
136    }
137
138    /** {@inheritDoc} */
139    @Override
140    public void execute(FemaleDigitalActionSocket socket) {
141        runOnLogixNG_Thread(() -> {internalExecute(this, socket);}, true);
142    }
143
144    /**
145     * Executes a LogixNG Module.
146     * @param module      The module to be executed
147     * @param parameters  The parameters
148     */
149    public static void executeModule(Module module, Map<String, Object> parameters)
150            throws IllegalArgumentException {
151
152        if (module == null) {
153            throw new IllegalArgumentException("The parameter \"module\" is null");
154        }
155        if (!(module.getRootSocket() instanceof DefaultFemaleDigitalActionSocket)) {
156            throw new IllegalArgumentException("The module " + module.getDisplayName() + " is not a DigitalActionModule");
157        }
158        if (parameters == null) {
159            throw new IllegalArgumentException("The parameter \"parameters\" is null");
160        }
161
162        LogixNG_Thread thread = LogixNG_Thread.getThread(LogixNG_Thread.DEFAULT_LOGIXNG_THREAD);
163        ConditionalNG conditionalNG = new DefaultConditionalNG("IQC0000000", null);
164        InternalFemaleSocket socket = new InternalFemaleSocket(conditionalNG, module, parameters);
165        thread.runOnLogixNGEventually(() -> { internalExecute(conditionalNG, socket); });
166    }
167
168    private static class InternalFemaleSocket extends DefaultFemaleDigitalActionSocket {
169
170        private final ConditionalNG _conditionalNG;
171        private final Module _module;
172        private final Map<String, Object> _parameters;
173
174        public InternalFemaleSocket(ConditionalNG conditionalNG, Module module, Map<String, Object> parameters) {
175            super(null, new FemaleSocketListener(){
176                @Override
177                public void connected(FemaleSocket socket) {
178                    // Do nothing
179                }
180
181                @Override
182                public void disconnected(FemaleSocket socket) {
183                    // Do nothing
184                }
185            }, "A");
186            _conditionalNG = conditionalNG;
187            _module = module;
188            _parameters = parameters;
189        }
190
191        @Override
192        public void execute() throws JmriException {
193            FemaleSocket socket = _module.getRootSocket();
194            if (!(socket instanceof DefaultFemaleDigitalActionSocket)) {
195                throw new IllegalArgumentException("The module " + _module.getDisplayName() + " is not a DigitalActionModule");
196            }
197
198            synchronized(this) {
199                SymbolTable oldSymbolTable = _conditionalNG.getSymbolTable();
200                DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(_conditionalNG);
201                List<Module.ParameterData> _parameterData = new ArrayList<>();
202                for (Module.Parameter p : _module.getParameters()) {
203                    _parameterData.add(new Module.ParameterData(
204                            p.getName(), SymbolTable.InitialValueType.None, "",
205                            Module.ReturnValueType.None, ""));
206                }
207                newSymbolTable.createSymbols(_conditionalNG.getSymbolTable(), _parameterData);
208                for (var entry : _parameters.entrySet()) {
209                    newSymbolTable.setValue(entry.getKey(), entry.getValue());
210                }
211                _conditionalNG.setSymbolTable(newSymbolTable);
212
213                ((DefaultFemaleDigitalActionSocket)socket).execute();
214                _conditionalNG.setSymbolTable(oldSymbolTable);
215            }
216        }
217    }
218
219    private static void internalExecute(ConditionalNG conditionalNG, FemaleDigitalActionSocket femaleSocket) {
220        if (conditionalNG.isEnabled()) {
221            DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG);
222
223            try {
224                conditionalNG.setCurrentConditionalNG(conditionalNG);
225
226                conditionalNG.setSymbolTable(newSymbolTable);
227
228                LogixNG logixNG = conditionalNG.getLogixNG();
229                InlineLogixNG inlineLogixNG = null;
230                if (logixNG != null) {
231                    inlineLogixNG = logixNG.getInlineLogixNG();
232                }
233                if (inlineLogixNG != null) {
234                    List<SymbolTable.VariableData> localVariables = new ArrayList<>();
235                    localVariables.add(new SymbolTable.VariableData(
236                            "__InlineLogixNG__", SymbolTable.InitialValueType.String,
237                            inlineLogixNG.getNameString()));
238//                    localVariables.add(new SymbolTable.VariableData(
239//                            "__PositionableId__", SymbolTable.InitialValueType.String,
240//                            inlineLogixNG.getId()));
241                    localVariables.add(new SymbolTable.VariableData(
242                            "__Editor__", SymbolTable.InitialValueType.String,
243                            inlineLogixNG.getEditorName()));
244                    newSymbolTable.createSymbols(localVariables);
245                }
246
247                if (femaleSocket != null) {
248                    femaleSocket.execute();
249                } else {
250                    conditionalNG.getFemaleSocket().execute();
251                }
252            } catch (ReturnException | ExitException e) {
253                // A Return action in a ConditionalNG causes a ReturnException so this is okay.
254                // An Exit action in a ConditionalNG causes a ExitException so this is okay.
255            } catch (PassThruException e) {
256                // This happens due to a a Break action or a Continue action that isn't handled.
257                log.info("ConditionalNG {} was aborted during execute: {}",
258                        conditionalNG.getSystemName(), e.getCause(), e.getCause());
259            } catch (AbortConditionalNGExecutionException e) {
260                if (InstanceManager.getDefault(LogixNGPreferences.class).getShowSystemNameInException()) {
261                    log.warn("ConditionalNG {} was aborted during execute in the item {}: {}",
262                            conditionalNG.getSystemName(), e.getMaleSocket().getSystemName(), e.getCause(), e.getCause());
263                } else {
264                    log.warn("ConditionalNG {} was aborted during execute: {}",
265                            conditionalNG.getSystemName(), e.getCause(), e.getCause());
266                }
267            } catch (JmriException | RuntimeException e) {
268//                LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}",
269//                        conditionalNG.getSystemName(), e, e);
270                log.warn("ConditionalNG {} got an exception during execute: {}",
271                        conditionalNG.getSystemName(), e, e);
272            }
273
274            conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable());
275        }
276    }
277
278    private static class ExecuteTask implements ThreadingUtil.ThreadAction {
279
280        private final ConditionalNG _conditionalNG;
281        private final ExecuteLock _executeLock;
282        private final FemaleDigitalActionSocket _localFemaleSocket;
283
284        public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) {
285            _conditionalNG = conditionalNG;
286            _executeLock = executeLock;
287            _localFemaleSocket = femaleSocket;
288        }
289
290        @Override
291        public void run() {
292            while (_executeLock.loop()) {
293                internalExecute(_conditionalNG, _localFemaleSocket);
294            }
295        }
296
297    }
298
299    /**
300     * Set the current ConditionalNG.
301     * @param conditionalNG the current ConditionalNG
302     */
303    @Override
304    public void setCurrentConditionalNG(ConditionalNG conditionalNG) {
305        if (this != conditionalNG) {
306            throw new UnsupportedOperationException("The new conditionalNG must be the same as myself");
307        }
308        for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) {
309            m.setCurrentConditionalNG(conditionalNG);
310        }
311    }
312
313    /** {@inheritDoc} */
314    @Override
315    public Stack getStack() {
316        return _stack;
317    }
318
319    /** {@inheritDoc} */
320    @Override
321    public SymbolTable getSymbolTable() {
322        return _symbolTable;
323    }
324
325    /** {@inheritDoc} */
326    @Override
327    public void setSymbolTable(SymbolTable symbolTable) {
328        _symbolTable = symbolTable;
329    }
330
331    @Override
332    public String getBeanType() {
333        return Bundle.getMessage("BeanNameConditionalNG");
334    }
335
336    @Override
337    public void setState(int s) throws JmriException {
338        log.warn("Unexpected call to setState in DefaultConditionalNG.");  // NOI18N
339    }
340
341    @Override
342    public int getState() {
343        log.warn("Unexpected call to getState in DefaultConditionalNG.");  // NOI18N
344        return UNKNOWN;
345    }
346
347    @Override
348    public void connected(FemaleSocket socket) {
349        _socketSystemName = socket.getConnectedSocket().getSystemName();
350    }
351
352    @Override
353    public void disconnected(FemaleSocket socket) {
354        _socketSystemName = null;
355    }
356
357    @Override
358    public String getShortDescription(Locale locale) {
359        return "ConditionalNG: "+getDisplayName();
360    }
361
362    @Override
363    public String getLongDescription(Locale locale) {
364        if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) {
365            return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName();
366        }
367        return "ConditionalNG: "+getDisplayName();
368    }
369
370    @Override
371    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
372        if (index != 0) {
373            throw new IllegalArgumentException(
374                    String.format("index has invalid value: %d", index));
375        }
376
377        return _femaleSocket;
378    }
379
380    @Override
381    public int getChildCount() {
382        return 1;
383    }
384
385    @Override
386    public Category getCategory() {
387        throw new UnsupportedOperationException("Not supported.");
388    }
389
390    @Override
391    public void setSocketSystemName(String systemName) {
392        if ((systemName == null) || (!systemName.equals(_socketSystemName))) {
393            _femaleSocket.disconnect();
394        }
395        _socketSystemName = systemName;
396    }
397
398    @Override
399    public String getSocketSystemName() {
400        return _socketSystemName;
401    }
402
403    /** {@inheritDoc} */
404    @Override
405    final public void setup() {
406        if (!_femaleSocket.isConnected()
407                || !_femaleSocket.getConnectedSocket().getSystemName()
408                        .equals(_socketSystemName)) {
409
410            _femaleSocket.disconnect();
411
412            if (_socketSystemName != null) {
413                try {
414                    MaleSocket maleSocket =
415                            InstanceManager.getDefault(DigitalActionManager.class)
416                                    .getBySystemName(_socketSystemName);
417                    if (maleSocket != null) {
418                        _femaleSocket.connect(maleSocket);
419                        maleSocket.setup();
420                    } else {
421                        log.error("digital action is not found: {}", _socketSystemName);
422                    }
423                } catch (SocketAlreadyConnectedException ex) {
424                    // This shouldn't happen and is a runtime error if it does.
425                    throw new RuntimeException("socket is already connected");
426                }
427            }
428        } else {
429            _femaleSocket.setup();
430        }
431    }
432
433    /** {@inheritDoc} */
434    @Override
435    final public void disposeMe() {
436        _femaleSocket.dispose();
437    }
438
439    /** {@inheritDoc} */
440    @Override
441    public void setEnabled(boolean enable) {
442        _enabled = enable;
443        if (isActive()) {
444            LogixNG logixNG = getLogixNG();
445            if ((logixNG != null) && logixNG.isActive()) {
446                registerListeners();
447                if (_executeAtStartup) {
448                    execute();
449                }
450            }
451        } else {
452            unregisterListeners();
453        }
454    }
455
456    /** {@inheritDoc} */
457    @Override
458    public boolean isEnabled() {
459        return _enabled;
460    }
461
462    /** {@inheritDoc} */
463    @Override
464    public void setExecuteAtStartup(boolean value) {
465        _executeAtStartup = value;
466    }
467
468    /** {@inheritDoc} */
469    @Override
470    public boolean isExecuteAtStartup() {
471        return _executeAtStartup;
472    }
473
474    /** {@inheritDoc} */
475    @Override
476    public synchronized boolean isListenersRegistered() {
477        return _listenersAreRegistered;
478    }
479
480    /** {@inheritDoc} */
481    @Override
482    public synchronized void registerListenersForThisClass() {
483        _listenersAreRegistered = true;
484    }
485
486    /** {@inheritDoc} */
487    @Override
488    public synchronized void unregisterListenersForThisClass() {
489        _listenersAreRegistered = false;
490    }
491
492    @Override
493    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
494        throw new UnsupportedOperationException("Not supported yet.");
495    }
496
497    @Override
498    public boolean existsInTree() {
499        return true;
500    }
501
502    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class);
503
504}