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 // setRosterEntry() must run BEFORE notifyThrottleFound() here -- 486 // a listener that inspects the roster entry during its own 487 // notifyThrottleFound() (e.g. JsonThrottle.sendStatus()) would 488 // otherwise always see null. 489 if (ads != null && a.get(i).getRosterEntry() != null && throttle.getRosterEntry() == null) { 490 throttle.setRosterEntry(a.get(i).getRosterEntry()); 491 } 492 l.notifyThrottleFound(throttle); 493 addressThrottles.get(addr).incrementUse(); 494 addressThrottles.get(addr).addListener(l); 495 updateNumUsers(addr, addressThrottles.get(addr).getUseCount()); 496 } 497 throttleListeners.remove(addr); 498 } 499 ArrayList<WaitingThrottle> p = listenerOnly.get(addr); 500 if (p == null) { 501 log.debug("notifyThrottleKnown with zero-length propertyChangeListeners: {}", addr); 502 } else { 503 for (WaitingThrottle waitingThrottle : p) { 504 PropertyChangeListener l = waitingThrottle.getPropertyChangeListener(); 505 log.debug("Notify propertyChangeListener"); 506 l.propertyChange(new PropertyChangeEvent(this, "throttleAssigned", null, addr)); 507 if (ads != null && waitingThrottle.getRosterEntry() != null && throttle.getRosterEntry() == null) { 508 throttle.setRosterEntry(waitingThrottle.getRosterEntry()); 509 } 510 throttle.addPropertyChangeListener(l); 511 } 512 listenerOnly.remove(addr); 513 } 514 } 515 516 517 /** 518 * For when a steal / share decision is needed and the ThrottleListener has delegated 519 * this decision to the ThrottleManager. 520 * <p> 521 * Responds to the question by requesting a Throttle "Steal" by default. 522 * <p> 523 * Can be overridden by hardware types which do not wish the default behaviour to Steal. 524 * <p> 525 * This applies only to those systems where "stealing" or "sharing" applies, such as LocoNet. 526 * @param address The LocoAddress the steal / share question relates to 527 * @param question The Question to be put to the ThrottleListener 528 */ 529 protected void makeHardwareDecision(LocoAddress address, ThrottleListener.DecisionType question){ 530 responseThrottleDecision(address, null, ThrottleListener.DecisionType.STEAL ); 531 } 532 533 /** 534 * When the system-specific ThrottleManager has been unable to create the DCC 535 * throttle because it is already in use and must be "stolen" or "shared" to take control, 536 * it needs to notify the listener of this situation. 537 * <p> 538 * This applies only to those systems where "stealing" or "sharing" applies, such as LocoNet. 539 * 540 * @param address The LocoAddress the steal / share question relates to 541 * @param question The Question to be put to the ThrottleListener 542 */ 543 protected synchronized void notifyDecisionRequest(LocoAddress address, ThrottleListener.DecisionType question) { 544 ArrayList<WaitingThrottle> a = throttleListeners.get(address); 545 if (a == null) { 546 log.debug("Cannot issue question. No throttle listeners registered for address {}", address.getNumber()); 547 return; 548 } 549 ThrottleListener l; 550 log.debug("{} listener(s) registered for address {}", a.size(), address.getNumber()); 551 for (int i = 0; i < a.size(); i++) { // enhanced for (WaitingThrottle waitingThrottle : a) doesn't work somehow 552 if (a.get(i).canHandleDecisions()) { 553 l = a.get(i).getListener(); 554 log.debug("Notifying a throttle listener (address {}) of the steal share situation", address.getNumber()); 555 l.notifyDecisionRequired(address, question); 556 } else { 557 log.debug("Passing {} to hardware steal / share decision making", address.getNumber()); 558 makeHardwareDecision(address, question); 559 } 560 } 561 } 562 563 /** 564 * Check to see if the Dispatch Button should be enabled or not Default to 565 * true, override if necessary 566 * 567 */ 568 @Override 569 public boolean hasDispatchFunction() { 570 return true; 571 } 572 573 /** 574 * What speed modes are supported by this system? value should be xor of 575 * possible modes specifed by the DccThrottle interface 576 */ 577 @Override 578 public EnumSet<SpeedStepMode> supportedSpeedModes() { 579 return EnumSet.of(SpeedStepMode.NMRA_DCC_128); 580 } 581 582 /** 583 * Hardware that uses the Silent Steal preference 584 * will need to override 585 * {@inheritDoc} 586 */ 587 @Override 588 public boolean enablePrefSilentStealOption() { 589 return false; 590 } 591 592 /** 593 * Hardware that uses the Silent Share preference 594 * will need to override 595 * {@inheritDoc} 596 */ 597 @Override 598 public boolean enablePrefSilentShareOption() { 599 return false; 600 } 601 602 /** 603 * {@inheritDoc} 604 */ 605 @Override 606 public synchronized void attachListener(LocoAddress la, java.beans.PropertyChangeListener p) { 607 if (addressThrottles.containsKey(la)) { 608 addressThrottles.get(la).getThrottle().addPropertyChangeListener(p); 609 p.propertyChange(new PropertyChangeEvent(this, "throttleAssigned", null, la)); 610 } else { 611 if (!listenerOnly.containsKey(la)) { 612 listenerOnly.put(la, new ArrayList<>()); 613 } 614 615 // get the corresponding list to check length 616 ArrayList<WaitingThrottle> a = listenerOnly.get(la); 617 a.add(new WaitingThrottle(p, null, false)); 618 //Only request that the throttle is set up if it hasn't already been 619 //requested. 620 if ((!throttleListeners.containsKey(la)) && (a.size() == 1)) { 621 requestThrottleSetup(la, false); 622 } 623 } 624 } 625 626 /** 627 * {@inheritDoc} 628 */ 629 @Override 630 public synchronized void removeListener(LocoAddress la, java.beans.PropertyChangeListener p) { 631 if (addressThrottles.containsKey(la)) { 632 addressThrottles.get(la).getThrottle().removePropertyChangeListener(p); 633 p.propertyChange(new PropertyChangeEvent(this, "throttleRemoved", la, null)); 634 return; 635 } 636 p.propertyChange(new PropertyChangeEvent(this, "throttleNotFoundInRemoval", la, null)); 637 } 638 639 /** 640 * {@inheritDoc} 641 */ 642 @Override 643 public synchronized boolean addressStillRequired(LocoAddress la) { 644 if (addressThrottles.containsKey(la)) { 645 log.debug("usage count is {}", addressThrottles.get(la).getUseCount()); 646 return (addressThrottles.get(la).getUseCount() > 0); 647 } 648 return false; 649 } 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 public boolean addressStillRequired(int address, boolean isLongAddress) { 656 DccLocoAddress la = new DccLocoAddress(address, isLongAddress); 657 return addressStillRequired(la); 658 } 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override 664 public boolean addressStillRequired(int address) { 665 boolean isLong = true; 666 if (canBeShortAddress(address)) { 667 isLong = false; 668 } 669 return addressStillRequired(address, isLong); 670 } 671 672 /** 673 * {@inheritDoc} 674 */ 675 @Override 676 public boolean addressStillRequired(BasicRosterEntry re) { 677 return addressStillRequired(re.getDccLocoAddress()); 678 } 679 680 /** 681 * {@inheritDoc} 682 */ 683 @Override 684 public void releaseThrottle(DccThrottle t, ThrottleListener l) { 685 log.debug("AbstractThrottleManager.releaseThrottle: {}, {}", t, l); 686 disposeThrottle(t, l); 687 } 688 689 /** 690 * {@inheritDoc} 691 */ 692 @Override 693 public boolean disposeThrottle(DccThrottle t, ThrottleListener l) { 694 log.debug("AbstractThrottleManager.disposeThrottle: {}, {}", t, l); 695 696// if (!active) log.error("Dispose called when not active"); <-- might need to control this in the sub class 697 LocoAddress la = t.getLocoAddress(); 698 if (addressReleased(la, l)) { 699 log.debug("Address {} still has active users", t.getLocoAddress()); 700 return false; 701 } 702 if (t.getPropertyChangeListeners().length > 0) { 703 log.debug("Throttle {} still has {} active propertyChangeListeners registered to the throttle", t.getLocoAddress(), t.getPropertyChangeListeners().length); 704 return false; 705 } 706 synchronized (this) { 707 if (addressThrottles.containsKey(la)) { 708 addressThrottles.remove(la); 709 log.debug("Loco Address {} removed from the stack ", la); 710 } else { 711 log.debug("Loco Address {} not found in the stack ", la); 712 } 713 } 714 return true; 715 } 716 717 /** 718 * Throttle can no longer be relied upon, 719 * potentially from an external forced steal or hardware error. 720 * <p> 721 * Normally, #releaseThrottle should be used to close throttles. 722 * <p> 723 * Removes locoaddress from list to force new throttle requests 724 * to request new sessions where the Command station model 725 * implements a dynamic stack, not a static stack. 726 * 727 * <p> 728 * Managers still need to advise listeners that the session has 729 * been cancelled and actually dispose of the throttle 730 * @param la address release 731 */ 732 protected void forceDisposeThrottle(LocoAddress la) { 733 log.debug("force dispose address {}", la); 734 if (addressThrottles.containsKey(la)) { 735 addressThrottles.remove(la); 736 log.debug("Loco Address {} removed from the stack ", la); 737 } else { 738 log.debug("Loco Address {} not found in the stack ", la); 739 } 740 } 741 742 /** 743 * {@inheritDoc} 744 */ 745 @Override 746 public void dispatchThrottle(DccThrottle t, ThrottleListener l) { 747 releaseThrottle(t, l); 748 } 749 750 /** 751 * {@inheritDoc} 752 */ 753 @Override 754 public void dispose() { 755 } 756 757 /** 758 * {@inheritDoc} 759 */ 760 @Override 761 public synchronized int getThrottleUsageCount(LocoAddress la) { 762 if (addressThrottles.containsKey( la)) { 763 return addressThrottles.get(la).getUseCount(); 764 } 765 return 0; 766 } 767 768 /** 769 * {@inheritDoc} 770 */ 771 @Override 772 public int getThrottleUsageCount(int address, boolean isLongAddress) { 773 DccLocoAddress la = new DccLocoAddress(address, isLongAddress); 774 return getThrottleUsageCount(la); 775 } 776 777 /** 778 * {@inheritDoc} 779 */ 780 @Override 781 public int getThrottleUsageCount(int address) { 782 boolean isLong = true; 783 if (canBeShortAddress(address)) { 784 isLong = false; 785 } 786 return getThrottleUsageCount(address, isLong); 787 } 788 789 /** 790 * {@inheritDoc} 791 */ 792 @Override 793 public int getThrottleUsageCount(BasicRosterEntry re) { 794 return getThrottleUsageCount(re.getDccLocoAddress()); 795 } 796 797 /** 798 * Release a Throttle from a ThrottleListener. 799 * @param la address release 800 * @param l listening object 801 * @return True if throttle still has listeners or a positive use count, else False 802 */ 803 protected synchronized boolean addressReleased(LocoAddress la, ThrottleListener l) { 804 if (addressThrottles.containsKey(la)) { 805 if (addressThrottles.get(la).containsListener(l)) { 806 log.debug("decrementUse called with listener {}", l); 807 addressThrottles.get(la).decrementUse(); 808 addressThrottles.get(la).removeListener(l); 809 } else if (l == null) { 810 log.debug("decrementUse called withOUT listener"); 811 /*The release release has been called, but as no listener has 812 been specified, we can only decrement the use flag*/ 813 addressThrottles.get(la).decrementUse(); 814 } 815 } 816 if (addressThrottles.containsKey(la)) { 817 if (addressThrottles.get(la).getUseCount() > 0) { 818 updateNumUsers(la, addressThrottles.get(la).getUseCount()); 819 log.debug("addressReleased still has at least one listener"); 820 return true; 821 } 822 } 823 return false; 824 } 825 826 /** 827 * The number of users of this throttle has been updated 828 * <p> 829 * Typically used to update dispatch / release availablility 830 * specific implementations can override this function to get updates 831 * 832 * @param la the Loco Address which has been updated 833 * @param numUsers current number of users 834 */ 835 protected void updateNumUsers( LocoAddress la, int numUsers ){ 836 log.debug("Throttle {} now has {} users", la, numUsers); 837 } 838 839 /** 840 * {@inheritDoc} 841 */ 842 @Override 843 public Object getThrottleInfo(LocoAddress la, String item) { 844 DccThrottle t; 845 synchronized (this) { 846 if (addressThrottles.containsKey(la)) { 847 t = addressThrottles.get(la).getThrottle(); 848 } else { 849 return null; 850 } 851 } 852 if (item.equals(Throttle.ISFORWARD)) { 853 return t.getIsForward(); 854 } else if (item.startsWith("Speed")) { 855 switch (item) { 856 case Throttle.SPEEDSETTING: 857 return t.getSpeedSetting(); 858 case Throttle.SPEEDINCREMENT: 859 return t.getSpeedIncrement(); 860 case Throttle.SPEEDSTEPMODE: 861 return t.getSpeedStepMode(); 862 default: // skip 863 } 864 } 865 for ( int i = 0; i< t.getFunctions().length; i++ ) { 866 if (item.equals(Throttle.getFunctionString(i))) { 867 return t.getFunction(i); 868 } 869 } 870 return null; 871 } 872 873 private boolean _hideStealNotifications = false; 874 875 /** 876 * If not headless, display a session stolen dialogue box with 877 * checkbox to hide notifications for rest of JMRI session 878 * 879 * @param address the LocoAddress of the stolen / cancelled Throttle 880 */ 881 protected void showSessionCancelDialogue(LocoAddress address){ 882 if ((!java.awt.GraphicsEnvironment.isHeadless()) && (!_hideStealNotifications)){ 883 jmri.util.ThreadingUtil.runOnGUI(() -> { 884 javax.swing.JCheckBox checkbox = new javax.swing.JCheckBox( 885 Bundle.getMessage("HideFurtherAlerts")); 886 Object[] params = {Bundle.getMessage("LocoStolen", address), checkbox}; 887 java.awt.event.ActionListener stolenpopupcheckbox = (java.awt.event.ActionEvent evt) -> 888 this.hideStealNotifications(checkbox.isSelected()); 889 checkbox.addActionListener(stolenpopupcheckbox); 890 JmriJOptionPane.showMessageDialogNonModal(null, params, 891 Bundle.getMessage("LocoStolen", address), 892 JmriJOptionPane.WARNING_MESSAGE, null); 893 }); 894 } 895 } 896 897 /** 898 * Receive notification from a throttle dialogue 899 * to display steal dialogues for rest of the JMRI instance session. 900 * False by default to show notifications 901 * 902 * @param hide set True to hide notifications, else False. 903 */ 904 public void hideStealNotifications(boolean hide){ 905 _hideStealNotifications = hide; 906 } 907 908 /** 909 * This subClass keeps track of which loco address have been requested and 910 * by whom. It primarily uses an increment count to keep track of all the 911 * Addresses in use as not all external code will have been refactored over 912 * to use the new disposeThrottle. 913 */ 914 protected static class Addresses { 915 916 int useActiveCount = 0; 917 DccThrottle throttle; 918 ArrayList<ThrottleListener> listeners = new ArrayList<>(); 919 BasicRosterEntry re = null; 920 921 protected Addresses(DccThrottle throttle) { 922 this.throttle = throttle; 923 } 924 925 void incrementUse() { 926 useActiveCount++; 927 log.debug("{} increased Use Size to {}", throttle.getLocoAddress(), useActiveCount); 928 } 929 930 void decrementUse() { 931 // Do not want to go below 0 on the usage front! 932 if (useActiveCount > 0) { 933 useActiveCount--; 934 } 935 log.debug("{} decreased Use Size to {}", throttle.getLocoAddress(), useActiveCount); 936 } 937 938 int getUseCount() { 939 return useActiveCount; 940 } 941 942 DccThrottle getThrottle() { 943 return throttle; 944 } 945 946 void setThrottle(DccThrottle throttle) { 947 DccThrottle old = this.throttle; 948 this.throttle = throttle; 949 if ((old == null) || (old == throttle)) { 950 return; 951 } 952 953 // As the throttle has changed, we need to inform the listeners. 954 // However if a throttle hasn't used the new code, it will not have been 955 // removed and will get a notification. 956 log.debug("Throttle assigned {} has been changed, need to notify throttle users", throttle.getLocoAddress() ); 957 958 this.throttle = throttle; 959 for (ThrottleListener listener : listeners) { 960 listener.notifyThrottleFound(throttle); 961 } 962 //This handles moving the listeners from the old throttle to the new one 963 LocoAddress la = this.throttle.getLocoAddress(); 964 PropertyChangeEvent e = new PropertyChangeEvent(this, "throttleAssignmentChanged", null, la); // NOI18N 965 for (PropertyChangeListener prop : old.getPropertyChangeListeners()) { 966 this.throttle.addPropertyChangeListener(prop); 967 prop.propertyChange(e); 968 } 969 } 970 971 void setRosterEntry(BasicRosterEntry _re) { 972 re = _re; 973 } 974 975 BasicRosterEntry getRosterEntry() { 976 return re; 977 } 978 979 void addListener(ThrottleListener l) { 980 // Check for duplication here 981 if (listeners.contains(l)) 982 log.debug("this Addresses listeners already includes listener {}", l); 983 else 984 listeners.add(l); 985 } 986 987 void removeListener(ThrottleListener l) { 988 listeners.remove(l); 989 } 990 991 boolean containsListener(ThrottleListener l) { 992 return listeners.contains(l); 993 } 994 } 995 996 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractThrottleManager.class); 997 998}