001package jmri.jmrit.logixng.actions;
002
003import java.util.*;
004
005import javax.annotation.Nonnull;
006
007import jmri.*;
008import jmri.jmrit.logixng.*;
009import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
010import jmri.jmrit.logixng.util.ReferenceUtil;
011import jmri.jmrit.logixng.util.parser.*;
012import jmri.util.TypeConversionUtil;
013
014/**
015 * Executes an action when the expression is True.
016 *
017 * @author Daniel Bergqvist Copyright 2018
018 */
019public class TableForEach extends AbstractDigitalAction
020        implements FemaleSocketListener {
021
022    private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean =
023            new LogixNG_SelectNamedBean<>(
024                    this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class));
025    private NamedBeanAddressing _rowOrColumnAddressing = NamedBeanAddressing.Direct;
026    private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row;
027    private String _rowOrColumnName = "";
028    private String _rowOrColumnReference = "";
029    private String _rowOrColumnLocalVariable = "";
030    private String _rowOrColumnFormula = "";
031    private ExpressionNode _rowOrColumnExpressionNode;
032    private String _variableName = "";
033    private String _socketSystemName;
034    private final FemaleDigitalActionSocket _socket;
035
036    public TableForEach(String sys, String user) {
037        super(sys, user);
038        _socket = InstanceManager.getDefault(DigitalActionManager.class)
039                .createFemaleSocket(this, this, "A1");
040    }
041
042    @Override
043    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
044        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
045        String sysName = systemNames.get(getSystemName());
046        String userName = userNames.get(getSystemName());
047        if (sysName == null) sysName = manager.getAutoSystemName();
048        TableForEach copy = new TableForEach(sysName, userName);
049        copy.setComment(getComment());
050        _selectNamedBean.copy(copy._selectNamedBean);
051        copy.setRowOrColumnAddressing(_rowOrColumnAddressing);
052        copy.setRowOrColumn(_tableRowOrColumn);
053        copy.setRowOrColumnName(_rowOrColumnName);
054        copy.setLocalVariableName(_variableName);
055        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
056    }
057
058    public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() {
059        return _selectNamedBean;
060    }
061
062    /** {@inheritDoc} */
063    @Override
064    public LogixNG_Category getCategory() {
065        return LogixNG_Category.FLOW_CONTROL;
066    }
067
068    private String getNewRowOrColumnName() throws JmriException {
069
070        switch (_rowOrColumnAddressing) {
071            case Direct:
072                return _rowOrColumnName;
073
074            case Reference:
075                return ReferenceUtil.getReference(
076                        getConditionalNG().getSymbolTable(), _rowOrColumnReference);
077
078            case LocalVariable:
079                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
080                return TypeConversionUtil
081                        .convertToString(symbolTable.getValue(_rowOrColumnLocalVariable), false);
082
083            case Formula:
084                return _rowOrColumnExpressionNode != null
085                        ? TypeConversionUtil.convertToString(
086                                _rowOrColumnExpressionNode.calculate(
087                                        getConditionalNG().getSymbolTable()), false)
088                        : null;
089
090            default:
091                throw new IllegalArgumentException("invalid _rowOrColumnAddressing state: " + _rowOrColumnAddressing.name());
092        }
093    }
094
095    /** {@inheritDoc} */
096    @Override
097    public void execute() throws JmriException {
098        Table table = _selectNamedBean.evaluateNamedBean(getConditionalNG());
099
100        if (table == null) {
101            return;
102        }
103
104        String rowOrColumnName = getNewRowOrColumnName();
105
106        if (rowOrColumnName == null) {
107            log.error("rowOrColumnName is null");
108            return;
109        }
110        if (_variableName == null) {
111            log.error("variableName is null");
112            return;
113        }
114        if (!_socket.isConnected()) {
115            log.error("socket is not connected");
116            return;
117        }
118
119        SymbolTable symbolTable = getConditionalNG().getSymbolTable();
120
121        if (_tableRowOrColumn == TableRowOrColumn.Row) {
122            int row = 0;    // Empty row name is header row
123            if (!rowOrColumnName.isEmpty()) {
124                row = table.getRowNumber(rowOrColumnName);
125            }
126            for (int column=1; column <= table.numColumns(); column++) {
127                // If the header is null or empty, treat the row as a comment
128                Object header = table.getCell(0, column);
129                if ((header != null) && (!header.toString().isEmpty())) {
130                    symbolTable.setValue(_variableName, table.getCell(row, column));
131                    try {
132                        _socket.execute();
133                    } catch (BreakException e) {
134                        break;
135                    } catch (ContinueException e) {
136                        // Do nothing, just catch it.
137                    }
138                }
139            }
140        } else {
141            int column = 0;    // Empty column name is header column
142            if (!rowOrColumnName.isEmpty()) {
143                column = table.getColumnNumber(rowOrColumnName);
144            }
145            for (int row=1; row <= table.numRows(); row++) {
146                // If the header is null or empty, treat the row as a comment
147                Object header = table.getCell(row, 0);
148//                System.out.format("Column header: %s%n", header);
149                if ((header != null) && (!header.toString().isEmpty())) {
150                    symbolTable.setValue(_variableName, table.getCell(row, column));
151//                    System.out.format("Variable: %s, value: %s%n", _variableName, table.getCell(row, column));
152                    try {
153                        _socket.execute();
154                    } catch (BreakException e) {
155                        break;
156                    } catch (ContinueException e) {
157                        // Do nothing, just catch it.
158                    }
159                }
160            }
161        }
162    }
163
164    /**
165     * Get tableRowOrColumn.
166     * @return tableRowOrColumn
167     */
168    public TableRowOrColumn getRowOrColumn() {
169        return _tableRowOrColumn;
170    }
171
172    /**
173     * Set tableRowOrColumn.
174     * @param tableRowOrColumn tableRowOrColumn
175     */
176    public void setRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) {
177        _tableRowOrColumn = tableRowOrColumn;
178    }
179
180    public void setRowOrColumnAddressing(NamedBeanAddressing addressing) throws ParserException {
181        _rowOrColumnAddressing = addressing;
182        parseRowOrColumnFormula();
183    }
184
185    public NamedBeanAddressing getRowOrColumnAddressing() {
186        return _rowOrColumnAddressing;
187    }
188
189    /**
190     * Get name of row or column
191     * @return name of row or column
192     */
193    public String getRowOrColumnName() {
194        return _rowOrColumnName;
195    }
196
197    /**
198     * Set name of row or column
199     * @param rowOrColumnName name of row or column
200     */
201    public void setRowOrColumnName(@Nonnull String rowOrColumnName) {
202        if (rowOrColumnName == null) throw new RuntimeException("Daniel");
203        _rowOrColumnName = rowOrColumnName;
204    }
205
206    public void setRowOrColumnReference(@Nonnull String reference) {
207        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
208            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
209        }
210        _rowOrColumnReference = reference;
211    }
212
213    public String getRowOrColumnReference() {
214        return _rowOrColumnReference;
215    }
216
217    public void setRowOrColumnLocalVariable(@Nonnull String localVariable) {
218        _rowOrColumnLocalVariable = localVariable;
219    }
220
221    public String getRowOrColumnLocalVariable() {
222        return _rowOrColumnLocalVariable;
223    }
224
225    public void setRowOrColumnFormula(@Nonnull String formula) throws ParserException {
226        _rowOrColumnFormula = formula;
227        parseRowOrColumnFormula();
228    }
229
230    public String getRowOrColumnFormula() {
231        return _rowOrColumnFormula;
232    }
233
234    private void parseRowOrColumnFormula() throws ParserException {
235        if (_rowOrColumnAddressing == NamedBeanAddressing.Formula) {
236            Map<String, Variable> variables = new HashMap<>();
237
238            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
239            _rowOrColumnExpressionNode = parser.parseExpression(_rowOrColumnFormula);
240        } else {
241            _rowOrColumnExpressionNode = null;
242        }
243    }
244
245    /**
246     * Get name of local variable
247     * @return name of local variable
248     */
249    public String getLocalVariableName() {
250        return _variableName;
251    }
252
253    /**
254     * Set name of local variable
255     * @param localVariableName name of local variable
256     */
257    public void setLocalVariableName(String localVariableName) {
258        _variableName = localVariableName;
259    }
260
261    @Override
262    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
263        switch (index) {
264            case 0:
265                return _socket;
266
267            default:
268                throw new IllegalArgumentException(
269                        String.format("index has invalid value: %d", index));
270        }
271    }
272
273    @Override
274    public int getChildCount() {
275        return 1;
276    }
277
278    @Override
279    public void connected(FemaleSocket socket) {
280        if (socket == _socket) {
281            _socketSystemName = socket.getConnectedSocket().getSystemName();
282        } else {
283            throw new IllegalArgumentException("unkown socket");
284        }
285    }
286
287    @Override
288    public void disconnected(FemaleSocket socket) {
289        if (socket == _socket) {
290            _socketSystemName = null;
291        } else {
292            throw new IllegalArgumentException("unkown socket");
293        }
294    }
295
296    @Override
297    public String getShortDescription(Locale locale) {
298        return Bundle.getMessage(locale, "TableForEach_Short");
299    }
300
301    @Override
302    public String getLongDescription(Locale locale) {
303        String namedBean = _selectNamedBean.getDescription(locale);
304        String rowOrColumnName;
305
306        switch (_rowOrColumnAddressing) {
307            case Direct:
308                String name = _rowOrColumnName;
309                if (name.isEmpty()) name = Bundle.getMessage("TableForEach_Header");
310                rowOrColumnName = Bundle.getMessage(locale, "AddressByDirect", name);
311                break;
312
313            case Reference:
314                rowOrColumnName = Bundle.getMessage(locale, "AddressByReference", _rowOrColumnReference);
315                break;
316
317            case LocalVariable:
318                rowOrColumnName = Bundle.getMessage(locale, "AddressByLocalVariable", _rowOrColumnLocalVariable);
319                break;
320
321            case Formula:
322                rowOrColumnName = Bundle.getMessage(locale, "AddressByFormula", _rowOrColumnFormula);
323                break;
324
325            default:
326                throw new IllegalArgumentException("invalid _rowOrColumnAddressing state: " + _rowOrColumnAddressing.name());
327        }
328
329        return Bundle.getMessage(locale, "TableForEach_Long",
330                _tableRowOrColumn.getOpposite().toStringLowerCase(),
331                _tableRowOrColumn.toStringLowerCase(),
332                rowOrColumnName,
333                namedBean,
334                _variableName,
335                _socket.getName());
336    }
337
338    public FemaleDigitalActionSocket getSocket() {
339        return _socket;
340    }
341
342    public String getSocketSystemName() {
343        return _socketSystemName;
344    }
345
346    public void setSocketSystemName(String systemName) {
347        _socketSystemName = systemName;
348    }
349
350    /** {@inheritDoc} */
351    @Override
352    public void setup() {
353        try {
354            if ( !_socket.isConnected()
355                    || !_socket.getConnectedSocket().getSystemName()
356                            .equals(_socketSystemName)) {
357
358                String socketSystemName = _socketSystemName;
359                _socket.disconnect();
360                if (socketSystemName != null) {
361                    MaleSocket maleSocket =
362                            InstanceManager.getDefault(DigitalActionManager.class)
363                                    .getBySystemName(socketSystemName);
364                    _socket.disconnect();
365                    if (maleSocket != null) {
366                        _socket.connect(maleSocket);
367                        maleSocket.setup();
368                    } else {
369                        log.error("cannot load digital action {}", socketSystemName);
370                    }
371                }
372            } else {
373                _socket.getConnectedSocket().setup();
374            }
375        } catch (SocketAlreadyConnectedException ex) {
376            // This shouldn't happen and is a runtime error if it does.
377            throw new RuntimeException("socket is already connected");
378        }
379    }
380
381    /** {@inheritDoc} */
382    @Override
383    public void disposeMe() {
384    }
385
386    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TableForEach.class);
387
388}