001package jmri.implementation;
002
003import java.util.*;
004import javax.annotation.Nonnull;
005import jmri.NamedBeanHandle;
006import jmri.Turnout;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * SignalMast implemented via Turnout objects.
012 * <p>
013 * A SignalMast that is built up using turnouts to control a specific
014 * appearance. System name specifies the creation information:
015 * <pre>
016 * IF$tsm:basic:one-searchlight(IT1)(IT2)
017 * </pre> The name is a colon-separated series of terms:
018 * <ul>
019 * <li>IF$tsm - defines signal masts of this type
020 * <li>basic - name of the signaling system
021 * <li>one-searchlight - name of the particular aspect map
022 * <li>(IT1)(IT2) - colon-separated list of names for Turnouts
023 * </ul>
024 *
025 * @author Bob Jacobsen Copyright (C) 2009, 2014
026 */
027public class TurnoutSignalMast extends AbstractSignalMast {
028
029    public TurnoutSignalMast(String systemName, String userName) {
030        super(systemName, userName);
031        configureFromName(systemName);
032    }
033
034    public TurnoutSignalMast(String systemName) {
035        super(systemName);
036        configureFromName(systemName);
037    }
038
039    private static final String mastType = "IF$tsm";
040
041    private void configureFromName(String systemName) {
042        // split out the basic information
043        String[] parts = systemName.split(":");
044        if (parts.length < 3) {
045            log.error("SignalMast system name needs at least three parts: {}", systemName);
046            throw new IllegalArgumentException("System name needs at least three parts: " + systemName);
047        }
048        if (!parts[0].equals(mastType)) {
049            log.warn("SignalMast system name should start with {} but is {}", mastType, systemName);
050        }
051        String system = parts[1];
052        String mast = parts[2];
053
054        mast = mast.substring(0, mast.indexOf("("));
055        setMastType(mast);
056
057        String tmp = parts[2].substring(parts[2].indexOf("($") + 2, parts[2].indexOf(")"));
058        try {
059            int autoNumber = Integer.parseInt(tmp);
060            if (autoNumber > getLastRef()) {
061                setLastRef(autoNumber);
062            }
063        } catch (NumberFormatException e) {
064            log.warn("Auto generated SystemName {} is not in the correct format", systemName);
065        }
066
067        configureSignalSystemDefinition(system);
068        configureAspectTable(system, mast);
069    }
070
071    @Override
072    public void setAspect(@Nonnull String aspect) {
073        // check it's a choice
074        if (!map.checkAspect(aspect)) {
075            // not a valid aspect
076            log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName());
077            throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName());
078        } else if (disabledAspects.contains(aspect)) {
079            log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName());
080            throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName());
081        }
082        
083        
084        if (getLit()) { // If the signalmast is lit, then send the commands to change the aspect.
085            
086            // reset all states before setting this one, including this one
087            if (resetPreviousStates) {
088                // Clear all the current states, this will result in the signalmast going blank for a very short time.
089                for (Map.Entry<String, TurnoutAspect> entry : turnouts.entrySet()) {
090                    String appearance = entry.getKey();
091                    TurnoutAspect aspt = entry.getValue();
092                    if (!isAspectDisabled(appearance)) {
093                        int setState = Turnout.CLOSED;
094                        if (aspt.getTurnoutState() == Turnout.CLOSED) {
095                            setState = Turnout.THROWN;
096                        }
097                        if (aspt.getTurnout() != null ) {
098                            if (aspt.getTurnout().getKnownState() != setState) {
099                                aspt.getTurnout().setCommandedState(setState);
100                            }
101                        } else {
102                            log.error("Trying to reset \"{}\" on signal mast \"{}\" which has not been configured", appearance, getDisplayName());
103                        }
104                    }
105                }
106            }
107
108            // set the finel state if possible
109            if (turnouts.get(aspect) != null && turnouts.get(aspect).getTurnout() != null) {
110                Turnout turnToSet = turnouts.get(aspect).getTurnout();
111                int stateToSet = turnouts.get(aspect).getTurnoutState();
112                turnToSet.setCommandedState(stateToSet);
113            } else {
114                log.error("Trying to set \"{}\" on signal mast \"{}\" which has not been configured", aspect, getDisplayName());
115            }
116            
117        } else if (log.isDebugEnabled()) {
118            log.debug("Mast set to unlit, will not send aspect change to hardware");
119        }
120        super.setAspect(aspect);
121    }
122
123    private TurnoutAspect unLit = null;
124
125    public void setUnLitTurnout(String turnoutName, int turnoutState) {
126        unLit = new TurnoutAspect(turnoutName, turnoutState);
127    }
128
129    public String getUnLitTurnoutName() {
130        if (unLit != null) {
131            return unLit.getTurnoutName();
132        }
133        return null;
134    }
135
136    public Turnout getUnLitTurnout() {
137        if (unLit != null) {
138            return unLit.getTurnout();
139        }
140        return null;
141    }
142
143    public int getUnLitTurnoutState() {
144        if (unLit != null) {
145            return unLit.getTurnoutState();
146        }
147        return -1;
148    }
149
150    @Override
151    public void setLit(boolean newLit) {
152        if (!allowUnLit() || newLit == getLit()) {
153            return;
154        }
155        super.setLit(newLit);
156        if (newLit) {
157            // This will force the signalmast to send out the commands to set the aspect again.
158            setAspect(getAspect());
159        } else {
160            if (unLit != null) {
161                // there is a specific unlit output defined
162                Turnout t = unLit.getTurnout();
163                if (t != null && t.getKnownState() != getUnLitTurnoutState()) {
164                    t.setCommandedState(getUnLitTurnoutState());
165                }
166            } else {
167                // turn everything off
168                for (TurnoutAspect aspect : turnouts.values()) {
169                    int setState = Turnout.CLOSED;
170                    if (aspect.getTurnoutState() == Turnout.CLOSED) {
171                        setState = Turnout.THROWN;
172                    }
173                    if (aspect.getTurnout().getKnownState() != setState) {
174                        aspect.getTurnout().setCommandedState(setState);
175                    }
176                }
177            }
178        }
179    }
180
181    public String getTurnoutName(String appearance) {
182        TurnoutAspect aspect = turnouts.get(appearance);
183        if (aspect != null) {
184            return aspect.getTurnoutName();
185        }
186        return "";
187    }
188
189    public int getTurnoutState(String appearance) {
190        TurnoutAspect aspect = turnouts.get(appearance);
191        if (aspect != null) {
192            return aspect.getTurnoutState();
193        }
194        return -1;
195    }
196
197    public void setTurnout(String appearance, String turn, int state) {
198        if (turnouts.containsKey(appearance)) {
199            log.debug("Appearance {} is already defined so will override", appearance);
200            turnouts.remove(appearance);
201        }
202        turnouts.put(appearance, new TurnoutAspect(turn, state));
203    }
204
205    HashMap<String, TurnoutAspect> turnouts = new HashMap<>();
206
207    private boolean resetPreviousStates = false;
208
209    /**
210     * If the signal mast driver requires the previous state to be cleared down
211     * before the next state is set.
212     *
213     * @param boo true if prior states should be cleared; false otherwise
214     */
215    public void resetPreviousStates(boolean boo) {
216        resetPreviousStates = boo;
217    }
218
219    public boolean resetPreviousStates() {
220        return resetPreviousStates;
221    }
222
223    static class TurnoutAspect {
224
225        NamedBeanHandle<Turnout> namedTurnout;
226        int state;
227
228        TurnoutAspect(String turnoutName, int turnoutState) {
229            if (turnoutName != null && !turnoutName.equals("")) {
230                state = turnoutState;
231                Turnout turn = jmri.InstanceManager.turnoutManagerInstance().getTurnout(turnoutName);
232                if (turn == null) {  
233                    log.error("TurnoutAspect couldn't locate turnout {}", turnoutName);
234                    return;
235                }
236                namedTurnout = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(turnoutName, turn);
237            }
238        }
239
240        Turnout getTurnout() {
241            if (namedTurnout == null) {
242                return null;
243            }
244            return namedTurnout.getBean();
245        }
246
247        String getTurnoutName() {
248            if (namedTurnout == null) {
249                return null;
250            }
251            return namedTurnout.getName();
252        }
253
254        int getTurnoutState() {
255            return state;
256        }
257    }
258
259    boolean isTurnoutUsed(Turnout t) {
260        for (TurnoutAspect ta : turnouts.values()) {
261            if (t.equals(ta.getTurnout())) {
262                return true;
263            }
264        }
265        if (t.equals(getUnLitTurnout())) {
266            return true;
267        }
268        return false;
269    }
270
271    public List<NamedBeanHandle<Turnout>> getHeadsUsed() {
272        return new ArrayList<NamedBeanHandle<Turnout>>();
273    }
274
275    /**
276     *
277     * @param newVal for ordinal of all TurnoutSignalMasts in use
278     */
279    protected static void setLastRef(int newVal) {
280        lastRef = newVal;
281    }
282
283    /**
284     * @return highest ordinal of all TurnoutSignalMasts in use
285     */
286    public static int getLastRef() {
287        return lastRef;
288    }
289
290    /**
291     * Ordinal of all TurnoutSignalMasts to create unique system name.
292     */
293    protected static volatile int lastRef = 0;
294    // TODO narrow access to static, once jmri/jmrit/beantable/signalmast/TurnoutSignalMastAddPane uses setLastRef(n)
295    //private static volatile int lastRef = 0;
296
297    @Override
298    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
299        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
300            if (evt.getOldValue() instanceof Turnout) {
301                if (isTurnoutUsed((Turnout) evt.getOldValue())) {
302                    java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null);
303                    throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseTurnoutSignalMastVeto", getDisplayName()), e); // NOI18N
304                }
305            }
306        }
307    }
308
309    @Override
310    public void dispose() {
311        super.dispose();
312    }
313
314    private final static Logger log = LoggerFactory.getLogger(TurnoutSignalMast.class);
315
316}