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