001package jmri.managers;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006import java.util.Map.Entry;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.Nonnull;
010
011import jmri.*;
012import jmri.implementation.DefaultSignalMastLogic;
013import jmri.implementation.SignalSpeedMap;
014import jmri.jmrit.display.layoutEditor.*;
015import jmri.jmrix.internal.InternalSystemConnectionMemo;
016
017/**
018 * Default implementation of a SignalMastLogicManager.
019 *
020 * @see jmri.SignalMastLogicManager
021 *
022 * @author Kevin Dickerson Copyright (C) 2011
023 */
024public class DefaultSignalMastLogicManager
025        extends AbstractManager<SignalMastLogic>
026        implements SignalMastLogicManager {
027
028    public DefaultSignalMastLogicManager(InternalSystemConnectionMemo memo) {
029        super(memo);
030        registerSelf();
031        addListeners();
032    }
033
034    final void addListeners(){
035        InstanceManager.getDefault(LayoutBlockManager.class).addPropertyChangeListener(propertyBlockManagerListener);
036        InstanceManager.getDefault(SignalMastManager.class).addVetoableChangeListener(this);
037        InstanceManager.getDefault(TurnoutManager.class).addVetoableChangeListener(this);
038    }
039
040    /**
041     * {@inheritDoc}
042     */
043    @Override
044    public int getXMLOrder() {
045        return Manager.SIGNALMASTLOGICS;
046    }
047
048    private static final SignalSpeedMap _speedMap = InstanceManager.getDefault(SignalSpeedMap.class);
049
050    private static final String PROPERTY_INTERMEDIATE_SIGNAL = "intermediateSignal";
051
052    private static final String PROPERTY_INTERMEDIATE_SECTION = "intermediateSection";
053
054    public static final SignalSpeedMap getSpeedMap() {
055        return _speedMap;
056    }
057
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    @CheckForNull
063    public SignalMastLogic getSignalMastLogic(SignalMast source) {
064        for (SignalMastLogic signalMastLogic : _beans) {
065            if (signalMastLogic.getSourceMast() == source) {
066                return signalMastLogic;
067            }
068        }
069        return null;
070    }
071
072    /**
073     * Provide / create New SML.
074     * {@inheritDoc}
075     */
076    @Nonnull
077    @Override
078    public SignalMastLogic newSignalMastLogic(@Nonnull SignalMast source) throws IllegalArgumentException {
079        for (SignalMastLogic signalMastLogic : _beans) {
080            if (signalMastLogic.getSourceMast() == source) {
081                return signalMastLogic;
082            }
083        }
084        SignalMastLogic logic = new DefaultSignalMastLogic(source);
085        _beans.add(logic);
086        firePropertyChange(PROPERTY_LENGTH, null, _beans.size());
087        return logic;
088    }
089
090    /** {@inheritDoc} */
091    @Override
092    public void replaceSignalMast(@Nonnull SignalMast oldMast, @Nonnull SignalMast newMast) {
093        Objects.requireNonNull(oldMast);
094        Objects.requireNonNull(newMast);
095
096        for (SignalMastLogic source : _beans) {
097            if (source.getSourceMast() == oldMast) {
098                source.replaceSourceMast(oldMast, newMast);
099            } else {
100                source.replaceDestinationMast(oldMast, newMast);
101            }
102        }
103    }
104
105    /** {@inheritDoc} */
106    @Override
107    public void swapSignalMasts(@Nonnull SignalMast mastA, @Nonnull SignalMast mastB) {
108        Objects.requireNonNull(mastA);
109        Objects.requireNonNull(mastB);
110
111        List<SignalMastLogic> mastALogicList = getLogicsByDestination(mastA);
112        SignalMastLogic mastALogicSource = getSignalMastLogic(mastA);
113
114        List<SignalMastLogic> mastBLogicList = getLogicsByDestination(mastB);
115        SignalMastLogic mastBLogicSource = getSignalMastLogic(mastB);
116
117        if (mastALogicSource != null) {
118            mastALogicSource.replaceSourceMast(mastA, mastB);
119        }
120        if (mastBLogicSource != null) {
121            mastBLogicSource.replaceSourceMast(mastB, mastA);
122        }
123
124        for (SignalMastLogic mastALogic : mastALogicList) {
125            mastALogic.replaceDestinationMast(mastA, mastB);
126        }
127        for (SignalMastLogic mastBLogic : mastBLogicList) {
128            mastBLogic.replaceDestinationMast(mastB, mastA);
129        }
130
131    }
132
133    /** {@inheritDoc} */
134    @Nonnull
135    @Override
136    public List<SignalMastLogic> getLogicsByDestination(@Nonnull SignalMast destination) {
137        List<SignalMastLogic> list = new ArrayList<>();
138        for (SignalMastLogic source : _beans) {
139            if (source.isDestinationValid(destination)) {
140                list.add(source);
141            }
142        }
143        return list;
144    }
145
146    /** {@inheritDoc} */
147    @Nonnull
148    @Override
149    public List<SignalMastLogic> getSignalMastLogicList() {
150        return new ArrayList<>(_beans);
151    }
152
153    /** {@inheritDoc} */
154    @Override
155    public boolean isSignalMastUsed(@Nonnull SignalMast mast) {
156        SignalMastLogic sml = getSignalMastLogic(mast);
157        if ( sml != null
158            /* Although we might have it registered as a source, it may not have
159             any valid destination, so therefore it can be returned as not in use. */
160            && !sml.getDestinationList().isEmpty()) {
161                return true;
162        }
163        return !getLogicsByDestination(mast).isEmpty();
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    public void removeSignalMastLogic(@Nonnull SignalMastLogic sml, @Nonnull SignalMast dest) {
169        if (sml.removeDestination(dest)) {
170            removeSignalMastLogic(sml);
171        }
172    }
173
174    /** {@inheritDoc} */
175    @Override
176    public void removeSignalMastLogic(@Nonnull SignalMastLogic sml) {
177        Objects.requireNonNull(sml);
178        //Need to provide a method to delete and dispose.
179        sml.dispose();
180
181        _beans.remove(sml);
182        firePropertyChange(PROPERTY_LENGTH, null, _beans.size());
183    }
184
185    /** {@inheritDoc} */
186    @Override
187    public void removeSignalMast(@Nonnull SignalMast mast) {
188        Objects.requireNonNull(mast);
189        for (SignalMastLogic source : _beans) {
190            if (source.isDestinationValid(mast)) {
191                source.removeDestination(mast);
192            }
193        }
194        SignalMastLogic sml = getSignalMastLogic(mast);
195        if ( sml != null ) {
196            removeSignalMastLogic(sml);
197        }
198    }
199
200    /**
201     * Disable the use of info from the Layout Editor Panels to configure
202     * a Signal Mast Logic for a specific Signal Mast.
203     *
204     * @param mast The Signal Mast for which LE info is to be disabled
205     */
206    @Override
207    public void disableLayoutEditorUse(@Nonnull SignalMast mast) {
208        SignalMastLogic source = getSignalMastLogic(mast);
209        if (source != null) {
210            source.disableLayoutEditorUse();
211        }
212        for (SignalMastLogic sml : getLogicsByDestination(mast)) {
213            try {
214                sml.useLayoutEditor(false, mast);
215            } catch (JmriException e) {
216                log.error("Error occurred while trying to disable layout editor use", e);
217            }
218        }
219    }
220
221    // Abstract methods to be extended by subclasses:
222
223    /**
224     * Initialise all the Signal Mast Logics. Primarily used after
225     * loading a configuration.
226     */
227    @Override
228    public void initialise() {
229        for (SignalMastLogic signalMastLogic : _beans) {
230            signalMastLogic.initialise();
231        }
232    }
233
234    /** {@inheritDoc} */
235    @Override
236    public char typeLetter() {
237        throw new UnsupportedOperationException("Not supported yet.");
238    }
239
240    int signalLogicDelay = 500;
241
242    /** {@inheritDoc} */
243    @Override
244    public int getSignalLogicDelay() {
245        return signalLogicDelay;
246    }
247
248    /** {@inheritDoc} */
249    @Override
250    public void setSignalLogicDelay(int l) {
251        signalLogicDelay = l;
252    }
253
254    private final PropertyChangeListener propertyBlockManagerListener = new PropertyChangeListener() {
255        @Override
256        public void propertyChange(PropertyChangeEvent e) {
257            if ( LayoutBlockManager.PROPERTY_TOPOLOGY.equals(e.getPropertyName())) {
258                boolean newValue = (Boolean) e.getNewValue();
259                if (newValue) {
260                    for (SignalMastLogic signalMastLogic : _beans) {
261                        signalMastLogic.setupLayoutEditorDetails();
262                    }
263                    if (runWhenStablised) {
264                        try {
265                            automaticallyDiscoverSignallingPairs();
266                        } catch (JmriException je) {
267                            //Considered normal if routing not enabled
268                        }
269                    }
270                }
271            }
272        }
273    };
274
275    private boolean runWhenStablised = false;
276
277    /**
278     * Discover valid destination Signal Masts for a given source Signal Mast on a
279     * given Layout Editor Panel.
280     *
281     * @param source Source SignalMast
282     * @param layout Layout Editor panel to check.
283     */
284    @Override
285    public void discoverSignallingDest(@Nonnull SignalMast source, @Nonnull LayoutEditor layout) throws JmriException {
286        firePropertyChange(PROPERTY_AUTO_SIGNALMAST_GENERATE_START, null, source.getDisplayName());
287
288        HashMap<SignalMast, List<NamedBean>> validPaths = new HashMap<>();
289        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
290        if (!lbm.isAdvancedRoutingEnabled()) {
291            //log.debug("advanced routing not enabled");
292            throw new JmriException("advanced routing not enabled");
293        }
294        if (!lbm.routingStablised()) {
295            throw new JmriException("routing not stabilised");
296        }
297
298        validPaths.put(source, lbm.getLayoutBlockConnectivityTools()
299            .discoverPairDest(source, layout, SignalMast.class, LayoutBlockConnectivityTools.Routing.MASTTOMAST));
300
301        validPaths.entrySet().forEach( entry -> {
302            SignalMast key = entry.getKey();
303            SignalMastLogic sml = getSignalMastLogic(key);
304            if (sml == null) {
305                sml = newSignalMastLogic(key);
306            }
307
308            List<NamedBean> validDestMast = entry.getValue();
309            for (NamedBean sm : validDestMast) {
310                if (!sml.isDestinationValid((SignalMast) sm)) {
311                    try {
312                        sml.setDestinationMast((SignalMast) sm);
313                        sml.useLayoutEditorDetails(true, true, (SignalMast) sm);
314                        sml.useLayoutEditor(true, (SignalMast) sm);
315                    } catch (JmriException e) {
316                        //log.debug("We shouldn't get an exception here");
317                        log.error("Exception found when adding pair {}  to destination {}/\n{}",
318                            source.getDisplayName(), sm.getDisplayName(), e.toString());
319                        //throw e;
320                    }
321                }
322            }
323            if (sml.getDestinationList().size() == 1
324                    && sml.getAutoTurnouts(sml.getDestinationList().get(0)).isEmpty()) {
325                key.setProperty(PROPERTY_INTERMEDIATE_SIGNAL, true);
326            } else {
327                key.removeProperty(PROPERTY_INTERMEDIATE_SIGNAL);
328            }
329        });
330        initialise();
331        firePropertyChange(PROPERTY_AUTO_SIGNALMAST_GENERATE_COMPLETE, null, source.getDisplayName());
332    }
333
334    /**
335     * Discover all possible valid source + destination signal mast pairs on all
336     * Layout Editor Panels.
337     */
338    @Override
339    public void automaticallyDiscoverSignallingPairs() throws JmriException {
340        runWhenStablised = false;
341        LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class);
342        if (!lbm.isAdvancedRoutingEnabled()) {
343            throw new JmriException("advanced routing not enabled");
344        }
345        if (!lbm.routingStablised()) {
346            runWhenStablised = true;
347            return;
348        }
349        HashMap<NamedBean, List<NamedBean>> validPaths = lbm.getLayoutBlockConnectivityTools()
350            .discoverValidBeanPairs(null, SignalMast.class, LayoutBlockConnectivityTools.Routing.MASTTOMAST);
351        firePropertyChange(PROPERTY_AUTO_GENERATE_UPDATE, null,
352            ("Found " + validPaths.size() + " masts as sources for logic"));
353        InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet().forEach(nb ->
354            nb.removeProperty(PROPERTY_INTERMEDIATE_SIGNAL));
355        for (Entry<NamedBean, List<NamedBean>> e : validPaths.entrySet()) {
356            SignalMast key = (SignalMast) e.getKey();
357            SignalMastLogic sml = getSignalMastLogic(key);
358            if (sml == null) {
359                sml = newSignalMastLogic(key);
360            }
361            List<NamedBean> validDestMast = validPaths.get(key);
362            for (NamedBean nb : validDestMast) {
363                if (!sml.isDestinationValid((SignalMast) nb)) {
364                    try {
365                        sml.setDestinationMast((SignalMast) nb);
366                        sml.useLayoutEditorDetails(true, true, (SignalMast) nb);
367                        sml.useLayoutEditor(true, (SignalMast) nb);
368                    }
369                    catch (JmriException ex) {
370                        //log.debug("we shouldn't get an exception here!");
371                        log.warn("Unexpected exception setting mast", ex);
372                    }
373                }
374            }
375            if (sml.getDestinationList().size() == 1
376                    && sml.getAutoTurnouts(sml.getDestinationList().get(0)).isEmpty()) {
377                key.setProperty(PROPERTY_INTERMEDIATE_SIGNAL, true);
378            }
379        }
380        initialise();
381        firePropertyChange(PROPERTY_AUTO_GENERATE_COMPLETE, null, null);
382    }
383
384    /**
385     * Populate Sections of type SIGNALMASTLOGIC used with Layout Editor with
386     * Signal Mast attributes as stored in Signal Mast Logic.
387     */
388    public void generateSection() {
389        SectionManager sm = InstanceManager.getDefault(SectionManager.class);
390        sm.getNamedBeanSet().stream().map( nb -> {
391            if (nb.getSectionType() == Section.SIGNALMASTLOGIC) {
392                nb.removeProperty(PROPERTY_INTERMEDIATE_SECTION);
393            }
394            return nb;
395        }).forEachOrdered( nb -> nb.removeProperty("forwardMast"));
396        for (SignalMastLogic sml : getSignalMastLogicList()) {
397            LayoutBlock faceLBlock = sml.getFacingBlock();
398            if (faceLBlock != null) {
399                boolean sourceIntermediate = false;
400                Object intermSigProp = sml.getSourceMast().getProperty(PROPERTY_INTERMEDIATE_SIGNAL);
401                if (intermSigProp != null) {
402                    sourceIntermediate = ((Boolean) intermSigProp);
403                }
404                for (SignalMast destMast : sml.getDestinationList()) {
405                    if (!sml.getAutoBlocksBetweenMasts(destMast).isEmpty()) {
406                        String secUserName = sml.getSourceMast().getDisplayName() + ":" + destMast.getDisplayName();
407                        Section sec = sm.getSection(secUserName);
408                        if (sec != null) {
409                            //A Section already exists, lets check that it is one used with the SML, if so carry on using that.
410                            if (sec.getSectionType() != Section.SIGNALMASTLOGIC) {
411                                break;
412                            }
413                        } else {
414                            try {
415                                sec = sm.createNewSection(secUserName);
416                            } catch(IllegalArgumentException ex){
417                                log.warn("Unable to create section for {} {}",secUserName,ex.getMessage());
418                                continue;
419                            }
420                            // new mast
421                            sec.setSectionType(Section.SIGNALMASTLOGIC);
422                            try {
423                                //Auto running requires forward/reverse sensors, but at this stage SML does not support that, so just create dummy internal ones for now.
424                                Sensor sen = InstanceManager.sensorManagerInstance().provideSensor("IS:" + sec.getSystemName() + ":forward");
425                                sen.setUserName(sec.getSystemName() + ":forward");
426
427                                sen = InstanceManager.sensorManagerInstance().provideSensor("IS:" + sec.getSystemName() + ":reverse");
428                                sen.setUserName(sec.getSystemName() + ":reverse");
429                                sec.setForwardBlockingSensorName(sec.getSystemName() + ":forward");
430                                sec.setReverseBlockingSensorName(sec.getSystemName() + ":reverse");
431                            } catch (IllegalArgumentException ex) {
432                                log.warn("Failed to provide Sensor in generateSection");
433                            }
434                        }
435                        sml.setAssociatedSection(sec, destMast);
436                        sec.setProperty("forwardMast", destMast.getDisplayName());
437                        boolean destIntermediate = false;
438                        Object destMastImSigProp = destMast.getProperty(PROPERTY_INTERMEDIATE_SIGNAL);
439                        if ( destMastImSigProp != null) {
440                            destIntermediate = ((Boolean) destMastImSigProp);
441                        }
442
443                        sec.setProperty(PROPERTY_INTERMEDIATE_SECTION, sourceIntermediate || destIntermediate);
444
445                        //Not 100% sure about this for now so will comment out
446                        //sml.addSensor(sec.getSystemName()+":forward", Sensor.INACTIVE, destMast);
447                    }
448                }
449            } else {
450                log.info("No facing block found {}", sml.getSourceMast().getDisplayName());
451            }
452        }
453    }
454
455    /** {@inheritDoc} */
456    @Override
457    public String getBeanTypeHandled(boolean plural) {
458        return Bundle.getMessage(plural ? "BeanNameSignalMastLogics" : "BeanNameSignalMastLogic");
459    }
460
461    /**
462     * {@inheritDoc}
463     */
464    @Override
465    public Class<SignalMastLogic> getNamedBeanClass() {
466        return SignalMastLogic.class;
467    }
468
469    /**
470     * {@inheritDoc}
471     */
472    @Override
473    public int setupSignalMastsDirectionSensors() {
474        int errorCount = 0;
475        for (SignalMastLogic sml : getSignalMastLogicList()) {
476            errorCount += sml.setupDirectionSensors();
477        }
478        return errorCount;
479    }
480
481    /**
482     * {@inheritDoc}
483     */
484    @Override
485    public void removeSignalMastsDirectionSensors() {
486        for (SignalMastLogic sml : getSignalMastLogicList()) {
487            sml.removeDirectionSensors();
488        }
489    }
490
491    /** {@inheritDoc} */
492    @Override
493    public void dispose(){
494        InstanceManager.getDefault(LayoutBlockManager.class).removePropertyChangeListener(propertyBlockManagerListener);
495        InstanceManager.getDefault(SignalMastManager.class).removeVetoableChangeListener(this);
496        InstanceManager.getDefault(TurnoutManager.class).removeVetoableChangeListener(this);
497        super.dispose();
498    }
499
500    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultSignalMastLogicManager.class);
501}