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}