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