001package jmri.managers;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyVetoException;
006import java.beans.VetoableChangeListener;
007import java.util.*;
008
009import javax.annotation.CheckReturnValue;
010import javax.annotation.CheckForNull;
011import javax.annotation.Nonnull;
012import javax.annotation.OverridingMethodsMustInvokeSuper;
013
014import jmri.*;
015import jmri.beans.VetoableChangeSupport;
016import jmri.SystemConnectionMemo;
017import jmri.jmrix.ConnectionConfig;
018import jmri.jmrix.ConnectionConfigManager;
019import jmri.jmrix.internal.InternalSystemConnectionMemo;
020import jmri.util.NamedBeanComparator;
021
022/**
023 * Implementation of a Manager that can serves as a proxy for multiple
024 * system-specific implementations.
025 * <p>
026 * Automatically includes an Internal system, which need not be separately added
027 * any more.
028 * <p>
029 * Encapsulates access to the "Primary" manager, used by default, which is the
030 * first one provided.
031 * <p>
032 * Internally, this is done by using an ordered list of all non-Internal
033 * managers, plus a separate reference to the internal manager and default
034 * manager.
035 *
036 * @param <E> the supported type of NamedBean
037 * @author Bob Jacobsen Copyright (C) 2003, 2010, 2018
038 */
039abstract public class AbstractProxyManager<E extends NamedBean> extends VetoableChangeSupport implements ProxyManager<E>, PropertyChangeListener, Manager.ManagerDataListener<E> {
040
041    protected final Map<String, Boolean> silencedProperties = new HashMap<>();
042    protected final Set<String> silenceableProperties = new HashSet<>();
043
044    /**
045     * {@inheritDoc}
046     */
047    @Override
048    public List<Manager<E>> getManagerList() {
049        // make sure internal present
050        initInternal();
051        return new ArrayList<>(mgrs);
052    }
053
054    /**
055     * {@inheritDoc}
056     */
057    @Override
058    public List<Manager<E>> getDisplayOrderManagerList() {
059        // make sure internal present
060        initInternal();
061
062        ArrayList<Manager<E>> retval = new ArrayList<>();
063        if (defaultManager != null) {
064            retval.add(defaultManager);
065        }
066        mgrs.stream()
067                .filter(manager -> manager != defaultManager && manager != internalManager)
068                .forEachOrdered(retval::add);
069        if (internalManager != null && internalManager != defaultManager) {
070            retval.add(internalManager);
071        }
072        return retval;
073    }
074
075    public Manager<E> getInternalManager() {
076        initInternal();
077        return internalManager;
078    }
079
080    /**
081     * {@inheritDoc}
082     */
083    @Override
084    @Nonnull
085    public Manager<E> getDefaultManager() {
086        return defaultManager != null ? defaultManager : getInternalManager();
087    }
088
089    /**
090     * {@inheritDoc}
091     */
092    @Override
093    public void addManager(@Nonnull Manager<E> m) {
094        Objects.requireNonNull(m, "Can only add non-null manager");
095        // check for already present
096        for (Manager<E> check : mgrs) {
097            if (m == check) { // can't use contains(..) because of Comparator.equals is on the prefix
098                // already present, complain and skip
099                log.warn("Manager already present: {}", m); // NOI18N
100                return;
101            }
102        }
103        mgrs.add(m);
104
105        if (defaultManager == null) {
106            defaultManager = m;  // 1st one is default
107        }
108
109        // these arrays include the listeners for named properties, wrapped
110        // in a proxy that adds them to the manager for that property name
111        Arrays.stream(getPropertyChangeListeners()).forEach(l -> m.addPropertyChangeListener(l));
112        Arrays.stream(getVetoableChangeListeners()).forEach(l -> m.addVetoableChangeListener(l));
113
114        m.addPropertyChangeListener("beans", this);
115        m.addDataListener(this);
116        recomputeNamedBeanSet();
117        log.debug("added manager {}", m.getClass());
118    }
119
120    protected Manager<E> initInternal() {
121        if (internalManager == null) {
122            log.debug("create internal manager when first requested"); // NOI18N
123            internalManager = makeInternalManager();
124        }
125        return internalManager;
126    }
127
128    private final Set<Manager<E>> mgrs = new TreeSet<>((Manager<E> e1, Manager<E> e2) -> e1.getSystemPrefix().compareTo(e2.getSystemPrefix()));
129    private Manager<E> internalManager = null;
130    protected Manager<E> defaultManager = null;
131
132    /**
133     * Create specific internal manager as needed for concrete type.
134     *
135     * @return an internal manager
136     */
137    abstract protected Manager<E> makeInternalManager();
138
139    /** {@inheritDoc} */
140    @Override
141    public E getNamedBean(@Nonnull String name) {
142        E t = getByUserName(name);
143        if (t != null) {
144            return t;
145        }
146        return getBySystemName(name);
147    }
148
149    /** {@inheritDoc} */
150    @Override
151    @CheckReturnValue
152    @CheckForNull
153    public E getBySystemName(@Nonnull String systemName) {
154        Manager<E> m = getManager(systemName);
155        if (m == null) {
156            log.debug("getBySystemName did not find manager from name {}, defer to default manager", systemName);
157            m = getDefaultManager();
158        }
159        return m.getBySystemName(systemName);
160    }
161
162    /** {@inheritDoc} */
163    @Override
164    @CheckReturnValue
165    @CheckForNull
166    public E getByUserName(@Nonnull String userName) {
167        for (Manager<E> m : this.mgrs) {
168            E b = m.getByUserName(userName);
169            if (b != null) {
170                return b;
171            }
172        }
173        return null;
174    }
175
176    /**
177     * {@inheritDoc}
178     * <p>
179     * This implementation locates a specific Manager based on the system name
180     * and validates against that. If no matching Manager exists, the default
181     * Manager attempts to validate the system name.
182     */
183    @Override
184    @Nonnull
185    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) {
186        Manager<E> manager = getManager(systemName);
187        if (manager == null) {
188            manager = getDefaultManager();
189        }
190        return manager.validateSystemNameFormat(systemName, locale);
191    }
192
193    /**
194     * Validate system name format. Locate a system specific Manager based on a
195     * system name.
196     *
197     * @return if a manager is found, return its determination of validity of
198     *         system name format. Return INVALID if no manager exists.
199     */
200    @Override
201    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
202        Manager<E> m = getManager(systemName);
203        return m == null ? NameValidity.INVALID : m.validSystemNameFormat(systemName);
204    }
205
206    /** {@inheritDoc} */
207    @Override
208    public void dispose() {
209        mgrs.forEach(m -> m.dispose());
210        mgrs.clear();
211        if (internalManager != null) {
212            internalManager.dispose(); // don't make if not made yet
213        }
214    }
215
216    /**
217     * Get the manager for the given system name.
218     *
219     * @param systemName the given name
220     * @return the requested manager or null if there is no matching manager
221     */
222    @CheckForNull
223    protected Manager<E> getManager(@Nonnull String systemName) {
224        // make sure internal present
225        initInternal();
226        for (Manager<E> m : mgrs) {
227            if (systemName.startsWith(m.getSystemNamePrefix())) {
228                return m;
229            }
230        }
231        return null;
232    }
233
234    /**
235     * Get the manager for the given system name or the default manager if there
236     * is no matching manager.
237     *
238     * @param systemName the given name
239     * @return the requested manager or the default manager if there is no
240     *         matching manager
241     */
242    @Nonnull
243    protected Manager<E> getManagerOrDefault(@Nonnull String systemName) {
244        Manager<E> manager = getManager(systemName);
245        if (manager == null) {
246            manager = getDefaultManager();
247        }
248        return manager;
249    }
250
251    /**
252     * Shared method to create a systemName based on the address base, the prefix and manager class.
253     *
254     * @param curAddress base address to use
255     * @param prefix system prefix to use
256     * @param beanType Bean Type for manager (method is used for Turnout and Sensor Managers)
257     * @return a valid system name for this connection
258     * @throws JmriException if systemName cannot be created
259     */
260    String createSystemName(String curAddress, String prefix, Class<?> beanType) throws JmriException {
261        for (Manager<E> m : mgrs) {
262            if (prefix.equals(m.getSystemPrefix()) && beanType.equals(m.getNamedBeanClass())) {
263                try {
264                    if (beanType == Turnout.class) {
265                        return ((TurnoutManager) m).createSystemName(curAddress, prefix);
266                    } else if (beanType == Sensor.class) {
267                        return ((SensorManager) m).createSystemName(curAddress, prefix);
268                    }
269                    else if (beanType == Light.class) {
270                        return ((LightManager) m).createSystemName(curAddress, prefix);
271                    }
272                    else if (beanType == Reporter.class) {
273                        return ((ReporterManager) m).createSystemName(curAddress, prefix);
274                    }
275                    else {
276                        log.warn("createSystemName requested for incompatible Manager");
277                    }
278                } catch (jmri.JmriException ex) {
279                    throw ex;
280                }
281            }
282        }
283        throw new jmri.JmriException("Manager could not be found for System Prefix " + prefix);
284    }
285
286    @Nonnull
287    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws jmri.JmriException {
288        return createSystemName(curAddress, prefix, getNamedBeanClass());
289    }
290
291    @Nonnull
292    public String getNextValidSystemName(@Nonnull NamedBean currentBean) throws JmriException {
293        int prefixLength = Manager.getSystemPrefixLength(currentBean.getSystemName());
294
295        String prefix = currentBean.getSystemName().substring(0, prefixLength);
296        char typeLetter = currentBean.getSystemName().charAt(prefixLength);
297
298        for (Manager<E> m : mgrs) {
299            log.debug("getNextValidSystemName requested for {}", currentBean.getSystemName());
300            if (prefix.equals(m.getSystemPrefix()) && typeLetter == m.typeLetter()) {
301                return ((NameIncrementingManager) m).getNextValidSystemName(currentBean); // can thrown JmriException upward
302            }
303        }
304        throw new jmri.JmriException("\""+currentBean.getSystemName()+"\" did not match a manager");
305    }
306
307    /** {@inheritDoc} */
308    @Override
309    public void deleteBean(@Nonnull E s, @Nonnull String property) throws PropertyVetoException {
310        Manager<E> m = getManager(s.getSystemName());
311        if (m != null) {
312            m.deleteBean(s, property);
313        }
314    }
315
316    /**
317     * Try to create a system manager. If this proxy manager is able to create
318     * a system manager, the concrete class must implement this method.
319     *
320     * @param memo the system connection memo for this connection
321     * @return the new manager or null if it's not possible to create the manager
322     */
323    protected Manager<E> createSystemManager(@Nonnull SystemConnectionMemo memo) {
324        return null;
325    }
326
327    /**
328     * Get the Default Manager ToolTip.
329     * {@inheritDoc}
330     */
331    @Override
332    public String getEntryToolTip() {
333        return getDefaultManager().getEntryToolTip();
334    }
335
336    /**
337     * Try to create a system manager.
338     *
339     * @param systemPrefix the system prefix
340     * @return the new manager or null if it's not possible to create the manager
341     */
342    private Manager<E> createSystemManager(@Nonnull String systemPrefix) {
343        Manager<E> m = null;
344
345        ConnectionConfigManager manager = InstanceManager.getNullableDefault(ConnectionConfigManager.class);
346        if (manager == null) return null;
347
348        ConnectionConfig connections[] = manager.getConnections();
349
350        for (ConnectionConfig connection : connections) {
351            if (systemPrefix.equals(connection.getAdapter().getSystemPrefix())) {
352                m = createSystemManager(connection.getAdapter().getSystemConnectionMemo());
353            }
354            if (m != null) break;
355        }
356//        if (m == null) throw new RuntimeException("Manager not created");
357        return m;
358    }
359
360    /**
361     * {@inheritDoc}
362     * <p>
363     * Forwards the register request to the matching system.
364     */
365    @Override
366    public void register(@Nonnull E s) {
367        Manager<E> m = getManager(s.getSystemName());
368        if (m == null) {
369            String systemPrefix = Manager.getSystemPrefix(s.getSystemName());
370            m = createSystemManager(systemPrefix);
371        }
372        if (m != null) {
373            m.register(s);
374        } else {
375            log.error("Unable to register {} in this proxy manager. No system specific manager supports this bean.", s.getSystemName());
376        }
377    }
378
379    /**
380     * {@inheritDoc}
381     * <p>
382     * Forwards the deregister request to the matching system.
383     *
384     * @param s the name
385     */
386    @Override
387    public void deregister(@Nonnull E s) {
388        Manager<E> m = getManager(s.getSystemName());
389        if (m != null) {
390            m.deregister(s);
391        }
392    }
393
394    /**
395     * {@inheritDoc}
396     * List does not contain duplicates.
397     */
398    @Nonnull
399    @Override
400    public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() {
401        // Create List as set to prevent duplicates from multiple managers
402        // of the same hardware type.
403        Set<NamedBeanPropertyDescriptor<?>> set = new HashSet<>();
404        mgrs.forEach(m -> set.addAll(m.getKnownBeanProperties()));
405        return new ArrayList<>(set);
406    }
407
408    /** {@inheritDoc} */
409    @Override
410    @OverridingMethodsMustInvokeSuper
411    public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
412        super.addPropertyChangeListener(l);
413        mgrs.forEach(m -> m.addPropertyChangeListener(l));
414    }
415
416    /** {@inheritDoc} */
417    @Override
418    @OverridingMethodsMustInvokeSuper
419    public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
420        super.removePropertyChangeListener(l);
421        mgrs.forEach(m -> m.removePropertyChangeListener(l));
422    }
423
424    /** {@inheritDoc} */
425    @Override
426    @OverridingMethodsMustInvokeSuper
427    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
428        super.addPropertyChangeListener(propertyName, listener);
429        mgrs.forEach(m -> m.addPropertyChangeListener(propertyName, listener));
430    }
431
432    /** {@inheritDoc} */
433    @Override
434    @OverridingMethodsMustInvokeSuper
435    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
436        super.removePropertyChangeListener(propertyName, listener);
437        mgrs.forEach(m -> m.removePropertyChangeListener(propertyName, listener));
438    }
439
440    /** {@inheritDoc} */
441    @Override
442    @OverridingMethodsMustInvokeSuper
443    public synchronized void addVetoableChangeListener(VetoableChangeListener l) {
444        super.addVetoableChangeListener(l);
445        mgrs.forEach(m -> m.addVetoableChangeListener(l));
446    }
447
448    /** {@inheritDoc} */
449    @Override
450    @OverridingMethodsMustInvokeSuper
451    public synchronized void removeVetoableChangeListener(VetoableChangeListener l) {
452        super.removeVetoableChangeListener(l);
453        mgrs.forEach(m -> m.removeVetoableChangeListener(l));
454    }
455
456    /** {@inheritDoc} */
457    @Override
458    @OverridingMethodsMustInvokeSuper
459    public void addVetoableChangeListener(String propertyName, VetoableChangeListener listener) {
460        super.addVetoableChangeListener(propertyName, listener);
461        mgrs.forEach(m -> m.addVetoableChangeListener(propertyName, listener));
462    }
463
464    /** {@inheritDoc} */
465    @Override
466    @OverridingMethodsMustInvokeSuper
467    public void removeVetoableChangeListener(String propertyName, VetoableChangeListener listener) {
468        super.removeVetoableChangeListener(propertyName, listener);
469        mgrs.forEach(m -> m.removeVetoableChangeListener(propertyName, listener));
470    }
471
472    /**
473     * {@inheritDoc}
474     */
475    @Override
476    public void propertyChange(PropertyChangeEvent event) {
477        if (event.getPropertyName().equals("beans")) {
478            // A single added or removed bean has already been handled by
479            // intervalAdded or intervalRemoved; only an event without a bean
480            // needs the whole set rebuilt.
481            if (event.getOldValue() == null && event.getNewValue() == null) {
482                recomputeNamedBeanSet();
483            }
484            // Listeners added to this proxy are also added to each manager,
485            // so they have already received this event from the manager.
486            return;
487        }
488        event.setPropagationId(this);
489        if (!silencedProperties.getOrDefault(event.getPropertyName(), false)) {
490            firePropertyChange(event);
491        }
492    }
493
494    /**
495     * {@inheritDoc}
496     *
497     * @return The system connection memo for the manager returned by
498     *         {@link #getDefaultManager()}, or the Internal system connection
499     *         memo if there is no default manager
500     */
501    @Override
502    @Nonnull
503    public SystemConnectionMemo getMemo() {
504        try {
505            return getDefaultManager().getMemo();
506        } catch (IndexOutOfBoundsException ex) {
507            return InstanceManager.getDefault(InternalSystemConnectionMemo.class);
508        }
509    }
510
511    /**
512     * @return The system-specific prefix letter for the primary implementation
513     */
514    @Override
515    @Nonnull
516    public String getSystemPrefix() {
517        try {
518            return getDefaultManager().getSystemPrefix();
519        } catch (IndexOutOfBoundsException ie) {
520            return "?";
521        }
522    }
523
524    /**
525     * @return The type letter for for the primary implementation
526     */
527    @Override
528    public char typeLetter() {
529        return getDefaultManager().typeLetter();
530    }
531
532    /**
533     * {@inheritDoc}
534     */
535    @Override
536    @Nonnull
537    public String makeSystemName(@Nonnull String s) {
538        return getDefaultManager().makeSystemName(s);
539    }
540
541    /** {@inheritDoc} */
542    @CheckReturnValue
543    @Override
544    public int getObjectCount() {
545        return mgrs.stream().map(m -> m.getObjectCount()).reduce(0, Integer::sum);
546    }
547
548    private TreeSet<E> namedBeanSet = null;
549    protected void recomputeNamedBeanSet() {
550        if (namedBeanSet != null) { // only maintain if requested
551            namedBeanSet.clear();
552            mgrs.forEach(m -> namedBeanSet.addAll(m.getNamedBeanSet()));
553        }
554    }
555
556    /** {@inheritDoc} */
557    @Override
558    @Nonnull
559    public SortedSet<E> getNamedBeanSet() {
560        if (namedBeanSet == null) {
561            namedBeanSet = new TreeSet<>(new NamedBeanComparator<>());
562            recomputeNamedBeanSet();
563        }
564        return Collections.unmodifiableSortedSet(namedBeanSet);
565    }
566
567    /**
568     * {@inheritDoc}
569     */
570    @Override
571    @OverridingMethodsMustInvokeSuper
572    public void setPropertyChangesSilenced(String propertyName, boolean silenced) {
573        // since AbstractProxyManager has no explicit constructors, acccept
574        // "beans" as well as anything needed to be accepted by subclasses
575        if (!"beans".equals(propertyName) && !silenceableProperties.contains(propertyName)) {
576            throw new IllegalArgumentException("Property " + propertyName + " cannot be silenced.");
577        }
578        silencedProperties.put(propertyName, silenced);
579        if (propertyName.equals("beans") && !silenced) {
580            fireIndexedPropertyChange("beans", getNamedBeanSet().size(), null, null);
581        }
582    }
583
584    /** {@inheritDoc} */
585    @Override
586    public void addDataListener(ManagerDataListener<E> e) {
587        if (e != null) listeners.add(e);
588    }
589
590    /** {@inheritDoc} */
591    @Override
592    public void removeDataListener(ManagerDataListener<E> e) {
593        if (e != null) listeners.remove(e);
594    }
595
596    final List<ManagerDataListener<E>> listeners = new ArrayList<>();
597
598    /**
599     * {@inheritDoc}
600     * From Manager.ManagerDataListener, receives notifications from underlying
601     * managers.
602     */
603    @Override
604    public void contentsChanged(Manager.ManagerDataEvent<E> e) {
605        recomputeNamedBeanSet();
606    }
607
608    /**
609     * {@inheritDoc}
610     * From Manager.ManagerDataListener, receives notifications from underlying
611     * managers.
612     */
613    @Override
614    public void intervalAdded(AbstractProxyManager.ManagerDataEvent<E> e) {
615        if (namedBeanSet != null && e.getIndex0() == e.getIndex1()) {
616            // just one element added, and we have the object reference
617            namedBeanSet.add(e.getChangedBean());
618        } else {
619            recomputeNamedBeanSet();
620        }
621
622        if (muted) return;
623
624        int offset = 0;
625        for (Manager<E> m : mgrs) {
626            if (m == e.getSource()) break;
627            offset += m.getObjectCount();
628        }
629
630        ManagerDataEvent<E> eOut = new ManagerDataEvent<>(this, Manager.ManagerDataEvent.INTERVAL_ADDED, e.getIndex0()+offset, e.getIndex1()+offset, e.getChangedBean());
631
632        listeners.forEach(m -> m.intervalAdded(eOut));
633    }
634
635    /**
636     * {@inheritDoc}
637     * From Manager.ManagerDataListener, receives notifications from underlying
638     * managers.
639     */
640    @Override
641    public void intervalRemoved(AbstractProxyManager.ManagerDataEvent<E> e) {
642        if (namedBeanSet != null && e.getIndex0() == e.getIndex1()) {
643            // just one element removed, and we have the object reference
644            namedBeanSet.remove(e.getChangedBean());
645        } else {
646            recomputeNamedBeanSet();
647        }
648
649        if (muted) return;
650
651        int offset = 0;
652        for (Manager<E> m : mgrs) {
653            if (m == e.getSource()) break;
654            offset += m.getObjectCount();
655        }
656
657        ManagerDataEvent<E> eOut = new ManagerDataEvent<>(this, Manager.ManagerDataEvent.INTERVAL_REMOVED, e.getIndex0()+offset, e.getIndex1()+offset, e.getChangedBean());
658
659        listeners.forEach(m -> m.intervalRemoved(eOut));
660    }
661
662    private boolean muted = false;
663    /** {@inheritDoc} */
664    @Override
665    public void setDataListenerMute(boolean m) {
666        if (muted && !m) {
667            // send a total update, as we haven't kept track of specifics
668            ManagerDataEvent<E> e = new ManagerDataEvent<>(this, ManagerDataEvent.CONTENTS_CHANGED, 0, getObjectCount()-1, null);
669            listeners.forEach((listener) -> listener.contentsChanged(e));
670        }
671        this.muted = m;
672    }
673
674    // initialize logging
675    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractProxyManager.class);
676
677}