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