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