001package jmri.managers;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.ArrayList;
006import java.util.HashMap;
007import java.util.Set;
008
009import jmri.Block;
010import jmri.BlockManager;
011import jmri.CabSignal;
012import jmri.CabSignalListListener;
013import jmri.CabSignalManager;
014import jmri.InstanceManager;
015import jmri.LocoAddress;
016
017/**
018 * Abstract implementation of the {@link jmri.CabSignalManager} interface.
019 *
020 * <hr>
021 * This file is part of JMRI.
022 * <p>
023 * JMRI is free software; you can redistribute it and/or modify it under the
024 * terms of version 2 of the GNU General Public License as published by the Free
025 * Software Foundation. See the "COPYING" file for a copy of this license.
026 * <p>
027 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
028 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
029 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
030 *
031 * @author Paul Bender Copyright (C) 2019
032 */
033public abstract class AbstractCabSignalManager implements CabSignalManager, jmri.Disposable {
034
035    private final HashMap<LocoAddress, CabSignal> signalList;
036    private final ArrayList<CabSignalListListener> listListeners;
037
038    // keep a list of Blocks with listeners.
039    private final ArrayList<Block> _blocksWithListeners;
040
041    // the BlockManager and listeners this manager registers with; kept so
042    // that the listeners removed are the ones that were added
043    private final BlockManager blockManager;
044    private final PropertyChangeListener blockConfigListener = this::handleBlockConfigChanged;
045    private final PropertyChangeListener blockListener = this::handleBlockChange;
046
047    public AbstractCabSignalManager(){
048        signalList = new HashMap<>();
049        listListeners = new ArrayList<>();
050        _blocksWithListeners = new ArrayList<>();
051        blockManager = InstanceManager.getDefault(BlockManager.class);
052        blockManager.addPropertyChangeListener(BlockManager.PROPERTY_BEANS, blockConfigListener);
053    }
054
055    /**
056     * Find a CabSignal with the given address, and return it. If the CabSignal
057     * doesn't exit, create it.
058     *
059     * @param address the cab signal for the address
060     * @return an existing or new cab signal
061     */
062    @Override
063    public CabSignal getCabSignal(LocoAddress address){
064        if(_blocksWithListeners.isEmpty()) {
065           initBlocks();
066        }
067        if(!signalList.containsKey(address)){
068           signalList.put(address, createCabSignal(address));
069           notifyCabSignalListChanged();
070        }
071        return signalList.get(address);
072    }
073
074    /**
075     * Create a new cab signal with the given address.
076     *
077     * @param address the address the cab signal is for
078     * @return a new cab signal
079     */
080    protected abstract CabSignal createCabSignal(LocoAddress address);
081
082    /**
083     * Remove an old CabSignal.
084     *
085     * @param address the address associated with the cab signal
086     */
087    @Override
088    public void delCabSignal(LocoAddress address){
089       if(signalList.containsKey(address)){
090          signalList.remove(address);
091          notifyCabSignalListChanged();
092       }
093    }
094
095    /**
096     * Get a list of known cab signal addresses.
097     *
098     * @return list of cab signal addresses
099     */
100    @Override
101    public Set<LocoAddress> getCabSignalList(){
102       return signalList.keySet();
103    }
104
105    /**
106     * Get an array of known cab signals.
107     *
108     * @return array of cab signals
109     */
110    @Override
111    public CabSignal[] getCabSignalArray(){
112       return signalList.values().toArray(new CabSignal[1]);
113    }
114
115    /**
116     * Register a CabSignalListListener object with this CabSignalManager
117     *
118     * @param listener a CabSignal List Listener object.
119     */
120    @Override
121    public void addCabSignalListListener(CabSignalListListener listener){
122       if(!listListeners.contains(listener)){
123          listListeners.add(listener);
124       }
125    }
126
127    /**
128     * Remove a CabSignalListListener object with this CabSignalManager
129     *
130     * @param listener a CabSignal List Listener object.
131     */
132    @Override
133    public void removeCabSignalListListener(CabSignalListListener listener){
134       if(listListeners.contains(listener)){
135          listListeners.remove(listener);
136       }
137    }
138
139    /**
140     * Notify the registered CabSignalListListener objects that the CabSignalList
141     * has changed.
142     */
143    @Override
144    public void notifyCabSignalListChanged(){
145       for(CabSignalListListener l : listListeners){
146           l.notifyCabSignalListChanged();
147       }
148    }
149
150    // Adds changelistener to blocks
151    private void initBlocks(){
152        Set<Block> blockSet = blockManager.getNamedBeanSet();
153        for (Block b : blockSet) {
154            b.addPropertyChangeListener(blockListener);
155            _blocksWithListeners.add(b);
156        }
157    }
158
159    private void removeListenerFromBlocks(){
160        for (Block b : _blocksWithListeners) {
161            b.removePropertyChangeListener(blockListener);
162        }
163        _blocksWithListeners.clear();
164    }
165
166    /**
167     * Handle tasks when block contents change.
168     * @param e propChgEvent
169     */
170    private void handleBlockChange(PropertyChangeEvent e) {
171        log.debug("property {} new value {} old value {}",
172            e.getPropertyName(), e.getNewValue(), e.getOldValue());
173        if ( Block.PROPERTY_VALUE.equals(e.getPropertyName())
174                && e.getOldValue() == null && e.getNewValue() != null ) {
175            for ( CabSignal c : signalList.values() ) {
176                if ( c.getBlock() == null ){
177                    c.setBlock(); // cause this cab signal to look for a block.
178                }
179            }
180        }
181    }
182
183    private void handleBlockConfigChanged(PropertyChangeEvent e) {
184        log.debug("blocks changed in blockmanager {}", e);
185        removeListenerFromBlocks();
186        if ( !signalList.isEmpty() ) { // no need to add if no listeners.
187            initBlocks();
188        }
189    }
190
191    @Override
192    public void dispose(){
193        blockManager.removePropertyChangeListener(BlockManager.PROPERTY_BEANS, blockConfigListener);
194        for(CabSignal c : signalList.values()){
195            c.dispose();
196        }
197        removeListenerFromBlocks();
198    } 
199
200    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractCabSignalManager.class);
201
202}