001package jmri.jmrit.logixng.implementation;
002
003import java.util.Locale;
004import java.util.Map;
005
006import static jmri.NamedBean.UNKNOWN;
007
008import javax.annotation.Nonnull;
009
010import jmri.InstanceManager;
011import jmri.JmriException;
012import jmri.Manager;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.LogixNG_Thread;
015import jmri.util.*;
016
017/**
018 * The default implementation of ConditionalNG.
019 *
020 * @author Daniel Bergqvist Copyright 2019
021 */
022public class DefaultConditionalNG extends AbstractBase
023        implements ConditionalNG, FemaleSocketListener {
024
025    private final LogixNG_Thread _thread;
026    private int _startupThreadId;
027    private Base _parent = null;
028    private String _socketSystemName = null;
029    private final FemaleDigitalActionSocket _femaleSocket;
030    private boolean _enabled = 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 (_executeLock.once()) {
126            runOnLogixNG_Thread(new ExecuteTask(this, _executeLock, null), true);
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    private static void internalExecute(ConditionalNG conditionalNG, FemaleDigitalActionSocket femaleSocket) {
145        if (conditionalNG.isEnabled()) {
146            DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG);
147
148            try {
149                conditionalNG.setSymbolTable(newSymbolTable);
150                if (femaleSocket != null) {
151                    femaleSocket.execute();
152                } else {
153                    conditionalNG.getFemaleSocket().execute();
154                }
155            } catch (JmriException | RuntimeException e) {
156//                LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}",
157//                        conditionalNG.getSystemName(), e, e);
158                log.warn("ConditionalNG {} got an exception during execute: {}",
159                        conditionalNG.getSystemName(), e, e);
160            }
161
162            conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable());
163        }
164    }
165
166    private static class ExecuteTask implements ThreadingUtil.ThreadAction {
167
168        private final ConditionalNG _conditionalNG;
169        private final ExecuteLock _executeLock;
170        private final FemaleDigitalActionSocket _localFemaleSocket;
171
172        public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) {
173            _conditionalNG = conditionalNG;
174            _executeLock = executeLock;
175            _localFemaleSocket = femaleSocket;
176        }
177
178        @Override
179        public void run() {
180            while (_executeLock.loop()) {
181                internalExecute(_conditionalNG, _localFemaleSocket);
182            }
183        }
184
185    }
186
187    /** {@inheritDoc} */
188    @Override
189    public Stack getStack() {
190        return _stack;
191    }
192
193    /** {@inheritDoc} */
194    @Override
195    public SymbolTable getSymbolTable() {
196        return _symbolTable;
197    }
198
199    /** {@inheritDoc} */
200    @Override
201    public void setSymbolTable(SymbolTable symbolTable) {
202        _symbolTable = symbolTable;
203    }
204
205    @Override
206    public String getBeanType() {
207        return Bundle.getMessage("BeanNameConditionalNG");
208    }
209
210    @Override
211    public void setState(int s) throws JmriException {
212        log.warn("Unexpected call to setState in DefaultConditionalNG.");  // NOI18N
213    }
214
215    @Override
216    public int getState() {
217        log.warn("Unexpected call to getState in DefaultConditionalNG.");  // NOI18N
218        return UNKNOWN;
219    }
220
221    @Override
222    public void connected(FemaleSocket socket) {
223        _socketSystemName = socket.getConnectedSocket().getSystemName();
224    }
225
226    @Override
227    public void disconnected(FemaleSocket socket) {
228        _socketSystemName = null;
229    }
230
231    @Override
232    public String getShortDescription(Locale locale) {
233        return "ConditionalNG: "+getDisplayName();
234    }
235
236    @Override
237    public String getLongDescription(Locale locale) {
238        if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) {
239            return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName();
240        }
241        return "ConditionalNG: "+getDisplayName();
242    }
243
244    @Override
245    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
246        if (index != 0) {
247            throw new IllegalArgumentException(
248                    String.format("index has invalid value: %d", index));
249        }
250
251        return _femaleSocket;
252    }
253
254    @Override
255    public int getChildCount() {
256        return 1;
257    }
258
259    @Override
260    public Category getCategory() {
261        throw new UnsupportedOperationException("Not supported.");
262    }
263
264    public void setSocketSystemName(String systemName) {
265        if ((systemName == null) || (!systemName.equals(_socketSystemName))) {
266            _femaleSocket.disconnect();
267        }
268        _socketSystemName = systemName;
269    }
270
271    public String getSocketSystemName() {
272        return _socketSystemName;
273    }
274
275    /** {@inheritDoc} */
276    @Override
277    final public void setup() {
278        if (!_femaleSocket.isConnected()
279                || !_femaleSocket.getConnectedSocket().getSystemName()
280                        .equals(_socketSystemName)) {
281
282            _femaleSocket.disconnect();
283
284            if (_socketSystemName != null) {
285                try {
286                    MaleSocket maleSocket =
287                            InstanceManager.getDefault(DigitalActionManager.class)
288                                    .getBySystemName(_socketSystemName);
289                    if (maleSocket != null) {
290                        _femaleSocket.connect(maleSocket);
291                        maleSocket.setup();
292                    } else {
293                        log.error("digital action is not found: " + _socketSystemName);
294                    }
295                } catch (SocketAlreadyConnectedException ex) {
296                    // This shouldn't happen and is a runtime error if it does.
297                    throw new RuntimeException("socket is already connected");
298                }
299            }
300        } else {
301            _femaleSocket.setup();
302        }
303    }
304
305    /** {@inheritDoc} */
306    @Override
307    final public void disposeMe() {
308        _femaleSocket.dispose();
309    }
310
311    /** {@inheritDoc} */
312    @Override
313    public void setEnabled(boolean enable) {
314        _enabled = enable;
315        if (isActive()) {
316            LogixNG logixNG = getLogixNG();
317            if ((logixNG != null) && logixNG.isActive()) {
318                registerListeners();
319                execute();
320            }
321        } else {
322            unregisterListeners();
323        }
324    }
325
326    /** {@inheritDoc} */
327    @Override
328    public boolean isEnabled() {
329        return _enabled;
330    }
331
332    /** {@inheritDoc} */
333    @Override
334    public void registerListenersForThisClass() {
335        // Do nothing
336    }
337
338    /** {@inheritDoc} */
339    @Override
340    public void unregisterListenersForThisClass() {
341        // Do nothing
342    }
343
344    @Override
345    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
346        throw new UnsupportedOperationException("Not supported yet.");
347    }
348
349    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class);
350
351}