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 */
032abstract public class AbstractCabSignalManager implements CabSignalManager, jmri.Disposable {
033
034    protected HashMap<LocoAddress, CabSignal> signalList;
035    protected 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).addPropertyChangeListener("beans", this::handleBlockConfigChanged);
045    }
046
047    /**
048     * Find a CabSignal with the given address, and return it. If the CabSignal
049     * doesn't exit, create it.
050     *
051     * @param address the cab signal for the address
052     * @return an existing or new cab signal
053     */
054    @Override
055    public CabSignal getCabSignal(LocoAddress address){
056        if(_blocksWithListeners.isEmpty()) {
057           initBlocks();
058        }
059        if(!signalList.containsKey(address)){
060           signalList.put(address, createCabSignal(address));
061           notifyCabSignalListChanged();
062        }
063        return signalList.get(address);
064    }
065
066    /**
067     * Create a new cab signal with the given address.
068     *
069     * @param address the address the cab signal is for
070     * @return a new cab signal
071     */
072    abstract protected CabSignal createCabSignal(LocoAddress address);
073
074    /**
075     * Remove an old CabSignal.
076     *
077     * @param address the address associated with the cab signal
078     */
079    @Override
080    public void delCabSignal(LocoAddress address){
081       if(signalList.containsKey(address)){
082          signalList.remove(address);
083          notifyCabSignalListChanged();
084       }
085    }
086
087    /**
088     * Get a list of known cab signal addresses.
089     *
090     * @return list of cab signal addresses
091     */
092    @Override
093    public Set<LocoAddress> getCabSignalList(){
094       return signalList.keySet();
095    }
096
097    /**
098     * Get an array of known cab signals.
099     *
100     * @return array of cab signals
101     */
102    @Override
103    public CabSignal[] getCabSignalArray(){
104       return signalList.values().toArray(new CabSignal[1]);
105    }
106
107    /**
108     * Register a CabSignalListListener object with this CabSignalManager
109     *
110     * @param listener a CabSignal List Listener object.
111     */
112    @Override
113    public void addCabSignalListListener(CabSignalListListener listener){
114       if(!listListeners.contains(listener)){
115          listListeners.add(listener);
116       }
117    }
118
119    /**
120     * Remove a CabSignalListListener object with this CabSignalManager
121     *
122     * @param listener a CabSignal List Listener object.
123     */
124    @Override
125    public void removeCabSignalListListener(CabSignalListListener listener){
126       if(listListeners.contains(listener)){
127          listListeners.remove(listener);
128       }
129    }
130
131    /**
132     * Notify the registered CabSignalListListener objects that the CabSignalList
133     * has changed.
134     */
135    @Override
136    public void notifyCabSignalListChanged(){
137       for(CabSignalListListener l : listListeners){
138           l.notifyCabSignalListChanged();
139       }
140    }
141
142    // Adds changelistener to blocks
143    private void initBlocks(){
144        Set<Block> blockSet = InstanceManager.getDefault(BlockManager.class).getNamedBeanSet();
145        for (Block b : blockSet) {
146            b.addPropertyChangeListener(this::handleBlockChange);
147            _blocksWithListeners.add(b);
148        }
149    }
150
151    private void removeListenerFromBlocks(){
152        for (Block b : _blocksWithListeners) {
153            b.removePropertyChangeListener(this::handleBlockChange);
154        }
155        _blocksWithListeners.clear();
156    }
157
158    /**
159     * Handle tasks when block contents change.
160     * @param e propChgEvent
161     */
162    private void handleBlockChange(PropertyChangeEvent e) {
163        log.debug("property {} new value {} old value {}",e.getPropertyName(), e.getNewValue(), e.getOldValue());
164        if (e.getPropertyName().equals("value")){
165            if(e.getOldValue() == null && e.getNewValue() != null){
166                for(CabSignal c : signalList.values()){
167                    if(c.getBlock() == null){
168                        c.setBlock(); // cause this cab signal to look for a block.
169                    }
170                }
171            }
172        }
173    }
174
175    private void handleBlockConfigChanged(PropertyChangeEvent e) {
176        log.debug("blocks changed in blockmanager {}", e);
177        removeListenerFromBlocks();
178        if ( !signalList.isEmpty() ) { // no need to add if no listeners.
179            initBlocks();
180        }
181    }
182
183    @Override
184    public void dispose(){
185        InstanceManager.getDefault(BlockManager.class).removePropertyChangeListener("beans", this::handleBlockConfigChanged);
186        for(CabSignal c : signalList.values()){
187            c.dispose();
188        }
189        removeListenerFromBlocks();
190    } 
191
192    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractCabSignalManager.class);
193
194}