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}