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