001package jmri.jmrit.logixng.actions;
002
003import jmri.jmrit.logixng.NamedBeanType;
004
005import java.beans.*;
006import java.util.*;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
013import jmri.jmrit.logixng.util.parser.ParserException;
014
015/**
016 * This action listens on some beans and runs the ConditionalNG on property change.
017 *
018 * @author Daniel Bergqvist Copyright 2022
019 */
020public class ActionCreateBeansFromTable extends AbstractDigitalAction
021        implements PropertyChangeListener, VetoableChangeListener {
022
023    private boolean _onlyCreatableTypes = true;
024    private NamedBeanType _namedBeanType = NamedBeanType.Light;
025    private final LogixNG_SelectNamedBean<NamedTable> _selectNamedBean =
026            new LogixNG_SelectNamedBean<>(
027                    this, NamedTable.class, InstanceManager.getDefault(NamedTableManager.class), this);
028    private TableRowOrColumn _tableRowOrColumn = TableRowOrColumn.Row;
029    private String _rowOrColumnSystemName = "";
030    private String _rowOrColumnUserName = "";
031    private boolean _includeCellsWithoutHeader = false;
032    private final List<Map.Entry<NamedBean, String>> _namedBeansEntries = new ArrayList<>();
033    private boolean _moveUserName = false;
034    private boolean _updateToUserName = false;
035    private boolean _removeOldBean = false;
036
037    public ActionCreateBeansFromTable(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        ActionCreateBeansFromTable copy = new ActionCreateBeansFromTable(sysName, userName);
050        copy.setComment(getComment());
051        copy.setOnlyCreatableTypes(_onlyCreatableTypes);
052        copy.setNamedBeanType(_namedBeanType);
053        _selectNamedBean.copy(copy._selectNamedBean);
054        copy.setTableRowOrColumn(_tableRowOrColumn);
055        copy.setRowOrColumnSystemName(_rowOrColumnSystemName);
056        copy.setRowOrColumnUserName(_rowOrColumnUserName);
057        copy.setIncludeCellsWithoutHeader(_includeCellsWithoutHeader);
058        copy.setMoveUserName(_moveUserName);
059        copy.setUpdateToUserName(_updateToUserName);
060        copy.setRemoveOldBean(_removeOldBean);
061
062        for (var entry : _namedBeansEntries) {
063            copy._namedBeansEntries.add(
064                    new HashMap.SimpleEntry<>(entry.getKey(), entry.getValue()));
065        }
066
067        return manager.registerAction(copy);
068    }
069
070    /**
071     * Get whenever to show only types that can be created with this action.
072     * @return true if show only types that can be created, false otherwise
073     */
074    public boolean isOnlyCreatableTypes() {
075        return _onlyCreatableTypes;
076    }
077
078    /**
079     * Set whenever to show only types that can be created with this action.
080     * @param onlyCreatableTypes true show only types that can be created,
081     *                           false otherwise
082     */
083    public void setOnlyCreatableTypes(boolean onlyCreatableTypes) {
084        _onlyCreatableTypes = onlyCreatableTypes;
085    }
086
087    /**
088     * Get the type of the named beans
089     * @return the type of named beans
090     */
091    public NamedBeanType getNamedBeanType() {
092        return _namedBeanType;
093    }
094
095    /**
096     * Set the type of the named beans
097     * @param namedBeanType the type of the named beans
098     */
099    public void setNamedBeanType(@Nonnull NamedBeanType namedBeanType) {
100        if (namedBeanType == null) throw new RuntimeException("Daniel");
101        _namedBeanType = namedBeanType;
102    }
103
104    public LogixNG_SelectNamedBean<NamedTable> getSelectNamedBean() {
105        return _selectNamedBean;
106    }
107
108    /**
109     * Get tableRowOrColumn.
110     * @return tableRowOrColumn
111     */
112    public TableRowOrColumn getTableRowOrColumn() {
113        return _tableRowOrColumn;
114    }
115
116    /**
117     * Set tableRowOrColumn.
118     * @param tableRowOrColumn tableRowOrColumn
119     */
120    public void setTableRowOrColumn(@Nonnull TableRowOrColumn tableRowOrColumn) {
121        _tableRowOrColumn = tableRowOrColumn;
122    }
123
124    /**
125     * Get name of row or column
126     * @return name of row or column
127     */
128    public String getRowOrColumnSystemName() {
129        return _rowOrColumnSystemName;
130    }
131
132    /**
133     * Set name of row or column
134     * @param rowOrColumnName name of row or column
135     */
136    public void setRowOrColumnSystemName(@Nonnull String rowOrColumnName) {
137        if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null");
138        _rowOrColumnSystemName = rowOrColumnName;
139    }
140
141    /**
142     * Get name of row or column
143     * @return name of row or column
144     */
145    public String getRowOrColumnUserName() {
146        return _rowOrColumnUserName;
147    }
148
149    /**
150     * Set name of row or column
151     * @param rowOrColumnName name of row or column
152     */
153    public void setRowOrColumnUserName(@Nonnull String rowOrColumnName) {
154        if (rowOrColumnName == null) throw new IllegalArgumentException("Row/column name is null");
155        _rowOrColumnUserName = rowOrColumnName;
156    }
157
158    /**
159     * Get whenever to include cells that doesn't have a header.
160     * Cells without headers can be used to use some cells in the table
161     * as comments.
162     * @return true if include cells that doesn't have a header, false otherwise
163     */
164    public boolean isIncludeCellsWithoutHeader() {
165        return _includeCellsWithoutHeader;
166    }
167
168    /**
169     * Set whenever to include cells that doesn't have a header.
170     * Cells without headers can be used to use some cells in the table
171     * as comments.
172     * @param includeCellsWithoutHeader true if include rows/columns that
173     *                                  doesn't have a header, false otherwise
174     */
175    public void setIncludeCellsWithoutHeader(boolean includeCellsWithoutHeader) {
176        _includeCellsWithoutHeader = includeCellsWithoutHeader;
177    }
178
179    /**
180     * Get whenever to move the user name to the new bean.
181     * @return true if username should be moved, false otherwise
182     */
183    public boolean isMoveUserName() {
184        return _moveUserName;
185    }
186
187    /**
188     * Set whenever to move the user name to the new bean.
189     * @param isMoveUserName true if username should be moved, false otherwise
190     */
191    public void setMoveUserName(boolean isMoveUserName) {
192        _moveUserName = isMoveUserName;
193    }
194
195    /**
196     * Get whenever to use the user name for beans that already uses the system name.
197     * @return true if update beans to use user name, false otherwise
198     */
199    public boolean isUpdateToUserName() {
200        return _updateToUserName;
201    }
202
203    /**
204     * Set whenever to use the user name for beans that already uses the system name.
205     * @param updateToUserName true if update beans to use user name, false otherwise
206     */
207    public void setUpdateToUserName(boolean updateToUserName) {
208        _updateToUserName = updateToUserName;
209    }
210
211    /**
212     * Get whenever to remove the old bean.
213     * @return true if remove old bean, false otherwise
214     */
215    public boolean isRemoveOldBean() {
216        return _removeOldBean;
217    }
218
219    /**
220     * Set whenever to remove the old bean.
221     * @param removeOldBean true if remove old bean, false otherwise
222     */
223    public void setRemoveOldBean(boolean removeOldBean) {
224        _removeOldBean = removeOldBean;
225    }
226
227    /** {@inheritDoc} */
228    @Override
229    public Category getCategory() {
230        return Category.OTHER;
231    }
232
233    private List<BeanName> getItems() {
234        List<BeanName> items = new ArrayList<>();
235
236        if (_selectNamedBean.getNamedBean() == null) {
237            log.error("No table name is given");
238            return items;   // The list is empty
239        }
240        if (_rowOrColumnSystemName.isEmpty()) {
241            log.error("rowOrColumnSystemName is empty string");
242            return items;   // The list is empty
243        }
244
245        NamedTable table = _selectNamedBean.getBean();
246
247        if (_tableRowOrColumn == TableRowOrColumn.Row) {
248            int systemNameRow = table.getRowNumber(_rowOrColumnSystemName);
249            int userNameRow = table.getRowNumber(_rowOrColumnUserName);
250            for (int column=1; column <= table.numColumns(); column++) {
251                // If the header is null or empty, treat the row as a comment
252                // unless _includeRowColumnWithoutHeader is true
253                Object header = table.getCell(0, column);
254//                System.out.format("Row header: %s%n", header);
255                if (_includeCellsWithoutHeader
256                        || ((header != null) && (!header.toString().isEmpty()))) {
257                    Object systemNameCell = table.getCell(systemNameRow, column);
258                    Object userNameCell = table.getCell(userNameRow, column);
259                    if (systemNameCell != null && !systemNameCell.toString().isBlank()) {
260                        if (userNameCell != null && !userNameCell.toString().isBlank()) {
261                            items.add(new BeanName(systemNameCell.toString(), userNameCell.toString()));
262                        } else {
263                            items.add(new BeanName(systemNameCell.toString(), null));
264                        }
265                    }
266                }
267            }
268        } else {
269            int systemNameColumn = table.getColumnNumber(_rowOrColumnSystemName);
270            int userNameColumn = table.getColumnNumber(_rowOrColumnUserName);
271            for (int row=1; row <= table.numRows(); row++) {
272                // If the header is null or empty, treat the row as a comment
273                // unless _includeRowColumnWithoutHeader is true
274                Object header = table.getCell(row, 0);
275//                System.out.format("Column header: %s%n", header);
276                if (_includeCellsWithoutHeader
277                        || ((header != null) && (!header.toString().isEmpty()))) {
278                    Object systemNameCell = table.getCell(row, systemNameColumn);
279                    Object userNameCell = table.getCell(row, userNameColumn);
280                    if (systemNameCell != null && !systemNameCell.toString().isBlank()) {
281                        if (userNameCell != null && !userNameCell.toString().isBlank()) {
282                            items.add(new BeanName(systemNameCell.toString(), userNameCell.toString()));
283                        } else {
284                            items.add(new BeanName(systemNameCell.toString(), null));
285                        }
286                    }
287                }
288            }
289        }
290        return items;
291    }
292
293    private void moveUserName(
294            NamedBean oldNameBean,
295            NamedBean newNameBean,
296            String userName)
297            throws JmriException {
298
299        NamedBeanHandleManager nbMan = InstanceManager.getDefault(NamedBeanHandleManager.class);
300
301        if (nbMan.inUse(oldNameBean.getSystemName(), oldNameBean)) {
302            if (_updateToUserName) {
303                nbMan.updateBeanFromSystemToUser(oldNameBean);
304            }
305        }
306
307        oldNameBean.setUserName(null);
308        newNameBean.setUserName(userName);
309        nbMan.moveBean(oldNameBean, newNameBean, userName);
310    }
311
312    /** {@inheritDoc} */
313    @Override
314    public void execute() throws JmriException {
315        List<BeanName> items = getItems();
316        for (BeanName beanName : items) {
317            NamedBean sysBean = _namedBeanType.getManager().getBySystemName(beanName._systemName);
318            NamedBean userBean = null;
319            if (beanName._userName != null && !beanName._userName.isBlank()) {
320                userBean = _namedBeanType.getManager().getByUserName(beanName._userName);
321            }
322
323            // Create new bean if it doesn't exists
324            if (sysBean == null) {
325                if (_namedBeanType.getCreateBean() == null) {
326                    throw new JmriException(Bundle.getMessage(
327                            "ActionCreateBeansFromTable_Exception_CreateBeanNotSupported",
328                            _namedBeanType.getName(true)));
329                }
330
331                String userName = userBean != null ? null : beanName._userName;
332                try {
333                    sysBean = _namedBeanType.getCreateBean().createBean(beanName._systemName, userName);
334                } catch (IllegalArgumentException e) {
335                    throw new JmriException(Bundle.getMessage(
336                            "ActionCreateBeansFromTable_Exception_CantCreateBean2",
337                            beanName._systemName, e.getLocalizedMessage()));
338                }
339                if (sysBean == null) {
340                    throw new JmriException(Bundle.getMessage(
341                            "ActionCreateBeansFromTable_Exception_CantCreateBean",
342                            beanName._systemName));
343                }
344            }
345
346            if (userBean == null || sysBean == userBean) continue;
347
348            if (!_moveUserName) {
349                throw new JmriException(Bundle.getMessage("ActionCreateBeansFromTable_Exception_CantMoveUserName"));
350            }
351
352            moveUserName(userBean, sysBean, beanName._userName);
353
354            // Remove old bean if desired
355            if (_removeOldBean) {
356                try {
357                    _namedBeanType.getDeleteBean().deleteBean(userBean, "CanDelete");
358                } catch (java.beans.PropertyVetoException e) {
359                    if (e.getPropertyChangeEvent().getPropertyName().equals("DoNotDelete")) { // NOI18N
360                        throw new JmriException(String.format("Cannot delete bean: %s", e.getPropertyChangeEvent().getOldValue()), e);
361                    }
362                }
363                try {
364                    _namedBeanType.getDeleteBean().deleteBean(userBean, "DoDelete");
365                } catch (java.beans.PropertyVetoException e) {
366                    throw new JmriException(String.format("Cannot delete bean: %s", e.getPropertyChangeEvent().getOldValue()), e);
367                }
368            }
369        }
370    }
371
372    @Override
373    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
374        throw new UnsupportedOperationException("Not supported.");
375    }
376
377    @Override
378    public int getChildCount() {
379        return 0;
380    }
381
382    @Override
383    public String getShortDescription(Locale locale) {
384        return Bundle.getMessage(locale, "ActionCreateBeansFromTable_Short");
385    }
386
387    @Override
388    public String getLongDescription(Locale locale) {
389        String tableName = _selectNamedBean.getDescription(locale);
390        String includeCellsWithoutHeaderStr = _includeCellsWithoutHeader
391                ? Bundle.getMessage(locale, "ActionCreateBeansFromTable_FlagStr",
392                        Bundle.getMessage(locale, "ActionCreateBeansFromTable_IncludeCellsWithoutHeader"))
393                : "";
394        String includeMoveUserNameStr = _moveUserName
395                ? Bundle.getMessage(locale, "ActionCreateBeansFromTable_FlagStr",
396                        Bundle.getMessage(locale, "ActionCreateBeansFromTable_MoveUserName"))
397                : "";
398        String updateToUserNameStr = _updateToUserName
399                ? Bundle.getMessage(locale, "ActionCreateBeansFromTable_FlagStr",
400                        Bundle.getMessage(locale, "ActionCreateBeansFromTable_UpdateToUserName"))
401                : "";
402        String includeRemoveOldBeanStr = _removeOldBean
403                ? Bundle.getMessage(locale, "ActionCreateBeansFromTable_FlagStr",
404                        Bundle.getMessage(locale, "ActionCreateBeansFromTable_RemoveOldBean"))
405                : "";
406
407        return Bundle.getMessage(locale, "ActionCreateBeansFromTable_Long",
408                _namedBeanType.getName(true).toLowerCase(),
409                tableName,
410                _tableRowOrColumn.getOpposite().toStringLowerCase(),
411                _tableRowOrColumn.toStringLowerCase(),
412                _rowOrColumnSystemName,
413                _rowOrColumnUserName,
414                includeCellsWithoutHeaderStr,
415                includeMoveUserNameStr,
416                updateToUserNameStr,
417                includeRemoveOldBeanStr);
418    }
419
420    /** {@inheritDoc} */
421    @Override
422    public void setup() {
423        // Do nothing
424    }
425
426    /** {@inheritDoc} */
427    @Override
428    public void registerListenersForThisClass() {
429        if (_listenersAreRegistered) return;
430
431        _selectNamedBean.registerListeners();
432        _listenersAreRegistered = true;
433    }
434
435    /** {@inheritDoc} */
436    @Override
437    public void unregisterListenersForThisClass() {
438        if (!_listenersAreRegistered) return;
439
440        _selectNamedBean.unregisterListeners();
441        _listenersAreRegistered = false;
442    }
443
444    /** {@inheritDoc} */
445    @Override
446    public void propertyChange(PropertyChangeEvent evt) {
447        getConditionalNG().execute();
448    }
449
450    /** {@inheritDoc} */
451    @Override
452    public void disposeMe() {
453    }
454
455
456    /** {@inheritDoc} */
457    @Override
458    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
459        log.debug("getUsageReport :: ActionCreateBeansFromTable: bean = {}, report = {}", cdl, report);
460        if (_selectNamedBean.getBean() != null) {
461            if (bean.equals(_selectNamedBean.getBean())) {
462                report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
463            }
464        }
465    }
466
467
468    private static class BeanName {
469        final String _systemName;
470        final String _userName;
471
472        BeanName(String systemName, String userName) {
473            _systemName = systemName;
474            _userName = userName;
475        }
476    }
477
478    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionCreateBeansFromTable.class);
479
480}