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