001package jmri.implementation.decorators;
002
003import jmri.NamedBean;
004import jmri.beans.BeanUtil;
005
006import javax.annotation.CheckReturnValue;
007import javax.annotation.Nonnull;
008import javax.annotation.OverridingMethodsMustInvokeSuper;
009import java.beans.PropertyChangeListener;
010import java.beans.PropertyChangeSupport;
011import java.util.ArrayList;
012import java.util.HashMap;
013import java.util.Set;
014
015/**
016 * Abstract base for the NamedBean Decorators.
017 * <p>
018 *
019 * @author Bob Jacobsen Copyright (C) 2001
020 * @author Paul Bender Copyright (C) 2020
021 */
022public abstract class AbstractNamedBeanDecorator implements NamedBean {
023
024    private NamedBean decorated;
025
026    protected AbstractNamedBeanDecorator(NamedBean decorated){
027        this.decorated = decorated;
028    }
029
030    /**
031     * {@inheritDoc}
032     */
033    @Override
034    final public String getComment() {
035        return decorated.getComment();
036    }
037
038    /**
039     * {@inheritDoc}
040     */
041    @Override
042    final public void setComment(String comment) {
043        decorated.setComment(comment);
044    }
045
046    /**
047     * {@inheritDoc}
048     */
049    @Override
050    @CheckReturnValue
051    @Nonnull
052    final public String getDisplayName() {
053        return decorated.getDisplayName();
054    }
055
056    /**
057     * {@inheritDoc}
058     */
059    @Override
060    @CheckReturnValue
061    @Nonnull
062    final public String getDisplayName(DisplayOptions displayOptions) {
063        return decorated.getDisplayName(displayOptions);
064    }
065
066    // implementing classes will typically have a function/listener to get
067    // updates from the layout, which will then call
068    //  public void firePropertyChange(String propertyName,
069    //             Object oldValue,
070    //      Object newValue)
071    // _once_ if anything has changed state
072    // since we can't do a "super(this)" in the ctor to inherit from PropertyChangeSupport, we'll
073    // reflect to it
074    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
075    protected final HashMap<PropertyChangeListener, String> register = new HashMap<>();
076    protected final HashMap<PropertyChangeListener, String> listenerRefs = new HashMap<>();
077
078    @Override
079    @OverridingMethodsMustInvokeSuper
080    public synchronized void addPropertyChangeListener(@Nonnull PropertyChangeListener l,
081                                                       String beanRef, String listenerRef) {
082        pcs.addPropertyChangeListener(l);
083        if (beanRef != null) {
084            register.put(l, beanRef);
085        }
086        if (listenerRef != null) {
087            listenerRefs.put(l, listenerRef);
088        }
089    }
090
091    @Override
092    @OverridingMethodsMustInvokeSuper
093    public synchronized void addPropertyChangeListener(@Nonnull String propertyName,
094                                                       @Nonnull PropertyChangeListener l, String beanRef, String listenerRef) {
095        pcs.addPropertyChangeListener(propertyName, l);
096        if (beanRef != null) {
097            register.put(l, beanRef);
098        }
099        if (listenerRef != null) {
100            listenerRefs.put(l, listenerRef);
101        }
102    }
103
104    @Override
105    @OverridingMethodsMustInvokeSuper
106    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
107        pcs.addPropertyChangeListener(listener);
108    }
109
110    @Override
111    @OverridingMethodsMustInvokeSuper
112    public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
113        pcs.addPropertyChangeListener(propertyName, listener);
114    }
115
116    @Override
117    @OverridingMethodsMustInvokeSuper
118    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
119        pcs.removePropertyChangeListener(listener);
120        if (listener != null && !BeanUtil.contains(pcs.getPropertyChangeListeners(), listener)) {
121            register.remove(listener);
122            listenerRefs.remove(listener);
123        }
124    }
125
126    @Override
127    @OverridingMethodsMustInvokeSuper
128    public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
129        pcs.removePropertyChangeListener(propertyName, listener);
130        if (listener != null && !BeanUtil.contains(pcs.getPropertyChangeListeners(), listener)) {
131            register.remove(listener);
132            listenerRefs.remove(listener);
133        }
134    }
135
136    @Override
137    @Nonnull
138    public synchronized PropertyChangeListener[] getPropertyChangeListenersByReference(@Nonnull String name) {
139        ArrayList<PropertyChangeListener> list = new ArrayList<>();
140        register.entrySet().forEach((entry) -> {
141            PropertyChangeListener l = entry.getKey();
142            if (entry.getValue().equals(name)) {
143                list.add(l);
144            }
145        });
146        return list.toArray(new PropertyChangeListener[list.size()]);
147    }
148
149    /**
150     * Get a meaningful list of places where the bean is in use.
151     *
152     * @return ArrayList of the listeners
153     */
154    @Override
155    public synchronized ArrayList<String> getListenerRefs() {
156        return new ArrayList<>(listenerRefs.values());
157    }
158
159    @Override
160    @OverridingMethodsMustInvokeSuper
161    public synchronized void updateListenerRef(PropertyChangeListener l, String newName) {
162        if (listenerRefs.containsKey(l)) {
163            listenerRefs.put(l, newName);
164        }
165    }
166
167    @Override
168    public synchronized String getListenerRef(PropertyChangeListener l) {
169        return listenerRefs.get(l);
170    }
171
172    /**
173     * Get the number of current listeners.
174     *
175     * @return -1 if the information is not available for some reason.
176     */
177    @Override
178    public synchronized int getNumPropertyChangeListeners() {
179        return pcs.getPropertyChangeListeners().length;
180    }
181
182    @Override
183    @Nonnull
184    public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
185        return pcs.getPropertyChangeListeners();
186    }
187
188    @Override
189    @Nonnull
190    public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
191        return pcs.getPropertyChangeListeners(propertyName);
192    }
193
194    /** {@inheritDoc} */
195    @Override
196    @Nonnull
197    final public String getSystemName() {
198        return decorated.getSystemName();
199    }
200
201    /** {@inheritDoc}
202    */
203    @Nonnull
204    @Override
205    final public String toString() {
206        return decorated.getSystemName();
207    }
208
209    @Override
210    final public String getUserName() {
211        return decorated.getUserName();
212    }
213
214    @Nonnull
215    @Override
216    public String getBeanType() {
217        return decorated.getBeanType();
218    }
219
220    @Override
221    @OverridingMethodsMustInvokeSuper
222    public void setUserName(String s) throws BadUserNameException {
223        decorated.setUserName(s);
224    }
225
226    @OverridingMethodsMustInvokeSuper
227    protected void firePropertyChange(String p, Object old, Object n) {
228        pcs.firePropertyChange(p, old, n);
229    }
230
231    @Override
232    @OverridingMethodsMustInvokeSuper
233    public void dispose() {
234        PropertyChangeListener[] listeners = pcs.getPropertyChangeListeners();
235        for (PropertyChangeListener l : listeners) {
236            pcs.removePropertyChangeListener(l);
237            register.remove(l);
238            listenerRefs.remove(l);
239        }
240    }
241
242    @Override
243    @Nonnull
244    public String describeState(int state) {
245        return decorated.describeState(state);
246    }
247
248    /**
249     * {@inheritDoc}
250     */
251    @Override
252    @OverridingMethodsMustInvokeSuper
253    public void setProperty(@Nonnull String key,Object value){
254        decorated.setProperty(key,value);
255    }
256
257    @Override
258    @OverridingMethodsMustInvokeSuper
259    public Object getProperty(@Nonnull String key) {
260        return decorated.getProperty(key);
261    }
262
263    @Override
264    @OverridingMethodsMustInvokeSuper
265    @Nonnull
266    public Set<String> getPropertyKeys() {
267        return decorated.getPropertyKeys();
268    }
269
270    @Override
271    @OverridingMethodsMustInvokeSuper
272    public void removeProperty(String key) {
273        decorated.removeProperty(key);
274    }
275
276    @Override
277    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
278        decorated.vetoableChange(evt);
279    }
280
281    /**
282     * {@inheritDoc}
283     * <p>
284     * This implementation tests that
285     * {@link NamedBean#getSystemName()}
286     * is equal for this and obj.
287     *
288     * @param obj the reference object with which to compare.
289     * @return {@code true} if this object is the same as the obj argument;
290     *         {@code false} otherwise.
291     */
292    @Override
293    public boolean equals(Object obj) {
294        if (obj == this) return true;  // for efficiency
295        if (obj == null) return false; // by contract
296
297        if (obj instanceof AbstractNamedBeanDecorator) {  // NamedBeans are not equal to things of other types
298            AbstractNamedBeanDecorator b = (AbstractNamedBeanDecorator) obj;
299            return this.getSystemName().equals(b.getSystemName());
300        }
301        return false;
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    @Override
308    public int hashCode() {
309        return this.getSystemName().hashCode();
310    }
311    
312    /**
313     * {@inheritDoc} 
314     */
315    @CheckReturnValue
316    @Override
317    public int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2, @Nonnull NamedBean n) {
318        return decorated.compareSystemNameSuffix(suffix1,suffix2,n);
319    }
320
321}