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