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