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