001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005import java.util.stream.Collectors;
006
007import jmri.*;
008import jmri.jmrit.logixng.*;
009import jmri.jmrit.logixng.util.DuplicateKeyMap;
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 ActionListenOnBeans extends AbstractDigitalAction
017        implements PropertyChangeListener, VetoableChangeListener {
018
019    private final Map<String, NamedBeanReference> _namedBeanReferences = new DuplicateKeyMap<>();
020
021    public ActionListenOnBeans(String sys, String user)
022            throws BadUserNameException, BadSystemNameException {
023        super(sys, user);
024    }
025
026    @Override
027    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) {
028        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
029        String sysName = systemNames.get(getSystemName());
030        String userName = userNames.get(getSystemName());
031        if (sysName == null) sysName = manager.getAutoSystemName();
032        ActionListenOnBeans copy = new ActionListenOnBeans(sysName, userName);
033        copy.setComment(getComment());
034        for (NamedBeanReference reference : _namedBeanReferences.values()) {
035            copy.addReference(reference);
036        }
037        return manager.registerAction(copy);
038    }
039
040    /**
041     * Register a bean
042     * The bean must be on the form "beantype:name" where beantype is for
043     * example turnout, sensor or memory, and name is the name of the bean.
044     * The type can be upper case or lower case, it doesn't matter.
045     * @param beanAndType the bean and type
046     */
047    public void addReference(String beanAndType) {
048        assertListenersAreNotRegistered(log, "addReference");
049        String[] parts = beanAndType.split(":");
050        if (parts.length != 2) {
051            throw new IllegalArgumentException(
052                    "Parameter 'beanAndType' must be on the format type:name"
053                    + " where type is turnout, sensor, memory, ...");
054        }
055
056        try {
057            NamedBeanType type = NamedBeanType.valueOf(parts[0]);
058            NamedBeanReference reference = new NamedBeanReference(parts[1], type);
059            _namedBeanReferences.put(reference._name, reference);
060        } catch (IllegalArgumentException e) {
061            String types = Arrays.asList(NamedBeanType.values())
062                    .stream()
063                    .map(Enum::toString)
064                    .collect(Collectors.joining(", "));
065            throw new IllegalArgumentException(
066                    "Parameter 'beanAndType' has wrong type. Valid types are: " + types);
067        }
068    }
069
070    public void addReference(NamedBeanReference reference) {
071        assertListenersAreNotRegistered(log, "addReference");
072        _namedBeanReferences.put(reference._name, reference);
073    }
074
075    public void removeReference(NamedBeanReference reference) {
076        assertListenersAreNotRegistered(log, "removeReference");
077        _namedBeanReferences.remove(reference._name);
078    }
079
080    public Collection<NamedBeanReference> getReferences() {
081        return _namedBeanReferences.values();
082    }
083
084    public void clearReferences() {
085        _namedBeanReferences.clear();
086    }
087
088    @Override
089    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
090/*
091        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
092            if (evt.getOldValue() instanceof Memory) {
093                if (evt.getOldValue().equals(getMemory().getBean())) {
094                    throw new PropertyVetoException(getDisplayName(), evt);
095                }
096            }
097        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
098            if (evt.getOldValue() instanceof Memory) {
099                if (evt.getOldValue().equals(getMemory().getBean())) {
100                    setMemory((Memory)null);
101                }
102            }
103        }
104*/
105    }
106
107    /** {@inheritDoc} */
108    @Override
109    public Category getCategory() {
110        return Category.OTHER;
111    }
112
113    /** {@inheritDoc} */
114    @Override
115    public void execute() {
116        // Do nothing.
117        // The purpose of this action is only to listen on property changes
118        // of the registered beans and execute the ConditionalNG when it
119        // happens.
120    }
121
122    @Override
123    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
124        throw new UnsupportedOperationException("Not supported.");
125    }
126
127    @Override
128    public int getChildCount() {
129        return 0;
130    }
131
132    @Override
133    public String getShortDescription(Locale locale) {
134        return Bundle.getMessage(locale, "ActionListenOnBeans_Short");
135    }
136
137    @Override
138    public String getLongDescription(Locale locale) {
139        return Bundle.getMessage(locale, "ActionListenOnBeans_Long");
140    }
141
142    /** {@inheritDoc} */
143    @Override
144    public void setup() {
145        // Do nothing
146    }
147
148    /** {@inheritDoc} */
149    @Override
150    public void registerListenersForThisClass() {
151        if (_listenersAreRegistered) return;
152
153        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
154            if (namedBeanReference._handle != null) {
155                namedBeanReference._handle.getBean()
156                        .addPropertyChangeListener(namedBeanReference._type.getPropertyName(), this);
157            }
158        }
159        _listenersAreRegistered = true;
160    }
161
162    /** {@inheritDoc} */
163    @Override
164    public void unregisterListenersForThisClass() {
165        if (!_listenersAreRegistered) return;
166
167        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
168            if (namedBeanReference._handle != null) {
169                namedBeanReference._handle.getBean()
170                        .removePropertyChangeListener(namedBeanReference._type.getPropertyName(), this);
171            }
172        }
173        _listenersAreRegistered = false;
174    }
175
176    /** {@inheritDoc} */
177    @Override
178    public void propertyChange(PropertyChangeEvent evt) {
179        getConditionalNG().execute();
180    }
181
182    /** {@inheritDoc} */
183    @Override
184    public void disposeMe() {
185    }
186
187
188    public static class NamedBeanReference {
189
190        private String _name;
191        private NamedBeanType _type;
192        private NamedBeanHandle<? extends NamedBean> _handle;
193
194        public NamedBeanReference(String name, NamedBeanType type) {
195            _name = name;
196            _type = type;
197
198            NamedBean bean = _type.getManager().getNamedBean(name);
199            if (bean != null) {
200                _handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(_name, bean);
201            }
202        }
203
204        public String getName() {
205            return _name;
206        }
207
208        public void setName(String name) {
209            _name = name;
210        }
211
212        public NamedBeanType getType() {
213            return _type;
214        }
215
216        public void setType(NamedBeanType type) {
217            if (type == null) throw new NullPointerException("type is null");
218            _type = type;
219        }
220
221        public NamedBeanHandle<? extends NamedBean> getHandle() {
222            return _handle;
223        }
224
225        public void updateHandle() {
226            if (!_name.isEmpty()) {
227                NamedBean bean = _type.getManager().getNamedBean(_name);
228                if (bean != null) {
229                    _handle = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(_name, bean);
230                } else {
231                    log.warn("Cannot find named bean "+_name+" in manager for "+_type.getManager().getBeanTypeHandled());
232                    _handle = null;
233                }
234            } else {
235                _handle = null;
236            }
237        }
238    }
239
240    /** {@inheritDoc} */
241    @Override
242    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
243        log.debug("getUsageReport :: ActionListenOnBeans: bean = {}, report = {}", cdl, report);
244        for (NamedBeanReference namedBeanReference : _namedBeanReferences.values()) {
245            if (namedBeanReference._handle != null) {
246                if (bean.equals(namedBeanReference._handle.getBean())) {
247                    report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
248                }
249            }
250        }
251    }
252
253    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionListenOnBeans.class);
254
255}