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