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 (AbortConditionalNG_IgnoreException | ReturnException | ExitException e) {
253                // A AbortConditionalNG_IgnoreException should be ignored.
254                // A Return action in a ConditionalNG causes a ReturnException so this is okay.
255                // An Exit action in a ConditionalNG causes a ExitException so this is okay.
256            } catch (PassThruException e) {
257                // This happens due to a a Break action or a Continue action that isn't handled.
258                log.info("ConditionalNG {} was aborted during execute: {}",
259                        conditionalNG.getSystemName(), e.getCause(), e.getCause());
260            } catch (AbortConditionalNGExecutionException e) {
261                if (InstanceManager.getDefault(LogixNGPreferences.class).getShowSystemNameInException()) {
262                    log.warn("ConditionalNG {} was aborted during execute in the item {}: {}",
263                            conditionalNG.getSystemName(), e.getMaleSocket().getSystemName(), e.getCause(), e.getCause());
264                } else {
265                    log.warn("ConditionalNG {} was aborted during execute: {}",
266                            conditionalNG.getSystemName(), e.getCause(), e.getCause());
267                }
268            } catch (JmriException | RuntimeException e) {
269//                LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}",
270//                        conditionalNG.getSystemName(), e, e);
271                log.warn("ConditionalNG {} got an exception during execute: {}",
272                        conditionalNG.getSystemName(), e, e);
273            }
274
275            conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable());
276        }
277    }
278
279    private static class ExecuteTask implements ThreadingUtil.ThreadAction {
280
281        private final ConditionalNG _conditionalNG;
282        private final ExecuteLock _executeLock;
283        private final FemaleDigitalActionSocket _localFemaleSocket;
284
285        public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) {
286            _conditionalNG = conditionalNG;
287            _executeLock = executeLock;
288            _localFemaleSocket = femaleSocket;
289        }
290
291        @Override
292        public void run() {
293            while (_executeLock.loop()) {
294                internalExecute(_conditionalNG, _localFemaleSocket);
295            }
296        }
297
298    }
299
300    /**
301     * Set the current ConditionalNG.
302     * @param conditionalNG the current ConditionalNG
303     */
304    @Override
305    public void setCurrentConditionalNG(ConditionalNG conditionalNG) {
306        if (this != conditionalNG) {
307            throw new UnsupportedOperationException("The new conditionalNG must be the same as myself");
308        }
309        for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) {
310            m.setCurrentConditionalNG(conditionalNG);
311        }
312    }
313
314    /** {@inheritDoc} */
315    @Override
316    public Stack getStack() {
317        return _stack;
318    }
319
320    /** {@inheritDoc} */
321    @Override
322    public SymbolTable getSymbolTable() {
323        return _symbolTable;
324    }
325
326    /** {@inheritDoc} */
327    @Override
328    public void setSymbolTable(SymbolTable symbolTable) {
329        _symbolTable = symbolTable;
330    }
331
332    @Override
333    public String getBeanType() {
334        return Bundle.getMessage("BeanNameConditionalNG");
335    }
336
337    @Override
338    public void setState(int s) throws JmriException {
339        log.warn("Unexpected call to setState in DefaultConditionalNG.");  // NOI18N
340    }
341
342    @Override
343    public int getState() {
344        log.warn("Unexpected call to getState in DefaultConditionalNG.");  // NOI18N
345        return UNKNOWN;
346    }
347
348    @Override
349    public void connected(FemaleSocket socket) {
350        _socketSystemName = socket.getConnectedSocket().getSystemName();
351    }
352
353    @Override
354    public void disconnected(FemaleSocket socket) {
355        _socketSystemName = null;
356    }
357
358    @Override
359    public String getShortDescription(Locale locale) {
360        return "ConditionalNG: "+getDisplayName();
361    }
362
363    @Override
364    public String getLongDescription(Locale locale) {
365        if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) {
366            return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName();
367        }
368        return "ConditionalNG: "+getDisplayName();
369    }
370
371    @Override
372    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
373        if (index != 0) {
374            throw new IllegalArgumentException(
375                    String.format("index has invalid value: %d", index));
376        }
377
378        return _femaleSocket;
379    }
380
381    @Override
382    public int getChildCount() {
383        return 1;
384    }
385
386    @Override
387    public Category getCategory() {
388        throw new UnsupportedOperationException("Not supported.");
389    }
390
391    @Override
392    public void setSocketSystemName(String systemName) {
393        if ((systemName == null) || (!systemName.equals(_socketSystemName))) {
394            _femaleSocket.disconnect();
395        }
396        _socketSystemName = systemName;
397    }
398
399    @Override
400    public String getSocketSystemName() {
401        return _socketSystemName;
402    }
403
404    /** {@inheritDoc} */
405    @Override
406    final public void setup() {
407        if (!_femaleSocket.isConnected()
408                || !_femaleSocket.getConnectedSocket().getSystemName()
409                        .equals(_socketSystemName)) {
410
411            _femaleSocket.disconnect();
412
413            if (_socketSystemName != null) {
414                try {
415                    MaleSocket maleSocket =
416                            InstanceManager.getDefault(DigitalActionManager.class)
417                                    .getBySystemName(_socketSystemName);
418                    if (maleSocket != null) {
419                        _femaleSocket.connect(maleSocket);
420                        maleSocket.setup();
421                    } else {
422                        log.error("digital action is not found: {}", _socketSystemName);
423                    }
424                } catch (SocketAlreadyConnectedException ex) {
425                    // This shouldn't happen and is a runtime error if it does.
426                    throw new RuntimeException("socket is already connected");
427                }
428            }
429        } else {
430            _femaleSocket.setup();
431        }
432    }
433
434    /** {@inheritDoc} */
435    @Override
436    final public void disposeMe() {
437        _femaleSocket.dispose();
438    }
439
440    /** {@inheritDoc} */
441    @Override
442    public void setEnabled(boolean enable) {
443        _enabled = enable;
444        if (isActive()) {
445            LogixNG logixNG = getLogixNG();
446            if ((logixNG != null) && logixNG.isActive()) {
447                registerListeners();
448                if (_executeAtStartup) {
449                    execute();
450                }
451            }
452        } else {
453            unregisterListeners();
454        }
455    }
456
457    /** {@inheritDoc} */
458    @Override
459    public boolean isEnabled() {
460        return _enabled;
461    }
462
463    /** {@inheritDoc} */
464    @Override
465    public void setExecuteAtStartup(boolean value) {
466        _executeAtStartup = value;
467    }
468
469    /** {@inheritDoc} */
470    @Override
471    public boolean isExecuteAtStartup() {
472        return _executeAtStartup;
473    }
474
475    /** {@inheritDoc} */
476    @Override
477    public synchronized boolean isListenersRegistered() {
478        return _listenersAreRegistered;
479    }
480
481    /** {@inheritDoc} */
482    @Override
483    public synchronized void registerListenersForThisClass() {
484        _listenersAreRegistered = true;
485    }
486
487    /** {@inheritDoc} */
488    @Override
489    public synchronized void unregisterListenersForThisClass() {
490        _listenersAreRegistered = false;
491    }
492
493    @Override
494    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
495        throw new UnsupportedOperationException("Not supported yet.");
496    }
497
498    @Override
499    public boolean existsInTree() {
500        return true;
501    }
502
503    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class);
504
505}