001package jmri.jmrit.logixng.actions;
002
003import java.util.*;
004
005import jmri.*;
006import jmri.implementation.AbstractShutDownTask;
007import jmri.jmrit.logixng.*;
008import jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket;
009import jmri.jmrit.logixng.implementation.DefaultSymbolTable;
010
011/**
012 * Executes a digital action delayed.
013 *
014 * @author Daniel Bergqvist Copyright 2022
015 */
016public class ActionShutDownTask
017        extends AbstractDigitalAction
018        implements FemaleSocketListener {
019
020    private String _callSocketSystemName;
021    private String _runSocketSystemName;
022    private final FemaleDigitalExpressionSocket _callSocket;
023    private final FemaleDigitalActionSocket _runSocket;
024    private final Object _lock = new Object();
025
026
027    public ActionShutDownTask(String sys, String user) {
028        super(sys, user);
029        _callSocket = InstanceManager.getDefault(DigitalExpressionManager.class)
030                .createFemaleSocket(this, this, "E");
031        _runSocket = InstanceManager.getDefault(DigitalActionManager.class)
032                .createFemaleSocket(this, this, "A");
033    }
034
035    @Override
036    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
037        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
038        String sysName = systemNames.get(getSystemName());
039        String userName = userNames.get(getSystemName());
040        if (sysName == null) sysName = manager.getAutoSystemName();
041        ActionShutDownTask copy = new ActionShutDownTask(sysName, userName);
042        copy.setComment(getComment());
043        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
044    }
045
046    /** {@inheritDoc} */
047    @Override
048    public Category getCategory() {
049        return Category.OTHER;
050    }
051
052    /** {@inheritDoc} */
053    @Override
054    public void execute() throws JmriException {
055        ConditionalNG conditionalNG = getConditionalNG();
056        DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG.getSymbolTable());
057
058        ShutDownTask shutDownTask = new AbstractShutDownTask("LogixNG action ShutDownTask") {
059            @Override
060            public Boolean call() {
061                if (_callSocket.isConnected()) {
062                    InternalCallSocket internalSocket
063                            = new InternalCallSocket();
064                    internalSocket.conditionalNG = conditionalNG;
065                    internalSocket.newSymbolTable = newSymbolTable;
066
067                    try {
068                        synchronized(_lock) {
069                            conditionalNG.execute(internalSocket);
070                            while (!internalSocket._completed) _lock.wait();
071                            return internalSocket._result;
072                        }
073                    } catch (InterruptedException e) {
074                        log.error("Interrupted exception: {}", e, e);
075                        return true;
076                    }
077                } else {
078                    return true;
079                }
080            }
081
082            @Override
083            public void runEarly() {
084                if (_runSocket.isConnected()) {
085                    InternalRunSocket internalSocket
086                            = new InternalRunSocket();
087                    internalSocket.conditionalNG = conditionalNG;
088                    internalSocket.newSymbolTable = newSymbolTable;
089
090                    try {
091                        synchronized(_lock) {
092                            conditionalNG.execute(internalSocket);
093                            while (!internalSocket._completed) _lock.wait();
094                        }
095                    } catch (InterruptedException e) {
096                        log.error("Interrupted exception: {}", e, e);
097                    }
098                }
099            }
100
101            @Override
102            public void run() {
103                // Do nothing
104            }
105        };
106        InstanceManager.getDefault(ShutDownManager.class).register(shutDownTask);
107    }
108
109    @Override
110    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
111        switch (index) {
112            case 0:
113                return _callSocket;
114
115            case 1:
116                return _runSocket;
117
118            default:
119                throw new IllegalArgumentException(
120                        String.format("index has invalid value: %d", index));
121        }
122    }
123
124    @Override
125    public int getChildCount() {
126        return 2;
127    }
128
129    @Override
130    public void connected(FemaleSocket socket) {
131        if (socket == _callSocket) {
132            _callSocketSystemName = socket.getConnectedSocket().getSystemName();
133        } else if (socket == _runSocket) {
134            _runSocketSystemName = socket.getConnectedSocket().getSystemName();
135        } else {
136            throw new IllegalArgumentException("unkown socket");
137        }
138    }
139
140    @Override
141    public void disconnected(FemaleSocket socket) {
142        if (socket == _callSocket) {
143            _callSocketSystemName = null;
144        } else if (socket == _runSocket) {
145            _runSocketSystemName = null;
146        } else {
147            throw new IllegalArgumentException("unkown socket");
148        }
149    }
150
151    @Override
152    public String getShortDescription(Locale locale) {
153        return Bundle.getMessage(locale, "ActionShutDownTask_Short");
154    }
155
156    @Override
157    public String getLongDescription(Locale locale) {
158        return Bundle.getMessage(locale, "ActionShutDownTask_Long", _callSocket.getName(), _runSocket.getName());
159    }
160
161    public FemaleDigitalExpressionSocket getCallSocket() {
162        return _callSocket;
163    }
164
165    public String getCallSocketSystemName() {
166        return _callSocketSystemName;
167    }
168
169    public void setCallSocketSystemName(String systemName) {
170        _callSocketSystemName = systemName;
171    }
172
173    public FemaleDigitalActionSocket getRunSocket() {
174        return _runSocket;
175    }
176
177    public String getRunSocketSystemName() {
178        return _runSocketSystemName;
179    }
180
181    public void setRunSocketSystemName(String systemName) {
182        _runSocketSystemName = systemName;
183    }
184
185    /** {@inheritDoc} */
186    @Override
187    public void setup() {
188        try {
189            if (!_callSocket.isConnected()
190                    || !_callSocket.getConnectedSocket().getSystemName()
191                            .equals(_callSocketSystemName)) {
192
193                String socketSystemName = _callSocketSystemName;
194
195                _callSocket.disconnect();
196
197                if (socketSystemName != null) {
198                    MaleSocket maleSocket =
199                            InstanceManager.getDefault(DigitalExpressionManager.class)
200                                    .getBySystemName(socketSystemName);
201                    if (maleSocket != null) {
202                        _callSocket.connect(maleSocket);
203                        maleSocket.setup();
204                    } else {
205                        log.error("cannot load digital expression {}", socketSystemName);
206                    }
207                }
208            } else {
209                _callSocket.getConnectedSocket().setup();
210            }
211
212            if (!_runSocket.isConnected()
213                    || !_runSocket.getConnectedSocket().getSystemName()
214                            .equals(_runSocketSystemName)) {
215
216                String socketSystemName = _runSocketSystemName;
217
218                _runSocket.disconnect();
219
220                if (socketSystemName != null) {
221                    MaleSocket maleSocket =
222                            InstanceManager.getDefault(DigitalActionManager.class)
223                                    .getBySystemName(socketSystemName);
224                    if (maleSocket != null) {
225                        _runSocket.connect(maleSocket);
226                        maleSocket.setup();
227                    } else {
228                        log.error("cannot load digital action {}", socketSystemName);
229                    }
230                }
231            } else {
232                _runSocket.getConnectedSocket().setup();
233            }
234        } catch (SocketAlreadyConnectedException ex) {
235            // This shouldn't happen and is a runtime error if it does.
236            throw new RuntimeException("socket is already connected");
237        }
238    }
239
240
241
242    private class InternalCallSocket
243            extends DefaultFemaleDigitalActionSocket {
244
245        private ConditionalNG conditionalNG;
246        private SymbolTable newSymbolTable;
247        private boolean _completed = false;
248        private boolean _result = true;     // If no connected socket, return true
249
250        public InternalCallSocket() {
251            super(null, new FemaleSocketListener(){
252                @Override
253                public void connected(FemaleSocket socket) {
254                    // Do nothing
255                }
256
257                @Override
258                public void disconnected(FemaleSocket socket) {
259                    // Do nothing
260                }
261            }, "E");
262        }
263
264        @Override
265        public void execute() throws JmriException {
266            if (conditionalNG == null) { throw new NullPointerException("conditionalNG is null"); }
267            if (_callSocket != null) {
268                SymbolTable oldSymbolTable = conditionalNG.getSymbolTable();
269                conditionalNG.setSymbolTable(newSymbolTable);
270                _result = _callSocket.evaluate();
271                conditionalNG.setSymbolTable(oldSymbolTable);
272                synchronized(_lock) {
273                    _completed = true;
274                    _lock.notifyAll();
275                }
276            }
277        }
278
279    }
280
281
282
283    private class InternalRunSocket
284            extends DefaultFemaleDigitalActionSocket {
285
286        private ConditionalNG conditionalNG;
287        private SymbolTable newSymbolTable;
288        private boolean _completed = false;
289
290        public InternalRunSocket() {
291            super(null, new FemaleSocketListener(){
292                @Override
293                public void connected(FemaleSocket socket) {
294                    // Do nothing
295                }
296
297                @Override
298                public void disconnected(FemaleSocket socket) {
299                    // Do nothing
300                }
301            }, "A");
302        }
303
304        @Override
305        public void execute() throws JmriException {
306            if (conditionalNG == null) { throw new NullPointerException("conditionalNG is null"); }
307            if (_runSocket != null) {
308                SymbolTable oldSymbolTable = conditionalNG.getSymbolTable();
309                conditionalNG.setSymbolTable(newSymbolTable);
310                _runSocket.execute();
311                conditionalNG.setSymbolTable(oldSymbolTable);
312                synchronized(_lock) {
313                    _completed = true;
314                    _lock.notifyAll();
315                }
316            }
317        }
318
319    }
320
321
322    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionShutDownTask.class);
323
324}