001package jmri.jmrix; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006import javax.annotation.Nonnull; 007import javax.annotation.concurrent.GuardedBy; 008 009import jmri.BasicRosterEntry; 010import jmri.DccLocoAddress; 011import jmri.DccThrottle; 012import jmri.LocoAddress; 013import jmri.SpeedStepMode; 014import jmri.SystemConnectionMemo; 015import jmri.Throttle; 016import jmri.ThrottleListener; 017import jmri.ThrottleManager; 018import jmri.util.swing.JmriJOptionPane; 019 020/** 021 * Abstract implementation of a ThrottleManager. 022 * <p> 023 * Based on Glen Oberhauser's original {@link jmri.jmrix.loconet.LnThrottleManager} implementation. 024 * 025 * @author Bob Jacobsen Copyright (C) 2001 026 * @author Steve Rawlinson Copyright (C) 2016 027 */ 028abstract public class AbstractThrottleManager implements ThrottleManager { 029 030 public AbstractThrottleManager() { 031 } 032 033 public AbstractThrottleManager(SystemConnectionMemo memo) { 034 adapterMemo = memo; 035 } 036 037 protected SystemConnectionMemo adapterMemo; 038 039 protected String userName = "Internal"; 040 041 /** 042 * {@inheritDoc} 043 */ 044 @Override 045 public String getUserName() { 046 if (adapterMemo != null) { 047 return adapterMemo.getUserName(); 048 } 049 return userName; 050 } 051 052 /** 053 * By default, only DCC in this implementation 054 */ 055 @Override 056 public String[] getAddressTypes() { 057 return new String[]{ 058 LocoAddress.Protocol.DCC.getPeopleName(), 059 LocoAddress.Protocol.DCC_SHORT.getPeopleName(), 060 LocoAddress.Protocol.DCC_LONG.getPeopleName()}; 061 } 062 063 /** 064 * By default, only DCC in this implementation 065 */ 066 @Override 067 public String getAddressTypeString(LocoAddress.Protocol prot) { 068 return prot.getPeopleName(); 069 } 070 071 /** 072 * {@inheritDoc} 073 */ 074 @Override 075 public LocoAddress.Protocol[] getAddressProtocolTypes() { 076 return new LocoAddress.Protocol[]{ 077 LocoAddress.Protocol.DCC, 078 LocoAddress.Protocol.DCC_SHORT, 079 LocoAddress.Protocol.DCC_LONG}; 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public LocoAddress getAddress(String value, LocoAddress.Protocol protocol) { 087 if (value == null) { 088 return null; 089 } 090 if (protocol == null) { 091 return null; 092 } 093 int num = Integer.parseInt(value); 094 095 // if DCC long and can't be, or short and can't be, fix 096 if ((LocoAddress.Protocol.DCC == protocol || LocoAddress.Protocol.DCC_SHORT == protocol) && !canBeShortAddress(num)) { 097 protocol = LocoAddress.Protocol.DCC_LONG; 098 } 099 if ((LocoAddress.Protocol.DCC == protocol || LocoAddress.Protocol.DCC_LONG == protocol) && !canBeLongAddress(num)) { 100 protocol = LocoAddress.Protocol.DCC_SHORT; 101 } 102 103 // if still ambiguous, prefer short 104 if (protocol == LocoAddress.Protocol.DCC) { 105 protocol = LocoAddress.Protocol.DCC_SHORT; 106 } 107 108 return new DccLocoAddress(num, protocol); 109 } 110 111 /** 112 * {@inheritDoc} 113 */ 114 @Override 115 public LocoAddress getAddress(String value, String protocol) { 116 if (value == null) { 117 return null; 118 } 119 if (protocol == null) { 120 return null; 121 } 122 LocoAddress.Protocol p = getProtocolFromString(protocol); 123 124 return getAddress(value, p); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public LocoAddress.Protocol getProtocolFromString(String selection) { 132 return LocoAddress.Protocol.getByPeopleName(selection); 133 } 134 135 /** 136 * throttleListeners is indexed by the address, and contains as elements an 137 * ArrayList of WaitingThrottle objects, each of which has one ThrottleListener. 138 * This allows more than one ThrottleListener to request a throttle at a time. 139 * The entries in this Hashmap are only valid during the throttle setup process. 140 */ 141 @GuardedBy("this") 142 private final HashMap<LocoAddress, ArrayList<WaitingThrottle>> throttleListeners = new HashMap<>(5); 143 144 static class WaitingThrottle { 145 146 ThrottleListener l; 147 BasicRosterEntry re; 148 PropertyChangeListener pl; 149 boolean canHandleDecisions; 150 151 WaitingThrottle(ThrottleListener _l, BasicRosterEntry _re, boolean _canHandleDecisions) { 152 l = _l; 153 re = _re; 154 canHandleDecisions = _canHandleDecisions; 155 } 156 157 WaitingThrottle(PropertyChangeListener _pl, BasicRosterEntry _re, boolean _canHandleDecisions) { 158 pl = _pl; 159 re = _re; 160 canHandleDecisions = _canHandleDecisions; 161 } 162 163 PropertyChangeListener getPropertyChangeListener() { 164 return pl; 165 } 166 167 ThrottleListener getListener() { 168 return l; 169 } 170 171 BasicRosterEntry getRosterEntry() { 172 return re; 173 } 174 175 boolean canHandleDecisions() { 176 return canHandleDecisions; 177 } 178 179 } 180 181 /** 182 * listenerOnly is indexed by the address, and contains as elements an 183 * ArrayList of propertyChangeListeners objects that have requested 184 * notification of changes to a throttle that hasn't yet been created. The 185 * entries in this Hashmap are only valid during the throttle setup process. 186 */ 187 @GuardedBy("this") 188 private final HashMap<LocoAddress, ArrayList<WaitingThrottle>> listenerOnly = new HashMap<>(5); 189 190 /** 191 * Keeps a map of all the current active DCC loco Addresses that are in use. 192 * <p> 193 * addressThrottles is indexed by the address, and contains as elements a 194 * subclass of the throttle assigned to an address and the number of 195 * requests and active users for this address. 196 */ 197 @GuardedBy("this") 198 private final Hashtable<LocoAddress, Addresses> addressThrottles = new Hashtable<>(); 199 200 /** 201 * Does this DCC system allow a Throttle (e.g. an address) to be used by 202 * only one user at a time? 203 * @return true or false 204 */ 205 protected boolean singleUse() { 206 return true; 207 } 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override 213 public boolean requestThrottle(int address, boolean isLongAddress, ThrottleListener l, boolean canHandleDecisions) { 214 DccLocoAddress la = new DccLocoAddress(address, isLongAddress); 215 return requestThrottle(la, null, l, canHandleDecisions); 216 } 217 218 /** 219 * {@inheritDoc} 220 */ 221 @Override 222 public boolean requestThrottle(@Nonnull BasicRosterEntry re, ThrottleListener l, boolean canHandleDecisions) { 223 return requestThrottle(re.getDccLocoAddress(), re, l, canHandleDecisions); 224 } 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public boolean requestThrottle(LocoAddress la, ThrottleListener l, boolean canHandleDecisions) { 231 return requestThrottle(la, null, l, canHandleDecisions); 232 } 233 234 /** 235 * Request a throttle, given a decoder address. 236 * <p> 237 * When the decoder address is 238 * located, the ThrottleListener gets a callback via the 239 * ThrottleListener.notifyThrottleFound method. 240 * 241 * @param la LocoAddress of the decoder desired. 242 * @param l The ThrottleListener awaiting notification of a found throttle. 243 * @param re A BasicRosterEntry can be passed, this is attached to a throttle after creation. 244 * @param canHandleDecisions true if theThrottleListener can make a steal or share decision, else false. 245 * @return True if the request will continue, false if the request will not 246 * be made. False may be returned if a the throttle is already in 247 * use. 248 */ 249 protected synchronized boolean requestThrottle(LocoAddress la, BasicRosterEntry re, ThrottleListener l, boolean canHandleDecisions) { 250 boolean throttleFree = true; 251 252 // check for a valid throttle address 253 if (!canBeLongAddress(la.getNumber()) && !canBeShortAddress(la.getNumber())) { 254 return false; 255 } 256 257 // put the list in if not present 258 ArrayList<WaitingThrottle> a; 259 if (!throttleListeners.containsKey(la)) { 260 throttleListeners.put(la, new ArrayList<>()); 261 } 262 // get the corresponding list to check length 263 a = throttleListeners.get(la); 264 if (addressThrottles.containsKey(la)) { 265 log.debug("A throttle to address {} already exists, so will return that throttle", la.getNumber()); 266 a.add(new WaitingThrottle(l, re, canHandleDecisions)); 267 notifyThrottleKnown(addressThrottles.get(la).getThrottle(), la); 268 return throttleFree; 269 } else { 270 log.debug("LocoAddress {} has not been created before", la.getNumber()); 271 } 272 273 log.debug("After request in ATM: {}", a.size()); 274 275 // check length 276 if (singleUse() && (a.size() > 0)) { 277 throttleFree = false; 278 log.debug("singleUser() is true, and the list of WaitingThrottles isn't empty, returning false"); 279 } else if (a.size() == 0) { 280 a.add(new WaitingThrottle(l, re, canHandleDecisions)); 281 log.debug("list of WaitingThrottles is empty: {}; {}", la, a); 282 log.debug("calling requestThrottleSetup()"); 283 requestThrottleSetup(la, true); 284 } else { 285 a.add(new WaitingThrottle(l, re, canHandleDecisions)); 286 log.debug("singleUse() returns false and there are existing WaitThrottles, adding a one to the list"); 287 } 288 return throttleFree; 289 } 290 291 /** 292 * Request Throttle with no Steal / Share Callbacks 293 * {@inheritDoc} 294 * Request a throttle, given a decoder address. When the decoder address is 295 * located, the ThrottleListener gets a callback via the 296 * ThrottleListener.notifyThrottleFound method. 297 * <p> 298 * This is a convenience version of the call, which uses system-specific 299 * logic to tell whether the address is a short or long form. 300 * 301 * @param address The decoder address desired. 302 * @param l The ThrottleListener awaiting notification of a found 303 * throttle. 304 * @return True if the request will continue, false if the request will not 305 * be made. False may be returned if a the throttle is already in 306 * use. 307 */ 308 @Override 309 public boolean requestThrottle(int address, ThrottleListener l) { 310 boolean isLong = true; 311 if (canBeShortAddress(address)) { 312 isLong = false; 313 } 314 return requestThrottle(new DccLocoAddress(address, isLong), null, l, false); 315 } 316 317 /** 318 * {@inheritDoc} 319 */ 320 @Override 321 public boolean requestThrottle(int address, ThrottleListener l, boolean canHandleDecisions) { 322 boolean isLong = true; 323 if (canBeShortAddress(address)) { 324 isLong = false; 325 } 326 return requestThrottle(new DccLocoAddress(address, isLong), null, l, canHandleDecisions); 327 } 328 329 /** 330 * Abstract member to actually do the work of configuring a new throttle, 331 * usually via interaction with the DCC system. 332 * @param a address 333 * @param control false - read only. 334 */ 335 abstract public void requestThrottleSetup(LocoAddress a, boolean control); 336 337 /** 338 * Abstract member to actually do the work of configuring a new throttle, 339 * usually via interaction with the DCC system 340 * @param a address. 341 */ 342 public void requestThrottleSetup(LocoAddress a) { 343 requestThrottleSetup(a, true); 344 } 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override 350 public void cancelThrottleRequest(int address, boolean isLong, ThrottleListener l) { 351 DccLocoAddress la = new DccLocoAddress(address, isLong); 352 cancelThrottleRequest(la, l); 353 } 354 355 /** 356 * {@inheritDoc} 357 */ 358 @Override 359 public void cancelThrottleRequest(BasicRosterEntry re, ThrottleListener l) { 360 cancelThrottleRequest(re.getDccLocoAddress(), l); 361 } 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override 367 public synchronized void cancelThrottleRequest(LocoAddress la, ThrottleListener l) { 368 // failedThrottleRequest(la, "Throttle request was cancelled."); // needs I18N 369 ArrayList<WaitingThrottle> a = throttleListeners.get(la); 370 if (a == null || l == null ) { 371 return; 372 } 373 a.removeIf(wt -> l == wt.getListener()); // Safely remove the current element from the iterator and the list 374 } 375 376 /** 377 * {@inheritDoc} 378 * Cancel a request for a throttle. 379 * <p> 380 * This is a convenience version of the call, which uses system-specific 381 * logic to tell whether the address is a short or long form. 382 * 383 * @param address The decoder address desired. 384 * @param l The ThrottleListener cancelling request for a throttle. 385 */ 386 @Override 387 public void cancelThrottleRequest(int address, ThrottleListener l) { 388 boolean isLong = true; 389 if (canBeShortAddress(address)) { 390 isLong = false; 391 } 392 cancelThrottleRequest(address, isLong, l); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public void responseThrottleDecision(int address, ThrottleListener l, ThrottleListener.DecisionType decision) { 400 boolean isLong = true; 401 if (canBeShortAddress(address)) { 402 isLong = false; 403 } 404 responseThrottleDecision(address, isLong, l, decision); 405 406 } 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override 412 public void responseThrottleDecision(int address, boolean isLong, ThrottleListener l, ThrottleListener.DecisionType decision) { 413 DccLocoAddress la = new DccLocoAddress(address, isLong); 414 responseThrottleDecision(la, l, decision); 415 } 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override 421 public void responseThrottleDecision(LocoAddress address, ThrottleListener l, ThrottleListener.DecisionType decision) { 422 log.debug("Received response from ThrottleListener, this method should be overridden by a hardware type"); 423 } 424 425 /** 426 * If the system-specific ThrottleManager has been unable to create the DCC 427 * throttle then it needs to be removed from the throttleListeners, 428 * otherwise any subsequent request for that address results in the address 429 * being reported as already in use, if singleUse is set. This also sends a 430 * notification message back to the requestor with a string reason as to why 431 * the request has failed. 432 * 433 * @param address The Loco Address that the request failed on. 434 * @param reason A text string passed by the ThrottleManager as to why 435 */ 436 public synchronized void failedThrottleRequest(LocoAddress address, String reason) { 437 ArrayList<WaitingThrottle> a = throttleListeners.get(address); 438 if (a == null) { 439 log.warn("failedThrottleRequest with zero-length listeners: {}", address); 440 } else { 441 for (WaitingThrottle waitingThrottle : new ArrayList<>(a)) { 442 ThrottleListener l = waitingThrottle.getListener(); 443 l.notifyFailedThrottleRequest(address, reason); 444 } 445 } 446 throttleListeners.remove(address); 447 ArrayList<WaitingThrottle> p = listenerOnly.get(address); 448 if (p == null) { 449 log.debug("failedThrottleRequest with zero-length PropertyChange listeners: {}", address); 450 } else { 451 for (WaitingThrottle waitingThrottle : p) { 452 PropertyChangeListener l = waitingThrottle.getPropertyChangeListener(); 453 l.propertyChange(new PropertyChangeEvent(this, "attachFailed", address, null)); 454 } 455 } 456 listenerOnly.remove(address); 457 } 458 459 /** 460 * Handle throttle information when it's finally available, e.g. when a new 461 * Throttle object has been created. 462 * <p> 463 * This method creates a throttle for all ThrottleListeners of that address 464 * and notifies them via the ThrottleListener.notifyThrottleFound method. 465 * @param throttle throttle object 466 * @param addr address. 467 */ 468 public synchronized void notifyThrottleKnown(DccThrottle throttle, LocoAddress addr) { 469 log.debug("notifyThrottleKnown for {}", addr); 470 Addresses ads = null; 471 if (!addressThrottles.containsKey(addr)) { 472 log.debug("Address {} doesn't already exists so will add", addr); 473 ads = new Addresses(throttle); 474 addressThrottles.put(addr, ads); 475 } else { 476 addressThrottles.get(addr).setThrottle(throttle); 477 } 478 ArrayList<WaitingThrottle> a = throttleListeners.get(addr); 479 if (a == null) { 480 log.debug("notifyThrottleKnown with zero-length listeners: {}", addr); 481 } else { 482 for (int i = 0; i < a.size(); i++) { 483 ThrottleListener l = a.get(i).getListener(); 484 log.debug("Notify listener {} of {}", (i + 1), a.size() ); 485 l.notifyThrottleFound(throttle); 486 addressThrottles.get(addr).incrementUse(); 487 addressThrottles.get(addr).addListener(l); 488 if (ads != null && a.get(i).getRosterEntry() != null && throttle.getRosterEntry() == null) { 489 throttle.setRosterEntry(a.get(i).getRosterEntry()); 490 } 491 updateNumUsers(addr, addressThrottles.get(addr).getUseCount()); 492 } 493 throttleListeners.remove(addr); 494 } 495 ArrayList<WaitingThrottle> p = listenerOnly.get(addr); 496 if (p == null) { 497 log.debug("notifyThrottleKnown with zero-length propertyChangeListeners: {}", addr); 498 } else { 499 for (WaitingThrottle waitingThrottle : p) { 500 PropertyChangeListener l = waitingThrottle.getPropertyChangeListener(); 501 log.debug("Notify propertyChangeListener"); 502 l.propertyChange(new PropertyChangeEvent(this, "throttleAssigned", null, addr)); 503 if (ads != null && waitingThrottle.getRosterEntry() != null && throttle.getRosterEntry() == null) { 504 throttle.setRosterEntry(waitingThrottle.getRosterEntry()); 505 } 506 throttle.addPropertyChangeListener(l); 507 } 508 listenerOnly.remove(addr); 509 } 510 } 511 512 513 /** 514 * For when a steal / share decision is needed and the ThrottleListener has delegated 515 * this decision to the ThrottleManager. 516 * <p> 517 * Responds to the question by requesting a Throttle "Steal" by default. 518 * <p> 519 * Can be overridden by hardware types which do not wish the default behaviour to Steal. 520 * <p> 521 * This applies only to those systems where "stealing" or "sharing" applies, such as LocoNet. 522 * @param address The LocoAddress the steal / share question relates to 523 * @param question The Question to be put to the ThrottleListener 524 */ 525 protected void makeHardwareDecision(LocoAddress address, ThrottleListener.DecisionType question){ 526 responseThrottleDecision(address, null, ThrottleListener.DecisionType.STEAL ); 527 } 528 529 /** 530 * When the system-specific ThrottleManager has been unable to create the DCC 531 * throttle because it is already in use and must be "stolen" or "shared" to take control, 532 * it needs to notify the listener of this situation. 533 * <p> 534 * This applies only to those systems where "stealing" or "sharing" applies, such as LocoNet. 535 * 536 * @param address The LocoAddress the steal / share question relates to 537 * @param question The Question to be put to the ThrottleListener 538 */ 539 protected synchronized void notifyDecisionRequest(LocoAddress address, ThrottleListener.DecisionType question) { 540 ArrayList<WaitingThrottle> a = throttleListeners.get(address); 541 if (a == null) { 542 log.debug("Cannot issue question. No throttle listeners registered for address {}", address.getNumber()); 543 return; 544 } 545 ThrottleListener l; 546 log.debug("{} listener(s) registered for address {}", a.size(), address.getNumber()); 547 for (int i = 0; i < a.size(); i++) { // enhanced for (WaitingThrottle waitingThrottle : a) doesn't work somehow 548 if (a.get(i).canHandleDecisions()) { 549 l = a.get(i).getListener(); 550 log.debug("Notifying a throttle listener (address {}) of the steal share situation", address.getNumber()); 551 l.notifyDecisionRequired(address, question); 552 } else { 553 log.debug("Passing {} to hardware steal / share decision making", address.getNumber()); 554 makeHardwareDecision(address, question); 555 } 556 } 557 } 558 559 /** 560 * Check to see if the Dispatch Button should be enabled or not Default to 561 * true, override if necessary 562 * 563 */ 564 @Override 565 public boolean hasDispatchFunction() { 566 return true; 567 } 568 569 /** 570 * What speed modes are supported by this system? value should be xor of 571 * possible modes specifed by the DccThrottle interface 572 */ 573 @Override 574 public EnumSet<SpeedStepMode> supportedSpeedModes() { 575 return EnumSet.of(SpeedStepMode.NMRA_DCC_128); 576 } 577 578 /** 579 * Hardware that uses the Silent Steal preference 580 * will need to override 581 * {@inheritDoc} 582 */ 583 @Override 584 public boolean enablePrefSilentStealOption() { 585 return false; 586 } 587 588 /** 589 * Hardware that uses the Silent Share preference 590 * will need to override 591 * {@inheritDoc} 592 */ 593 @Override 594 public boolean enablePrefSilentShareOption() { 595 return false; 596 } 597 598 /** 599 * {@inheritDoc} 600 */ 601 @Override 602 public synchronized void attachListener(LocoAddress la, java.beans.PropertyChangeListener p) { 603 if (addressThrottles.containsKey(la)) { 604 addressThrottles.get(la).getThrottle().addPropertyChangeListener(p); 605 p.propertyChange(new PropertyChangeEvent(this, "throttleAssigned", null, la)); 606 } else { 607 if (!listenerOnly.containsKey(la)) { 608 listenerOnly.put(la, new ArrayList<>()); 609 } 610 611 // get the corresponding list to check length 612 ArrayList<WaitingThrottle> a = listenerOnly.get(la); 613 a.add(new WaitingThrottle(p, null, false)); 614 //Only request that the throttle is set up if it hasn't already been 615 //requested. 616 if ((!throttleListeners.containsKey(la)) && (a.size() == 1)) { 617 requestThrottleSetup(la, false); 618 } 619 } 620 } 621 622 /** 623 * {@inheritDoc} 624 */ 625 @Override 626 public synchronized void removeListener(LocoAddress la, java.beans.PropertyChangeListener p) { 627 if (addressThrottles.containsKey(la)) { 628 addressThrottles.get(la).getThrottle().removePropertyChangeListener(p); 629 p.propertyChange(new PropertyChangeEvent(this, "throttleRemoved", la, null)); 630 return; 631 } 632 p.propertyChange(new PropertyChangeEvent(this, "throttleNotFoundInRemoval", la, null)); 633 } 634 635 /** 636 * {@inheritDoc} 637 */ 638 @Override 639 public synchronized boolean addressStillRequired(LocoAddress la) { 640 if (addressThrottles.containsKey(la)) { 641 log.debug("usage count is {}", addressThrottles.get(la).getUseCount()); 642 return (addressThrottles.get(la).getUseCount() > 0); 643 } 644 return false; 645 } 646 647 /** 648 * {@inheritDoc} 649 */ 650 @Override 651 public boolean addressStillRequired(int address, boolean isLongAddress) { 652 DccLocoAddress la = new DccLocoAddress(address, isLongAddress); 653 return addressStillRequired(la); 654 } 655 656 /** 657 * {@inheritDoc} 658 */ 659 @Override 660 public boolean addressStillRequired(int address) { 661 boolean isLong = true; 662 if (canBeShortAddress(address)) { 663 isLong = false; 664 } 665 return addressStillRequired(address, isLong); 666 } 667 668 /** 669 * {@inheritDoc} 670 */ 671 @Override 672 public boolean addressStillRequired(BasicRosterEntry re) { 673 return addressStillRequired(re.getDccLocoAddress()); 674 } 675 676 /** 677 * {@inheritDoc} 678 */ 679 @Override 680 public void releaseThrottle(DccThrottle t, ThrottleListener l) { 681 log.debug("AbstractThrottleManager.releaseThrottle: {}, {}", t, l); 682 disposeThrottle(t, l); 683 } 684 685 /** 686 * {@inheritDoc} 687 */ 688 @Override 689 public boolean disposeThrottle(DccThrottle t, ThrottleListener l) { 690 log.debug("AbstractThrottleManager.disposeThrottle: {}, {}", t, l); 691 692// if (!active) log.error("Dispose called when not active"); <-- might need to control this in the sub class 693 LocoAddress la = t.getLocoAddress(); 694 if (addressReleased(la, l)) { 695 log.debug("Address {} still has active users", t.getLocoAddress()); 696 return false; 697 } 698 if (t.getPropertyChangeListeners().length > 0) { 699 log.debug("Throttle {} still has {} active propertyChangeListeners registered to the throttle", t.getLocoAddress(), t.getPropertyChangeListeners().length); 700 return false; 701 } 702 synchronized (this) { 703 if (addressThrottles.containsKey(la)) { 704 addressThrottles.remove(la); 705 log.debug("Loco Address {} removed from the stack ", la); 706 } else { 707 log.debug("Loco Address {} not found in the stack ", la); 708 } 709 } 710 return true; 711 } 712 713 /** 714 * Throttle can no longer be relied upon, 715 * potentially from an external forced steal or hardware error. 716 * <p> 717 * Normally, #releaseThrottle should be used to close throttles. 718 * <p> 719 * Removes locoaddress from list to force new throttle requests 720 * to request new sessions where the Command station model 721 * implements a dynamic stack, not a static stack. 722 * 723 * <p> 724 * Managers still need to advise listeners that the session has 725 * been cancelled and actually dispose of the throttle 726 * @param la address release 727 */ 728 protected void forceDisposeThrottle(LocoAddress la) { 729 log.debug("force dispose address {}", la); 730 if (addressThrottles.containsKey(la)) { 731 addressThrottles.remove(la); 732 log.debug("Loco Address {} removed from the stack ", la); 733 } else { 734 log.debug("Loco Address {} not found in the stack ", la); 735 } 736 } 737 738 /** 739 * {@inheritDoc} 740 */ 741 @Override 742 public void dispatchThrottle(DccThrottle t, ThrottleListener l) { 743 releaseThrottle(t, l); 744 } 745 746 /** 747 * {@inheritDoc} 748 */ 749 @Override 750 public void dispose() { 751 } 752 753 /** 754 * {@inheritDoc} 755 */ 756 @Override 757 public synchronized int getThrottleUsageCount(LocoAddress la) { 758 if (addressThrottles.containsKey( la)) { 759 return addressThrottles.get(la).getUseCount(); 760 } 761 return 0; 762 } 763 764 /** 765 * {@inheritDoc} 766 */ 767 @Override 768 public int getThrottleUsageCount(int address, boolean isLongAddress) { 769 DccLocoAddress la = new DccLocoAddress(address, isLongAddress); 770 return getThrottleUsageCount(la); 771 } 772 773 /** 774 * {@inheritDoc} 775 */ 776 @Override 777 public int getThrottleUsageCount(int address) { 778 boolean isLong = true; 779 if (canBeShortAddress(address)) { 780 isLong = false; 781 } 782 return getThrottleUsageCount(address, isLong); 783 } 784 785 /** 786 * {@inheritDoc} 787 */ 788 @Override 789 public int getThrottleUsageCount(BasicRosterEntry re) { 790 return getThrottleUsageCount(re.getDccLocoAddress()); 791 } 792 793 /** 794 * Release a Throttle from a ThrottleListener. 795 * @param la address release 796 * @param l listening object 797 * @return True if throttle still has listeners or a positive use count, else False 798 */ 799 protected synchronized boolean addressReleased(LocoAddress la, ThrottleListener l) { 800 if (addressThrottles.containsKey(la)) { 801 if (addressThrottles.get(la).containsListener(l)) { 802 log.debug("decrementUse called with listener {}", l); 803 addressThrottles.get(la).decrementUse(); 804 addressThrottles.get(la).removeListener(l); 805 } else if (l == null) { 806 log.debug("decrementUse called withOUT listener"); 807 /*The release release has been called, but as no listener has 808 been specified, we can only decrement the use flag*/ 809 addressThrottles.get(la).decrementUse(); 810 } 811 } 812 if (addressThrottles.containsKey(la)) { 813 if (addressThrottles.get(la).getUseCount() > 0) { 814 updateNumUsers(la, addressThrottles.get(la).getUseCount()); 815 log.debug("addressReleased still has at least one listener"); 816 return true; 817 } 818 } 819 return false; 820 } 821 822 /** 823 * The number of users of this throttle has been updated 824 * <p> 825 * Typically used to update dispatch / release availablility 826 * specific implementations can override this function to get updates 827 * 828 * @param la the Loco Address which has been updated 829 * @param numUsers current number of users 830 */ 831 protected void updateNumUsers( LocoAddress la, int numUsers ){ 832 log.debug("Throttle {} now has {} users", la, numUsers); 833 } 834 835 /** 836 * {@inheritDoc} 837 */ 838 @Override 839 public Object getThrottleInfo(LocoAddress la, String item) { 840 DccThrottle t; 841 synchronized (this) { 842 if (addressThrottles.containsKey(la)) { 843 t = addressThrottles.get(la).getThrottle(); 844 } else { 845 return null; 846 } 847 } 848 if (item.equals(Throttle.ISFORWARD)) { 849 return t.getIsForward(); 850 } else if (item.startsWith("Speed")) { 851 switch (item) { 852 case Throttle.SPEEDSETTING: 853 return t.getSpeedSetting(); 854 case Throttle.SPEEDINCREMENT: 855 return t.getSpeedIncrement(); 856 case Throttle.SPEEDSTEPMODE: 857 return t.getSpeedStepMode(); 858 default: // skip 859 } 860 } 861 for ( int i = 0; i< t.getFunctions().length; i++ ) { 862 if (item.equals(Throttle.getFunctionString(i))) { 863 return t.getFunction(i); 864 } 865 } 866 return null; 867 } 868 869 private boolean _hideStealNotifications = false; 870 871 /** 872 * If not headless, display a session stolen dialogue box with 873 * checkbox to hide notifications for rest of JMRI session 874 * 875 * @param address the LocoAddress of the stolen / cancelled Throttle 876 */ 877 protected void showSessionCancelDialogue(LocoAddress address){ 878 if ((!java.awt.GraphicsEnvironment.isHeadless()) && (!_hideStealNotifications)){ 879 jmri.util.ThreadingUtil.runOnGUI(() -> { 880 javax.swing.JCheckBox checkbox = new javax.swing.JCheckBox( 881 Bundle.getMessage("HideFurtherAlerts")); 882 Object[] params = {Bundle.getMessage("LocoStolen", address), checkbox}; 883 java.awt.event.ActionListener stolenpopupcheckbox = (java.awt.event.ActionEvent evt) -> 884 this.hideStealNotifications(checkbox.isSelected()); 885 checkbox.addActionListener(stolenpopupcheckbox); 886 JmriJOptionPane.showMessageDialogNonModal(null, params, 887 Bundle.getMessage("LocoStolen", address), 888 JmriJOptionPane.WARNING_MESSAGE, null); 889 }); 890 } 891 } 892 893 /** 894 * Receive notification from a throttle dialogue 895 * to display steal dialogues for rest of the JMRI instance session. 896 * False by default to show notifications 897 * 898 * @param hide set True to hide notifications, else False. 899 */ 900 public void hideStealNotifications(boolean hide){ 901 _hideStealNotifications = hide; 902 } 903 904 /** 905 * This subClass keeps track of which loco address have been requested and 906 * by whom. It primarily uses an increment count to keep track of all the 907 * Addresses in use as not all external code will have been refactored over 908 * to use the new disposeThrottle. 909 */ 910 protected static class Addresses { 911 912 int useActiveCount = 0; 913 DccThrottle throttle; 914 ArrayList<ThrottleListener> listeners = new ArrayList<>(); 915 BasicRosterEntry re = null; 916 917 protected Addresses(DccThrottle throttle) { 918 this.throttle = throttle; 919 } 920 921 void incrementUse() { 922 useActiveCount++; 923 log.debug("{} increased Use Size to {}", throttle.getLocoAddress(), useActiveCount); 924 } 925 926 void decrementUse() { 927 // Do not want to go below 0 on the usage front! 928 if (useActiveCount > 0) { 929 useActiveCount--; 930 } 931 log.debug("{} decreased Use Size to {}", throttle.getLocoAddress(), useActiveCount); 932 } 933 934 int getUseCount() { 935 return useActiveCount; 936 } 937 938 DccThrottle getThrottle() { 939 return throttle; 940 } 941 942 void setThrottle(DccThrottle throttle) { 943 DccThrottle old = this.throttle; 944 this.throttle = throttle; 945 if ((old == null) || (old == throttle)) { 946 return; 947 } 948 949 // As the throttle has changed, we need to inform the listeners. 950 // However if a throttle hasn't used the new code, it will not have been 951 // removed and will get a notification. 952 log.debug("Throttle assigned {} has been changed, need to notify throttle users", throttle.getLocoAddress() ); 953 954 this.throttle = throttle; 955 for (ThrottleListener listener : listeners) { 956 listener.notifyThrottleFound(throttle); 957 } 958 //This handles moving the listeners from the old throttle to the new one 959 LocoAddress la = this.throttle.getLocoAddress(); 960 PropertyChangeEvent e = new PropertyChangeEvent(this, "throttleAssignmentChanged", null, la); // NOI18N 961 for (PropertyChangeListener prop : old.getPropertyChangeListeners()) { 962 this.throttle.addPropertyChangeListener(prop); 963 prop.propertyChange(e); 964 } 965 } 966 967 void setRosterEntry(BasicRosterEntry _re) { 968 re = _re; 969 } 970 971 BasicRosterEntry getRosterEntry() { 972 return re; 973 } 974 975 void addListener(ThrottleListener l) { 976 // Check for duplication here 977 if (listeners.contains(l)) 978 log.debug("this Addresses listeners already includes listener {}", l); 979 else 980 listeners.add(l); 981 } 982 983 void removeListener(ThrottleListener l) { 984 listeners.remove(l); 985 } 986 987 boolean containsListener(ThrottleListener l) { 988 return listeners.contains(l); 989 } 990 } 991 992 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractThrottleManager.class); 993 994}