001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010
011/**
012 * This action listens on some beans and runs the ConditionalNG on property change.
013 *
014 * @author Daniel Bergqvist Copyright 2019
015 */
016public class ActionListenOnBeansTable extends AbstractDigitalAction
017        implements PropertyChangeListener, VetoableChangeListener {
018
019    private NamedBeanType _namedBeanType = NamedBeanType.Light;
020    private NamedBeanHandle<NamedTable> _tableHandle;
021    private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row;
022    private String _rowOrColumnName = "";
023    private boolean _includeCellsWithoutHeader = false;
024    private final List<Map.Entry<NamedBean, String>> _namedBeansEntries = new ArrayList<>();
025
026    public ActionListenOnBeansTable(String sys, String user)
027            throws BadUserNameException, BadSystemNameException {
028        super(sys, user);
029    }
030
031    @Override
032    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
033        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
034        String sysName = systemNames.get(getSystemName());
035        String userName = userNames.get(getSystemName());
036        if (sysName == null) sysName = manager.getAutoSystemName();
037        ActionListenOnBeansTable copy = new ActionListenOnBeansTable(sysName, userName);
038        copy.setComment(getComment());
039        copy.setNamedBeanType(_namedBeanType);
040        copy.setTable(_tableHandle);
041        copy.setTableRowOrColumn(_tableRowOrColumn);
042        copy.setIncludeCellsWithoutHeader(_includeCellsWithoutHeader);
043        return manager.registerAction(copy);
044    }
045
046    /**
047     * Get the type of the named beans
048     * @return the type of named beans
049     */
050    public NamedBeanType getNamedBeanType() {
051        return _namedBeanType;
052    }
053    
054    /**
055     * Set the type of the named beans
056     * @param namedBeanType the type of the named beans
057     */
058    public void setNamedBeanType(@Nonnull NamedBeanType namedBeanType) {
059        if (namedBeanType == null) throw new RuntimeException("Daniel");
060        _namedBeanType = namedBeanType;
061    }
062    
063    public void setTable(@Nonnull String tableName) {
064        assertListenersAreNotRegistered(log, "setTable");
065        NamedTable table = InstanceManager.getDefault(NamedTableManager.class).getNamedTable(tableName);
066        if (table != null) {
067            setTable(table);
068        } else {
069            removeTable();
070            log.error("table \"{}\" is not found", tableName);
071        }
072    }
073    
074    public void setTable(@Nonnull NamedBeanHandle<NamedTable> handle) {
075        assertListenersAreNotRegistered(log, "setTable");
076        _tableHandle = handle;
077        InstanceManager.getDefault(NamedTableManager.class).addVetoableChangeListener(this);
078    }
079    
080    public void setTable(@Nonnull NamedTable table) {
081        assertListenersAreNotRegistered(log, "setTable");
082        setTable(InstanceManager.getDefault(NamedBeanHandleManager.class)
083                .getNamedBeanHandle(table.getDisplayName(), table));
084    }
085    
086    public void removeTable() {
087        assertListenersAreNotRegistered(log, "setTable");
088        if (_tableHandle != null) {
089            InstanceManager.getDefault(NamedTableManager.class).removeVetoableChangeListener(this);
090            _tableHandle = null;
091        }
092    }
093    
094    public NamedBeanHandle<NamedTable> getTable() {
095        return _tableHandle;
096    }
097    
098    /**
099     * Get tableRowOrColumn.
100     * @return tableRowOrColumn
101     */
102    public TableRowOrColumn getTableRowOrColumn() {
103        return _tableRowOrColumn;
104    }
105    
106    /**
107     * Set tableRowOrColumn.
108     * @param tableRowOrColumn tableRowOrColumn
109     */
110    public void setTableRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) {
111        _tableRowOrColumn = tableRowOrColumn;
112    }
113    
114    /**
115     * Get name of row or column
116     * @return name of row or column
117     */
118    public String getRowOrColumnName() {
119        return _rowOrColumnName;
120    }
121    
122    /**
123     * Set name of row or column
124     * @param rowOrColumnName name of row or column
125     */
126    public void setRowOrColumnName(@Nonnull String rowOrColumnName) {
127        if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null");
128        _rowOrColumnName = rowOrColumnName;
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    @Override
153    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
154        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
155            if (evt.getOldValue() instanceof NamedTable) {
156                if (evt.getOldValue().equals(getTable().getBean())) {
157                    throw new PropertyVetoException(getDisplayName(), evt);
158                }
159            }
160        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
161            if (evt.getOldValue() instanceof NamedTable) {
162                if (evt.getOldValue().equals(getTable().getBean())) {
163                    removeTable();
164                }
165            }
166        }
167    }
168
169    /** {@inheritDoc} */
170    @Override
171    public Category getCategory() {
172        return Category.OTHER;
173    }
174
175    /** {@inheritDoc} */
176    @Override
177    public void execute() {
178        // Do nothing.
179        // The purpose of this action is only to listen on property changes
180        // of the registered beans and execute the ConditionalNG when it
181        // happens.
182    }
183
184    @Override
185    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
186        throw new UnsupportedOperationException("Not supported.");
187    }
188
189    @Override
190    public int getChildCount() {
191        return 0;
192    }
193
194    @Override
195    public String getShortDescription(Locale locale) {
196        return Bundle.getMessage(locale, "ActionListenOnBeansTable_Short");
197    }
198
199    @Override
200    public String getLongDescription(Locale locale) {
201        return Bundle.getMessage(locale, "ActionListenOnBeansTable_Long",
202                _namedBeanType.toString(),
203                _tableRowOrColumn.getOpposite().toStringLowerCase(),
204                _tableRowOrColumn.toStringLowerCase(),
205                _rowOrColumnName,
206                getTable() != null ? getTable().getName() : "");
207    }
208
209    /** {@inheritDoc} */
210    @Override
211    public void setup() {
212        // Do nothing
213    }
214
215    public List<String> getItems() {
216        List<String> items = new ArrayList<>();
217        
218        if (_tableHandle == null) {
219            log.error("tableHandle is null");
220            return items;   // The list is empty
221        }
222        if (_rowOrColumnName.isEmpty()) {
223            log.error("rowOrColumnName is empty string");
224            return items;   // The list is empty
225        }
226        
227        NamedTable table = _tableHandle.getBean();
228        
229        if (_tableRowOrColumn == TableRowOrColumn.Row) {
230            int row = table.getRowNumber(_rowOrColumnName);
231            for (int column=1; column <= table.numColumns(); column++) {
232                // If the header is null or empty, treat the row as a comment
233                // unless _includeRowColumnWithoutHeader is true
234                Object header = table.getCell(0, column);
235//                System.out.format("Row header: %s%n", header);
236                if (_includeCellsWithoutHeader
237                        || ((header != null) && (!header.toString().isEmpty()))) {
238                    Object cell = table.getCell(row, column);
239                    if (cell != null) items.add(cell.toString());
240                }
241            }
242        } else {
243            int column = table.getColumnNumber(_rowOrColumnName);
244            for (int row=1; row <= table.numRows(); row++) {
245                // If the header is null or empty, treat the row as a comment
246                // unless _includeRowColumnWithoutHeader is true
247                Object header = table.getCell(row, 0);
248//                System.out.format("Column header: %s%n", header);
249                if (_includeCellsWithoutHeader
250                        || ((header != null) && (!header.toString().isEmpty()))) {
251                    Object cell = table.getCell(row, column);
252                    if (cell != null && !cell.toString().isEmpty()) items.add(cell.toString());
253                }
254            }
255        }
256        return items;
257    }
258
259    /** {@inheritDoc} */
260    @Override
261    public void registerListenersForThisClass() {
262        if (_listenersAreRegistered) return;
263
264        List<String> items = getItems();
265        
266        for (String item : items) {
267            NamedBean namedBean = _namedBeanType.getManager().getNamedBean(item);
268            
269            if (namedBean != null) {
270                Map.Entry<NamedBean, String> namedBeanEntry =
271                        new HashMap.SimpleEntry<>(namedBean, _namedBeanType.getPropertyName());
272
273                _namedBeansEntries.add(namedBeanEntry);
274                namedBean.addPropertyChangeListener(_namedBeanType.getPropertyName(), this);
275            } else {
276                log.warn("The named bean \"{}\" cannot be found in the manager for {}", item, _namedBeanType.toString());
277            }
278        }
279        _listenersAreRegistered = true;
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    public void unregisterListenersForThisClass() {
285        if (!_listenersAreRegistered) return;
286
287        for (Map.Entry<NamedBean, String> namedBeanEntry : _namedBeansEntries) {
288            namedBeanEntry.getKey().removePropertyChangeListener(namedBeanEntry.getValue(), this);
289        }
290        _listenersAreRegistered = false;
291    }
292
293    /** {@inheritDoc} */
294    @Override
295    public void propertyChange(PropertyChangeEvent evt) {
296        getConditionalNG().execute();
297    }
298
299    /** {@inheritDoc} */
300    @Override
301    public void disposeMe() {
302    }
303
304
305    /** {@inheritDoc} */
306    @Override
307    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
308/*        
309        log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report);
310        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
311            if (namedBeanReference._handle != null) {
312                if (bean.equals(namedBeanReference._handle.getBean())) {
313                    report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
314                }
315            }
316        }
317*/
318    }
319
320    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeansTable.class);
321
322}