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