001package jmri.implementation;
002
003import java.util.ArrayList;
004import java.util.HashMap;
005import java.util.List;
006import javax.annotation.Nonnull;
007import jmri.InstanceManager;
008import jmri.NamedBean;
009import jmri.NamedBeanHandle;
010import jmri.NamedBeanHandleManager;
011import jmri.SignalHead;
012import jmri.SignalMast;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * SignalMast implemented via one SignalHead object.
018 * <p>
019 * System name specifies the creation information:
020 * <pre>
021 * IF$shsm:basic:one-searchlight(IH1)(IH2)
022 * </pre>
023 * The name is a colon-separated series of terms:
024 * <ul>
025 * <li>IF$shsm - defines signal masts of this type
026 * <li>basic - name of the signaling system
027 * <li>one-searchlight - name of the particular aspect map
028 * <li>(IH1)(IH2) - List of signal head names in parentheses.  Note: There is no colon between the mast name and the head names.
029 * </ul>
030 * There was an older form where the SignalHead names were also colon separated:
031 * IF$shsm:basic:one-searchlight:IH1:IH2 This was deprecated because colons appear in
032 * e.g. SE8c system names.
033 * <ul>
034 * <li>IF$shsm - defines signal masts of this type
035 * <li>basic - name of the signaling system
036 * <li>one-searchlight - name of the particular aspect map
037 * <li>IH1:IH2 - colon-separated list of names for SignalHeads
038 * </ul>
039 *
040 * @author Bob Jacobsen Copyright (C) 2009
041 */
042public class SignalHeadSignalMast extends AbstractSignalMast {
043
044    public SignalHeadSignalMast(String systemName, String userName) {
045        super(systemName, userName);
046        configureFromName(systemName);
047    }
048
049    public SignalHeadSignalMast(String systemName) {
050        super(systemName);
051        configureFromName(systemName);
052    }
053
054    private static final String THE_MAST_TYPE = "IF$shsm";
055
056    private void configureFromName(String systemName) {
057        // split out the basic information
058        String[] parts = systemName.split(":");
059        if (parts.length < 3) {
060            log.error("SignalMast system name needs at least three parts: {}", systemName);
061            throw new IllegalArgumentException("System name needs at least three parts: " + systemName);
062        }
063        if (!parts[0].equals(THE_MAST_TYPE)) {
064            log.warn("SignalMast system name should start with {} but is {}", THE_MAST_TYPE, systemName);
065        }
066        String prefix = parts[0];
067        String system = parts[1];
068        String mast = parts[2];
069
070        // if "mast" contains (, it's a new style
071        if (mast.indexOf('(') == -1) {
072            // old style
073            setMastType(mast);
074            configureSignalSystemDefinition(system);
075            configureAspectTable(system, mast);
076            configureHeads(parts, 3);
077        } else {
078            // new style
079            mast = mast.substring(0, mast.indexOf("("));
080            setMastType(mast);
081            String interim = systemName.substring(prefix.length() + 1 + system.length() + 1);
082            String parenstring = interim.substring(interim.indexOf("("), interim.length());
083            java.util.List<String> parens = jmri.util.StringUtil.splitParens(parenstring);
084            configureSignalSystemDefinition(system);
085            configureAspectTable(system, mast);
086            String[] heads = new String[parens.size()];
087            int i = 0;
088            for (String p : parens) {
089                heads[i] = p.substring(1, p.length() - 1);
090                i++;
091            }
092            configureHeads(heads, 0);
093        }
094    }
095
096    private void configureHeads(String parts[], int start) {
097        heads = new ArrayList<>();
098        for (int i = start; i < parts.length; i++) {
099            String name = parts[i];
100            // check head exists
101            SignalHead head = InstanceManager.getDefault(jmri.SignalHeadManager.class).getSignalHead(name);
102            if (head == null) {
103                log.warn("Attempting to create Mast from non-existant signal head {}", name);
104                continue;
105            }
106            NamedBeanHandle<SignalHead> s
107                    = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, head);
108            heads.add(s);
109        }
110    }
111
112    @Override
113    public void setAspect(@Nonnull String aspect) {
114        // check it's a choice
115        if (!map.checkAspect(aspect)) {
116            // not a valid aspect
117            log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName());
118            throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName());
119        } else if (disabledAspects.contains(aspect)) {
120            log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName());
121            throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName());
122        }
123
124        // set the outputs
125        if (log.isDebugEnabled()) {
126            log.debug("setAspect \"{}\", numHeads= {}", aspect, heads.size());
127        }
128        setAppearances(aspect);
129        // do standard processing
130        super.setAspect(aspect);
131    }
132
133    @Override
134    public void setHeld(boolean state) {
135        // set all Heads to state
136        for (NamedBeanHandle<SignalHead> h : heads) {
137            try {
138                h.getBean().setHeld(state);
139            } catch (java.lang.NullPointerException ex) {
140                log.error("NPE caused when trying to set Held due to missing signal head in mast {}", getDisplayName());
141            }
142        }
143        super.setHeld(state);
144    }
145
146    @Override
147    public void setLit(boolean state) {
148        // set all Heads to state
149        for (NamedBeanHandle<SignalHead> h : heads) {
150            try {
151                h.getBean().setLit(state);
152            } catch (java.lang.NullPointerException ex) {
153                log.error("NPE caused when trying to set Lit due to missing signal head in mast {}", getDisplayName());
154            }
155        }
156        super.setLit(state);
157    }
158
159    private List<NamedBeanHandle<SignalHead>> heads;
160
161    public List<NamedBeanHandle<SignalHead>> getHeadsUsed() {
162        return heads;
163    }
164
165    // taken out of the defaultsignalappearancemap
166    public void setAppearances(String aspect) {
167        if (map == null) {
168            log.error("No appearance map defined, unable to set appearance {}", getDisplayName());
169            return;
170        }
171        if (map.getSignalSystem() != null && map.getSignalSystem().checkAspect(aspect) && map.getAspectSettings(aspect) != null) {
172            log.warn("Attempt to set {} to undefined aspect: {}", getSystemName(), aspect);
173        } else if ((map.getAspectSettings(aspect) != null) && (heads.size() > map.getAspectSettings(aspect).length)) {
174            log.warn("setAppearance to \"{}\" finds {} heads but only {} settings", aspect, heads.size(), map.getAspectSettings(aspect).length);
175        }
176
177        int delay = 0;
178        try {
179            if (map.getProperty(aspect, "delay") != null) {
180                delay = Integer.parseInt(map.getProperty(aspect, "delay"));
181            }
182        } catch (Exception e) {
183            log.debug("No delay set");
184            //can be considered normal if does not exists or is invalid
185        }
186        HashMap<SignalHead, Integer> delayedSet = new HashMap<>(heads.size());
187        for (int i = 0; i < heads.size(); i++) {
188            // some extensive checking
189            boolean error = false;
190            if (heads.get(i) == null) {
191                log.error("Head {} unexpectedly null in setAppearances while setting aspect \"{}\" for {}", i, aspect, getSystemName());
192                error = true;
193            }
194            if (heads.get(i).getBean() == null) {
195                log.error("Head {} getBean() unexpectedly null in setAppearances while setting aspect \"{}\" for {}", i, aspect, getSystemName());
196                error = true;
197            }
198            if (map.getAspectSettings(aspect) == null) {
199                log.error("Couldn't get table array for aspect \"{}\" in setAppearances for {}", aspect, getSystemName());
200                error = true;
201            }
202
203            if (!error) {
204                SignalHead head = heads.get(i).getBean();
205                int[] dsam = map.getAspectSettings(aspect);
206                if (i < dsam.length) {
207                    int toSet = dsam[i];
208                    if (delay == 0) {
209                        head.setAppearance(toSet);
210                        log.debug("Setting {} to {}", head.getSystemName(),
211                                head.getAppearanceName(toSet));
212                    } else {
213                        delayedSet.put(head, toSet);
214                    }
215                } else {
216                    log.error("     head '{}' appearance not set for aspect '{}'", head.getSystemName(), aspect);
217                }
218            } else {
219                log.error("     head appearance not set due to above error");
220            }
221        }
222        if (delay != 0) {
223            // If a delay is required we will fire this off into a seperate thread and let it get on with it.
224            final HashMap<SignalHead, Integer> thrDelayedSet = delayedSet;
225            final int thrDelay = delay;
226            Runnable r = new Runnable() {
227                @Override
228                public void run() {
229                    setDelayedAppearances(thrDelayedSet, thrDelay);
230                }
231            };
232            Thread thr = jmri.util.ThreadingUtil.newThread(r);
233            thr.setName(getDisplayName() + " delayed set appearance");
234            thr.setDaemon(true);
235            try {
236                thr.start();
237            } catch (java.lang.IllegalThreadStateException ex) {
238                log.error("Illegal Thread Sate: {}",getDisplayName(), ex);
239            }
240        }
241    }
242
243    private void setDelayedAppearances(final HashMap<SignalHead, Integer> delaySet, final int delay) {
244        for (SignalHead head : delaySet.keySet()) {
245            final SignalHead thrHead = head;
246            Runnable r = new Runnable() {
247                @Override
248                public void run() {
249                    try {
250                        thrHead.setAppearance(delaySet.get(thrHead));
251                        if (log.isDebugEnabled()) {
252                            log.debug("Setting {} to {}", thrHead.getSystemName(),
253                                    thrHead.getAppearanceName(delaySet.get(thrHead)));
254                        }
255                        Thread.sleep(delay);
256                    } catch (InterruptedException ex) {
257                        Thread.currentThread().interrupt();
258                    }
259                }
260            };
261
262            Thread thr = jmri.util.ThreadingUtil.newThread(r);
263            thr.setName(getDisplayName());
264            thr.setDaemon(true);
265            try {
266                thr.start();
267                thr.join();
268            } catch (java.lang.IllegalThreadStateException | InterruptedException ex) {
269                log.error("Exception: ", ex);
270            }
271        }
272    }
273
274    public static List<SignalHead> getSignalHeadsUsed() {
275        List<SignalHead> headsUsed = new ArrayList<>();
276        for (SignalMast mast : InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet()) {
277            if (mast instanceof jmri.implementation.SignalHeadSignalMast) {
278                java.util.List<NamedBeanHandle<SignalHead>> masthead = ((jmri.implementation.SignalHeadSignalMast) mast).getHeadsUsed();
279                for (NamedBeanHandle<SignalHead> bean : masthead) {
280                    headsUsed.add(bean.getBean());
281                }
282            }
283        }
284        return headsUsed;
285    }
286
287    public static String isHeadUsed(SignalHead head) {
288        for (SignalMast mast : InstanceManager.getDefault(jmri.SignalMastManager.class).getNamedBeanSet()) {
289            if (mast instanceof jmri.implementation.SignalHeadSignalMast) {
290                java.util.List<NamedBeanHandle<SignalHead>> masthead = ((jmri.implementation.SignalHeadSignalMast) mast).getHeadsUsed();
291                for (NamedBeanHandle<SignalHead> bean : masthead) {
292                    if ((bean.getBean()) == head) {
293                        return mast.getDisplayName();
294                    }
295                }
296            }
297        }
298        return null;
299    }
300
301    @Override
302    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
303        NamedBean nb = (NamedBean) evt.getOldValue();
304        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
305            if (nb instanceof SignalHead) {
306                for (NamedBeanHandle<SignalHead> bean : getHeadsUsed()) {
307                    if (bean.getBean().equals(nb)) {
308                        java.beans.PropertyChangeEvent e = new java.beans.PropertyChangeEvent(this, "DoNotDelete", null, null);
309                        throw new java.beans.PropertyVetoException(Bundle.getMessage("InUseSignalHeadSignalMastVeto", getDisplayName()), e);
310                    }
311                }
312            }
313        }
314    }
315
316    private final static Logger log = LoggerFactory.getLogger(SignalHeadSignalMast.class);
317
318}