001package jmri.jmrit.logixng.expressions;
002
003import java.util.List;
004import java.util.ArrayList;
005import java.util.Locale;
006import java.util.Map;
007import jmri.InstanceManager;
008import jmri.JmriException;
009import jmri.jmrit.logixng.*;
010
011/**
012 * Evaluates to True if any of the children expressions evaluate to true.
013 *
014 * @author Daniel Bergqvist Copyright 2018
015 */
016public class Or extends AbstractDigitalExpression implements FemaleSocketListener {
017
018    private Type _type = Type.EvaluateAll;
019    private final List<ExpressionEntry> _expressionEntries = new ArrayList<>();
020    private boolean disableCheckForUnconnectedSocket = false;
021
022    public Or(String sys, String user)
023            throws BadUserNameException, BadSystemNameException {
024        super(sys, user);
025        _expressionEntries
026                .add(new ExpressionEntry(InstanceManager.getDefault(DigitalExpressionManager.class)
027                        .createFemaleSocket(this, this, getNewSocketName())));
028    }
029
030    public Or(String sys, String user, List<Map.Entry<String, String>> expressionSystemNames)
031            throws BadUserNameException, BadSystemNameException {
032        super(sys, user);
033        setExpressionSystemNames(expressionSystemNames);
034    }
035
036    @Override
037    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
038        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
039        String sysName = systemNames.get(getSystemName());
040        String userName = userNames.get(getSystemName());
041        if (sysName == null) sysName = manager.getAutoSystemName();
042        Or copy = new Or(sysName, userName);
043        copy.setComment(getComment());
044        copy.setType(_type);
045        copy.setNumSockets(getChildCount());
046        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
047    }
048
049    /**
050     * Get the type.
051     * @return the type
052     */
053    public Type getType() {
054        return _type;
055    }
056
057    /**
058     * Set the type.
059     * @param type the type
060     */
061    public void setType(Type type) {
062        _type = type;
063    }
064
065    private void setExpressionSystemNames(List<Map.Entry<String, String>> systemNames) {
066        if (!_expressionEntries.isEmpty()) {
067            throw new RuntimeException("expression system names cannot be set more than once");
068        }
069
070        for (Map.Entry<String, String> entry : systemNames) {
071            FemaleDigitalExpressionSocket socket =
072                    InstanceManager.getDefault(DigitalExpressionManager.class)
073                            .createFemaleSocket(this, this, entry.getKey());
074
075            _expressionEntries.add(new ExpressionEntry(socket, entry.getValue()));
076        }
077    }
078
079    public String getExpressionSystemName(int index) {
080        return _expressionEntries.get(index)._socketSystemName;
081    }
082
083    /** {@inheritDoc} */
084    @Override
085    public Category getCategory() {
086        return Category.COMMON;
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    public boolean evaluate() throws JmriException {
092        boolean result = false;
093        for (ExpressionEntry e : _expressionEntries) {
094            if (e._socket.isConnected() && e._socket.evaluate()) {
095                if (_type == Type.EvaluateNeeded) return true;
096                result = true;
097            }
098        }
099        return result;
100    }
101
102    @Override
103    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
104        return _expressionEntries.get(index)._socket;
105    }
106
107    @Override
108    public int getChildCount() {
109        return _expressionEntries.size();
110    }
111
112    @Override
113    public String getShortDescription(Locale locale) {
114        return Bundle.getMessage(locale, "Or_Short");
115    }
116
117    @Override
118    public String getLongDescription(Locale locale) {
119        return Bundle.getMessage(locale, "Or_Long", _type.toString());
120    }
121
122    // This method ensures that we have enough of children
123    private void setNumSockets(int num) {
124        List<FemaleSocket> addList = new ArrayList<>();
125
126        // Is there not enough children?
127        while (_expressionEntries.size() < num) {
128            FemaleDigitalExpressionSocket socket =
129                    InstanceManager.getDefault(DigitalExpressionManager.class)
130                            .createFemaleSocket(this, this, getNewSocketName());
131            _expressionEntries.add(new ExpressionEntry(socket));
132            addList.add(socket);
133        }
134        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
135    }
136
137    private void checkFreeSocket() {
138        boolean hasFreeSocket = false;
139
140        for (ExpressionEntry entry : _expressionEntries) {
141            hasFreeSocket |= !entry._socket.isConnected();
142        }
143        if (!hasFreeSocket) {
144            FemaleDigitalExpressionSocket socket =
145                    InstanceManager.getDefault(DigitalExpressionManager.class)
146                            .createFemaleSocket(this, this, getNewSocketName());
147            _expressionEntries.add(new ExpressionEntry(socket));
148
149            List<FemaleSocket> list = new ArrayList<>();
150            list.add(socket);
151            firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, list);
152        }
153    }
154
155    /** {@inheritDoc} */
156    @Override
157    public boolean isSocketOperationAllowed(int index, FemaleSocketOperation oper) {
158        switch (oper) {
159            case Remove:        // Possible if socket is not connected
160                return ! getChild(index).isConnected();
161            case InsertBefore:
162                return true;    // Always possible
163            case InsertAfter:
164                return true;    // Always possible
165            case MoveUp:
166                return index > 0;   // Possible if not first socket
167            case MoveDown:
168                return index+1 < getChildCount();   // Possible if not last socket
169            default:
170                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
171        }
172    }
173
174    private void insertNewSocket(int index) {
175        FemaleDigitalExpressionSocket socket =
176                InstanceManager.getDefault(DigitalExpressionManager.class)
177                        .createFemaleSocket(this, this, getNewSocketName());
178        _expressionEntries.add(index, new ExpressionEntry(socket));
179
180        List<FemaleSocket> addList = new ArrayList<>();
181        addList.add(socket);
182        firePropertyChange(Base.PROPERTY_CHILD_COUNT, null, addList);
183    }
184
185    private void removeSocket(int index) {
186        List<FemaleSocket> removeList = new ArrayList<>();
187        removeList.add(_expressionEntries.remove(index)._socket);
188        firePropertyChange(Base.PROPERTY_CHILD_COUNT, removeList, null);
189    }
190
191    private void moveSocketDown(int index) {
192        ExpressionEntry temp = _expressionEntries.get(index);
193        _expressionEntries.set(index, _expressionEntries.get(index+1));
194        _expressionEntries.set(index+1, temp);
195
196        List<FemaleSocket> list = new ArrayList<>();
197        list.add(_expressionEntries.get(index)._socket);
198        list.add(_expressionEntries.get(index)._socket);
199        firePropertyChange(Base.PROPERTY_CHILD_REORDER, null, list);
200    }
201
202    /** {@inheritDoc} */
203    @Override
204    public void doSocketOperation(int index, FemaleSocketOperation oper) {
205        switch (oper) {
206            case Remove:
207                if (getChild(index).isConnected()) throw new UnsupportedOperationException("Socket is connected");
208                removeSocket(index);
209                break;
210            case InsertBefore:
211                insertNewSocket(index);
212                break;
213            case InsertAfter:
214                insertNewSocket(index+1);
215                break;
216            case MoveUp:
217                if (index == 0) throw new UnsupportedOperationException("cannot move up first child");
218                moveSocketDown(index-1);
219                break;
220            case MoveDown:
221                if (index+1 == getChildCount()) throw new UnsupportedOperationException("cannot move down last child");
222                moveSocketDown(index);
223                break;
224            default:
225                throw new UnsupportedOperationException("Oper is unknown" + oper.name());
226        }
227    }
228
229    @Override
230    public void connected(FemaleSocket socket) {
231        if (disableCheckForUnconnectedSocket) return;
232
233        for (ExpressionEntry entry : _expressionEntries) {
234            if (socket == entry._socket) {
235                entry._socketSystemName =
236                        socket.getConnectedSocket().getSystemName();
237            }
238        }
239
240        checkFreeSocket();
241    }
242
243    @Override
244    public void disconnected(FemaleSocket socket) {
245        for (ExpressionEntry entry : _expressionEntries) {
246            if (socket == entry._socket) {
247                entry._socketSystemName = null;
248                break;
249            }
250        }
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public void setup() {
256        // We don't want to check for unconnected sockets while setup sockets
257        disableCheckForUnconnectedSocket = true;
258
259        for (ExpressionEntry ee : _expressionEntries) {
260            try {
261                if ( !ee._socket.isConnected()
262                        || !ee._socket.getConnectedSocket().getSystemName()
263                                .equals(ee._socketSystemName)) {
264
265                    String socketSystemName = ee._socketSystemName;
266                    ee._socket.disconnect();
267                    if (socketSystemName != null) {
268                        MaleSocket maleSocket =
269                                InstanceManager.getDefault(DigitalExpressionManager.class)
270                                        .getBySystemName(socketSystemName);
271                        if (maleSocket != null) {
272                            ee._socket.connect(maleSocket);
273                            maleSocket.setup();
274                        } else {
275                            log.error("cannot load digital expression {}", socketSystemName);
276                        }
277                    }
278                } else {
279                    ee._socket.getConnectedSocket().setup();
280                }
281            } catch (SocketAlreadyConnectedException ex) {
282                // This shouldn't happen and is a runtime error if it does.
283                throw new RuntimeException("socket is already connected");
284            }
285        }
286
287        checkFreeSocket();
288
289        disableCheckForUnconnectedSocket = false;
290    }
291
292
293    /* This class is public since ExpressionOrXml needs to access it. */
294    private static class ExpressionEntry {
295        private String _socketSystemName;
296        private final FemaleDigitalExpressionSocket _socket;
297
298        private ExpressionEntry(FemaleDigitalExpressionSocket socket, String socketSystemName) {
299            _socketSystemName = socketSystemName;
300            _socket = socket;
301        }
302
303        private ExpressionEntry(FemaleDigitalExpressionSocket socket) {
304            this._socket = socket;
305        }
306    }
307
308    /** {@inheritDoc} */
309    @Override
310    public void registerListenersForThisClass() {
311        // Do nothing
312    }
313
314    /** {@inheritDoc} */
315    @Override
316    public void unregisterListenersForThisClass() {
317        // Do nothing
318    }
319
320    /** {@inheritDoc} */
321    @Override
322    public void disposeMe() {
323    }
324
325
326    public enum Type {
327        /**
328         * All the connected sockets are evaluated.
329         */
330        EvaluateAll(Bundle.getMessage("Or_EvaluateAll")),
331
332        /**
333         * Evaluation starts with the first socket and continues until
334         * all sockets are evaluated or the result is known.
335         * For the Or expression, it means that the evaluation stops
336         * as soon as a connected socket evaluates to true.
337         */
338        EvaluateNeeded(Bundle.getMessage("Or_EvaluateNeeded"));
339
340        private final String _text;
341
342        private Type(String text) {
343            this._text = text;
344        }
345
346        @Override
347        public String toString() {
348            return _text;
349        }
350
351    }
352
353
354    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Or.class);
355
356}