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