001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
011import jmri.jmrit.logixng.util.parser.ParserException;
012
013/**
014 * This action listens on some beans and runs the ConditionalNG on property change.
015 *
016 * @author Daniel Bergqvist Copyright 2019
017 */
018public class ActionListenOnBeansTable extends AbstractDigitalAction
019        implements PropertyChangeListener, VetoableChangeListener {
020
021    private NamedBeanType _namedBeanType = NamedBeanType.Light;
022    private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean =
023            new LogixNG_SelectNamedBean<>(
024                    this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class), this);
025    private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row;
026    private String _rowOrColumnName = "";
027    private boolean _includeCellsWithoutHeader = false;
028    private boolean _listenOnAllProperties = false;
029    private final List<Map.Entry<NamedBean, String>> _namedBeansEntries = new ArrayList<>();
030    private String _localVariableNamedBean;
031    private String _localVariableEvent;
032    private String _localVariableNewValue;
033    private String _lastNamedBean;
034    private String _lastEvent;
035    private String _lastNewValue;
036
037    public ActionListenOnBeansTable(String sys, String user)
038            throws BadUserNameException, BadSystemNameException {
039        super(sys, user);
040        _selectNamedBean.setOnlyDirectAddressingAllowed();
041    }
042
043    @Override
044    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
045        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
046        String sysName = systemNames.get(getSystemName());
047        String userName = userNames.get(getSystemName());
048        if (sysName == null) sysName = manager.getAutoSystemName();
049        ActionListenOnBeansTable copy = new ActionListenOnBeansTable(sysName, userName);
050        copy.setComment(getComment());
051        copy.setNamedBeanType(_namedBeanType);
052        _selectNamedBean.copy(copy._selectNamedBean);
053        copy.setTableRowOrColumn(_tableRowOrColumn);
054        copy.setRowOrColumnName(_rowOrColumnName);
055        copy.setIncludeCellsWithoutHeader(_includeCellsWithoutHeader);
056
057        copy.setLocalVariableNamedBean(_localVariableNamedBean);
058        copy.setLocalVariableEvent(_localVariableEvent);
059        copy.setLocalVariableNewValue(_localVariableNewValue);
060
061        for (var entry : _namedBeansEntries) {
062            copy._namedBeansEntries.add(
063                    new HashMap.SimpleEntry<>(entry.getKey(), entry.getValue()));
064        }
065
066        return manager.registerAction(copy);
067    }
068
069    /**
070     * Get the type of the named beans
071     * @return the type of named beans
072     */
073    public NamedBeanType getNamedBeanType() {
074        return _namedBeanType;
075    }
076
077    /**
078     * Set the type of the named beans
079     * @param namedBeanType the type of the named beans
080     */
081    public void setNamedBeanType(@Nonnull NamedBeanType namedBeanType) {
082        if (namedBeanType == null) throw new RuntimeException("Daniel");
083        _namedBeanType = namedBeanType;
084    }
085
086    public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() {
087        return _selectNamedBean;
088    }
089
090    /**
091     * Get tableRowOrColumn.
092     * @return tableRowOrColumn
093     */
094    public TableRowOrColumn getTableRowOrColumn() {
095        return _tableRowOrColumn;
096    }
097
098    /**
099     * Set tableRowOrColumn.
100     * @param tableRowOrColumn tableRowOrColumn
101     */
102    public void setTableRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) {
103        _tableRowOrColumn = tableRowOrColumn;
104    }
105
106    /**
107     * Get name of row or column
108     * @return name of row or column
109     */
110    public String getRowOrColumnName() {
111        return _rowOrColumnName;
112    }
113
114    /**
115     * Set name of row or column
116     * @param rowOrColumnName name of row or column
117     */
118    public void setRowOrColumnName(@Nonnull String rowOrColumnName) {
119        if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null");
120        _rowOrColumnName = rowOrColumnName;
121    }
122
123    public boolean getListenOnAllProperties() {
124        return _listenOnAllProperties;
125    }
126
127    public void setListenOnAllProperties(boolean listenOnAllProperties) {
128        _listenOnAllProperties = listenOnAllProperties;
129    }
130
131    /**
132     * Set whenever to include cells that doesn't have a header.
133     * Cells without headers can be used to use some cells in the table
134     * as comments.
135     * @return true if include cells that doesn't have a header, false otherwise
136     */
137    public boolean getIncludeCellsWithoutHeader() {
138        return _includeCellsWithoutHeader;
139    }
140
141    /**
142     * Set whenever to include cells that doesn't have a header.
143     * Cells without headers can be used to use some cells in the table
144     * as comments.
145     * @param includeCellsWithoutHeader true if include rows/columns that
146     *                                  doesn't have a header, false otherwise
147     */
148    public void setIncludeCellsWithoutHeader(boolean includeCellsWithoutHeader) {
149        _includeCellsWithoutHeader = includeCellsWithoutHeader;
150    }
151
152    public void setLocalVariableNamedBean(String localVariableNamedBean) {
153        if ((localVariableNamedBean != null) && (!localVariableNamedBean.isEmpty())) {
154            this._localVariableNamedBean = localVariableNamedBean;
155        } else {
156            this._localVariableNamedBean = null;
157        }
158    }
159
160    public String getLocalVariableNamedBean() {
161        return _localVariableNamedBean;
162    }
163
164    public void setLocalVariableEvent(String localVariableEvent) {
165        if ((localVariableEvent != null) && (!localVariableEvent.isEmpty())) {
166            this._localVariableEvent = localVariableEvent;
167        } else {
168            this._localVariableEvent = null;
169        }
170    }
171
172    public String getLocalVariableEvent() {
173        return _localVariableEvent;
174    }
175
176    public void setLocalVariableNewValue(String localVariableNewValue) {
177        if ((localVariableNewValue != null) && (!localVariableNewValue.isEmpty())) {
178            this._localVariableNewValue = localVariableNewValue;
179        } else {
180            this._localVariableNewValue = null;
181        }
182    }
183
184    public String getLocalVariableNewValue() {
185        return _localVariableNewValue;
186    }
187
188    /** {@inheritDoc} */
189    @Override
190    public Category getCategory() {
191        return Category.OTHER;
192    }
193
194    /** {@inheritDoc} */
195    @Override
196    public void execute() {
197        // The purpose of this action is only to listen on property changes
198        // of the registered beans and execute the ConditionalNG when it
199        // happens.
200
201        synchronized(this) {
202            SymbolTable symbolTable = getConditionalNG().getSymbolTable();
203            if (_localVariableNamedBean != null) {
204                symbolTable.setValue(_localVariableNamedBean, _lastNamedBean);
205            }
206            if (_localVariableEvent != null) {
207                symbolTable.setValue(_localVariableEvent, _lastEvent);
208            }
209            if (_localVariableNewValue != null) {
210                symbolTable.setValue(_localVariableNewValue, _lastNewValue);
211            }
212            _lastNamedBean = null;
213            _lastEvent = null;
214            _lastNewValue = null;
215        }
216    }
217
218    @Override
219    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
220        throw new UnsupportedOperationException("Not supported.");
221    }
222
223    @Override
224    public int getChildCount() {
225        return 0;
226    }
227
228    @Override
229    public String getShortDescription(Locale locale) {
230        return Bundle.getMessage(locale, "ActionListenOnBeansTable_Short");
231    }
232
233    @Override
234    public String getLongDescription(Locale locale) {
235        String tableName = _selectNamedBean.getDescription(locale);
236        return Bundle.getMessage(locale, "ActionListenOnBeansTable_Long",
237                _namedBeanType.toString(),
238                _tableRowOrColumn.getOpposite().toStringLowerCase(),
239                _tableRowOrColumn.toStringLowerCase(),
240                _rowOrColumnName,
241                tableName);
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public void setup() {
247        // Do nothing
248    }
249
250    public List<String> getItems() {
251        List<String> items = new ArrayList<>();
252
253        if (_selectNamedBean.getNamedBean() == null) {
254            log.error("No table name is given");
255            return items;   // The list is empty
256        }
257        if (_rowOrColumnName.isEmpty()) {
258            log.error("rowOrColumnName is empty string");
259            return items;   // The list is empty
260        }
261
262        NamedTable table = _selectNamedBean.getNamedBean().getBean();
263
264        if (_tableRowOrColumn == TableRowOrColumn.Row) {
265            int row = table.getRowNumber(_rowOrColumnName);
266            for (int column=1; column <= table.numColumns(); column++) {
267                // If the header is null or empty, treat the row as a comment
268                // unless _includeRowColumnWithoutHeader is true
269                Object header = table.getCell(0, column);
270//                System.out.format("Row header: %s%n", header);
271                if (_includeCellsWithoutHeader
272                        || ((header != null) && (!header.toString().isEmpty()))) {
273                    Object cell = table.getCell(row, column);
274                    if (cell != null) items.add(cell.toString());
275                }
276            }
277        } else {
278            int column = table.getColumnNumber(_rowOrColumnName);
279            for (int row=1; row <= table.numRows(); row++) {
280                // If the header is null or empty, treat the row as a comment
281                // unless _includeRowColumnWithoutHeader is true
282                Object header = table.getCell(row, 0);
283//                System.out.format("Column header: %s%n", header);
284                if (_includeCellsWithoutHeader
285                        || ((header != null) && (!header.toString().isEmpty()))) {
286                    Object cell = table.getCell(row, column);
287                    if (cell != null && !cell.toString().isEmpty()) items.add(cell.toString());
288                }
289            }
290        }
291        return items;
292    }
293
294    /** {@inheritDoc} */
295    @Override
296    public void registerListenersForThisClass() {
297        if (_listenersAreRegistered) return;
298
299        List<String> items = getItems();
300
301        for (String item : items) {
302            NamedBean namedBean = _namedBeanType.getManager().getNamedBean(item);
303
304            if (namedBean != null) {
305                Map.Entry<NamedBean, String> namedBeanEntry =
306                        new HashMap.SimpleEntry<>(namedBean, _namedBeanType.getPropertyName());
307
308                _namedBeansEntries.add(namedBeanEntry);
309                if (!_listenOnAllProperties
310                        && (_namedBeanType.getPropertyName() != null)) {
311                    namedBean.addPropertyChangeListener(_namedBeanType.getPropertyName(), this);
312                } else {
313                    namedBean.addPropertyChangeListener(this);
314                }
315            } else {
316                log.warn("The named bean \"{}\" cannot be found in the manager for {}", item, _namedBeanType.toString());
317            }
318        }
319        _selectNamedBean.registerListeners();
320        _listenersAreRegistered = true;
321    }
322
323    /** {@inheritDoc} */
324    @Override
325    public void unregisterListenersForThisClass() {
326        if (!_listenersAreRegistered) return;
327
328        for (Map.Entry<NamedBean, String> namedBeanEntry : _namedBeansEntries) {
329            if (!_listenOnAllProperties
330                    && (namedBeanEntry.getValue() != null)) {
331                namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this);
332            } else {
333                namedBeanEntry.getKey().removePropertyChangeListener(this);
334            }
335            namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this);
336        }
337        _selectNamedBean.unregisterListeners();
338        _listenersAreRegistered = false;
339    }
340
341    /** {@inheritDoc} */
342    @Override
343    public void propertyChange(PropertyChangeEvent evt) {
344//        System.out.format("Table: Property: %s, Bean: %s, Listen: %b%n", evt.getPropertyName(), ((NamedBean)evt.getSource()).getDisplayName(), _listenOnAllProperties);
345        synchronized(this) {
346            _lastNamedBean = ((NamedBean)evt.getSource()).getDisplayName();
347            _lastEvent = evt.getPropertyName();
348            _lastNewValue = evt.getNewValue() != null ? evt.getNewValue().toString() : null;
349        }
350        getConditionalNG().execute();
351    }
352
353    /** {@inheritDoc} */
354    @Override
355    public void disposeMe() {
356    }
357
358
359    /** {@inheritDoc} */
360    @Override
361    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
362/*
363        log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report);
364        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
365            if (namedBeanReference._handle != null) {
366                if (bean.equals(namedBeanReference._handle.getBean())) {
367                    report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
368                }
369            }
370        }
371*/
372    }
373
374    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeansTable.class);
375
376}