001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.event.ActionEvent;
006import java.beans.*;
007import java.util.*;
008
009import javax.annotation.CheckForNull;
010import javax.annotation.Nonnull;
011import javax.swing.*;
012import javax.swing.colorchooser.AbstractColorChooserPanel;
013
014import jmri.*;
015import jmri.implementation.AbstractNamedBean;
016import jmri.jmrit.beantable.beanedit.*;
017import jmri.jmrit.roster.RosterEntry;
018import jmri.swing.NamedBeanComboBox;
019import jmri.util.MathUtil;
020import jmri.util.swing.JmriColorChooser;
021import jmri.util.swing.JmriJOptionPane;
022import jmri.util.swing.SplitButtonColorChooserPanel;
023
024import org.slf4j.MDC;
025
026/**
027 * A LayoutBlock is a group of track segments and turnouts on a LayoutEditor
028 * panel corresponding to a 'block'. LayoutBlock is a LayoutEditor specific
029 * extension of the JMRI Block object.
030 * <p>
031 * LayoutBlocks may have an occupancy Sensor. The getOccupancy method returns
032 * the occupancy state of the LayoutBlock - OCCUPIED, EMPTY, or UNKNOWN. If no
033 * occupancy sensor is provided, UNKNOWN is returned. The occupancy sensor if
034 * there is one, is the same as the occupancy sensor of the corresponding JMRI
035 * Block.
036 * <p>
037 * The name of each Layout Block is the same as that of the corresponding block
038 * as defined in Layout Editor. A corresponding JMRI Block object is created
039 * when a LayoutBlock is created. The JMRI Block uses the name of the block
040 * defined in Layout Editor as its user name and a unique IBnnn system name. The
041 * JMRI Block object and its associated Path objects are useful in tracking a
042 * train around the layout. Blocks may be viewed in the Block Table.
043 * <p>
044 * A LayoutBlock may have an associated Memory object. This Memory object
045 * contains a string representing the current "value" of the corresponding JMRI
046 * Block object. If the value contains a train name, for example, displaying
047 * Memory objects associated with LayoutBlocks, and displayed near each Layout
048 * Block can follow a train around the layout, displaying its name when it is in
049 * the LayoutBlock.
050 * <p>
051 * LayoutBlocks are "cross-panel", similar to sensors and turnouts. A
052 * LayoutBlock may be used by more than one Layout Editor panel simultaneously.
053 * As a consequence, LayoutBlocks are saved with the configuration, not with a
054 * panel.
055 * <p>
056 * LayoutBlocks are used by TrackSegments, LevelXings, and LayoutTurnouts.
057 * LevelXings carry two LayoutBlock designations, which may be the same.
058 * LayoutTurnouts carry LayoutBlock designations also, one per turnout, except
059 * for double crossovers and slips which can have up to four.
060 * <p>
061 * LayoutBlocks carry a use count. The use count counts the number of track
062 * segments, layout turnouts, and levelcrossings which use the LayoutBlock. Only
063 * LayoutBlocks which have a use count greater than zero are saved when the
064 * configuration is saved.
065 *
066 * @author Dave Duchamp Copyright (c) 2004-2008
067 * @author George Warner Copyright (c) 2017-2019
068 */
069public class LayoutBlock extends AbstractNamedBean implements PropertyChangeListener {
070
071    private final boolean enableAddRouteLogging = false;
072    private final boolean enableUpdateRouteLogging = false;
073    private boolean enableDeleteRouteLogging = false;
074    private final boolean enableSearchRouteLogging = false;
075
076    private static final List<Integer> updateReferences = new ArrayList<>(500);
077
078    // might want to use the jmri ordered HashMap, so that we can add at the top
079    // and remove at the bottom.
080    private final List<Integer> actedUponUpdates = new ArrayList<>(500);
081
082    public void enableDeleteRouteLog() {
083        enableDeleteRouteLogging = false;
084    }
085
086    public void disableDeleteRouteLog() {
087        enableDeleteRouteLogging = false;
088    }
089
090    // constants
091    public static final int OCCUPIED = Block.OCCUPIED;
092    public static final int EMPTY = Block.UNOCCUPIED;
093    // operational instance variables (not saved to disk)
094    private int useCount = 0;
095    private NamedBeanHandle<Sensor> occupancyNamedSensor = null;
096    private NamedBeanHandle<Memory> namedMemory = null;
097    private boolean setSensorFromBlockEnabled = true;     // Controls whether getOccupancySensor should get the sensor from the block
098
099    private Block block = null;
100
101    private final List<LayoutEditor> panels = new ArrayList<>(); // panels using this block
102    private PropertyChangeListener mBlockListener = null;
103    private int jmriblknum = 1;
104    private boolean useExtraColor = false;
105    private boolean suppressNameUpdate = false;
106
107    // persistent instances variables (saved between sessions)
108    private String occupancySensorName = "";
109    private String memoryName = "";
110    private int occupiedSense = Sensor.ACTIVE;
111    private Color blockTrackColor = Color.darkGray;
112    private Color blockOccupiedColor = Color.red;
113    private Color blockExtraColor = Color.white;
114
115    /**
116     * Creates a LayoutBlock object.
117     *
118     * Note: initializeLayoutBlock() must be called to complete the process. They are split
119     *       so  that loading of panel files will be independent of whether LayoutBlocks or
120     *       Blocks are loaded first.
121     * @param sName System name of this LayoutBlock
122     * @param uName User name of this LayoutBlock but also the user name of the associated Block
123     */
124    public LayoutBlock(String sName, String uName) {
125        super(sName, uName);
126    }
127
128    /**
129     * Completes the creation of a LayoutBlock object by adding a Block to it.
130     *
131     * The block create process takes into account that the _bean register
132     * process considers IB1 and IB01 to be the same name which results in a
133     * silent failure.
134     */
135    public void initializeLayoutBlock() {
136        // get/create a Block object corresponding to this LayoutBlock
137        block = null;   // assume failure (pessimist!)
138        String userName = getUserName();
139        if ((userName != null) && !userName.isEmpty()) {
140            block = InstanceManager.getDefault(BlockManager.class).getByUserName(userName);
141        }
142
143        if (block == null) {
144            // Not found, create a new Block
145            BlockManager bm = InstanceManager.getDefault(BlockManager.class);
146            String s;
147            while (true) {
148                if (jmriblknum > 50000) {
149                    throw new IndexOutOfBoundsException("Run away prevented while trying to create a block");
150                }
151                s = "IB" + jmriblknum;
152                jmriblknum++;
153
154                // Find an unused system name
155                block = bm.getBySystemName(s);
156                if (block != null) {
157                    log.debug("System name is already used: {}", s);
158                    continue;
159                }
160
161                // Create a new block.  User name is null to prevent user name checking.
162                block = bm.createNewBlock(s, null);
163                if (block == null) {
164                    log.debug("Null block returned: {}", s);
165                    continue;
166                }
167
168                // Verify registration
169                Block testGet = bm.getBySystemName(s);
170                if ( testGet!=null && bm.getNamedBeanSet().contains(testGet) ) {
171                    log.debug("Block is valid: {}", s);
172                    break;
173                }
174                log.debug("Registration failed: {}", s);
175            }
176            block.setUserName(getUserName());
177        }
178
179        // attach a listener for changes in the Block
180        mBlockListener = this::handleBlockChange;
181        block.addPropertyChangeListener(mBlockListener,
182                getUserName(), "Layout Block:" + getUserName());
183        if (occupancyNamedSensor != null) {
184            block.setNamedSensor(occupancyNamedSensor);
185        }
186    }
187
188    /* initializeLayoutBlockRouting */
189    public void initializeLayoutBlockRouting() {
190        if (!InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
191            return;
192        }
193        setBlockMetric();
194
195        block.getPaths().stream().forEach(this::addAdjacency);
196    }
197
198    /*
199     * Accessor methods
200     */
201    // TODO: deprecate and just use getUserName() directly
202    public String getId() {
203        return getUserName();
204    }
205
206    public Color getBlockTrackColor() {
207        return blockTrackColor;
208    }
209
210    public void setBlockTrackColor(Color color) {
211        blockTrackColor = color;
212        JmriColorChooser.addRecentColor(color);
213    }
214
215    public Color getBlockOccupiedColor() {
216        return blockOccupiedColor;
217    }
218
219    public void setBlockOccupiedColor(Color color) {
220        blockOccupiedColor = color;
221        JmriColorChooser.addRecentColor(color);
222    }
223
224    public Color getBlockExtraColor() {
225        return blockExtraColor;
226    }
227
228    public void setBlockExtraColor(Color color) {
229        blockExtraColor = color;
230        JmriColorChooser.addRecentColor(color);
231    }
232
233    // TODO: Java standard pattern for boolean getters is "useExtraColor()"
234    public boolean getUseExtraColor() {
235        return useExtraColor;
236    }
237
238    public void setUseExtraColor(boolean b) {
239        useExtraColor = b;
240
241        if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
242            stateUpdate();
243        }
244        if (getBlock() != null) {
245            getBlock().setAllocated(b);
246        }
247    }
248
249    /* setUseExtraColor */
250    public void incrementUse() {
251        useCount++;
252    }
253
254    public void decrementUse() {
255        --useCount;
256        if (useCount <= 0) {
257            useCount = 0;
258        }
259    }
260
261    public int getUseCount() {
262        return useCount;
263    }
264
265    /**
266     * Keep track of LayoutEditor panels that are using this LayoutBlock.
267     *
268     * @param panel to keep track of
269     */
270    public void addLayoutEditor(LayoutEditor panel) {
271        // add to the panels list if not already there
272        if (!panels.contains(panel)) {
273            panels.add(panel);
274        }
275    }
276
277    public void deleteLayoutEditor(LayoutEditor panel) {
278        // remove from the panels list if there
279        if (panels.contains(panel)) {
280            panels.remove(panel);
281        }
282    }
283
284    public boolean isOnPanel(LayoutEditor panel) {
285        // returns true if this Layout Block is used on panel
286        return panels.contains(panel);
287    }
288
289    /**
290     * Redraw panels using this layout block.
291     */
292    public void redrawLayoutBlockPanels() {
293        panels.stream().forEach(LayoutEditor::redrawPanel);
294        firePropertyChange("redraw", null, null);
295    }
296
297    /**
298     * Validate that the supplied occupancy sensor name corresponds to an
299     * existing sensor and is unique among all blocks. If valid, returns the
300     * sensor and sets the block sensor name in the block. Else returns null,
301     * and does nothing to the block.
302     *
303     * @param sensorName to check
304     * @param openFrame  determines the <code>Frame</code> in which the dialog
305     *                   is displayed; if <code>null</code>, or if the
306     *                   <code>parentComponent</code> has no <code>Frame</code>,
307     *                   a default <code>Frame</code> is used
308     * @return the validated sensor
309     */
310    public Sensor validateSensor(String sensorName, Component openFrame) {
311        // check if anything entered
312        if ((sensorName == null) || sensorName.isEmpty()) {
313            // no sensor name entered
314            if (occupancyNamedSensor != null) {
315                setOccupancySensorName(null);
316            }
317            return null;
318        }
319
320        // get the sensor corresponding to this name
321        Sensor s = InstanceManager.sensorManagerInstance().getSensor(sensorName);
322        if (s == null) {
323            // There is no sensor corresponding to this name
324            JmriJOptionPane.showMessageDialog(openFrame,
325                    java.text.MessageFormat.format(Bundle.getMessage("Error7"),
326                            new Object[]{sensorName}),
327                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
328            return null;
329        }
330
331        // ensure that this sensor is unique among defined Layout Blocks
332        NamedBeanHandle<Sensor> savedNamedSensor = occupancyNamedSensor;
333        occupancyNamedSensor = null;
334        LayoutBlock b = InstanceManager.getDefault(LayoutBlockManager.class).
335                getBlockWithSensorAssigned(s);
336
337        if (b != this) {
338            if (b != null) {
339                if (b.getUseCount() > 0) {
340                    // new sensor is not unique, return to the old one
341                    occupancyNamedSensor = savedNamedSensor;
342                    JmriJOptionPane.showMessageDialog(openFrame,
343                            java.text.MessageFormat.format(Bundle.getMessage("Error6"),
344                                    new Object[]{sensorName, b.getId()}),
345                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
346                    return null;
347                } else {
348                    // the user is assigning a sensor which is already assigned to
349                    // layout block b. Layout block b is no longer in use so this
350                    // should be fine but it's technically possible to put
351                    // this discarded layout block back into service (possibly
352                    // by mistake) by entering its name in any edit layout block window.
353                    // That would cause a problem with the sensor being in use in
354                    // two active blocks, so as a precaution we remove the sensor
355                    // from the discarded block here.
356                    b.setOccupancySensorName(null);
357                }
358            }
359            // sensor is unique, or was only in use on a layout block not in use
360            setOccupancySensorName(sensorName);
361        }
362        return s;
363    }
364
365    /**
366     * Validate that the memory name corresponds to an existing memory. If
367     * valid, returns the memory. Else returns null, and notifies the user.
368     *
369     * @param memName   the memory name
370     * @param openFrame the frame to display any error dialog in
371     * @return the memory
372     */
373    public Memory validateMemory(String memName, Component openFrame) {
374        // check if anything entered
375        if ((memName == null) || memName.isEmpty()) {
376            // no memory entered
377            return null;
378        }
379        // get the memory corresponding to this name
380        Memory m = InstanceManager.memoryManagerInstance().getMemory(memName);
381        if (m == null) {
382            // There is no memory corresponding to this name
383            JmriJOptionPane.showMessageDialog(openFrame,
384                    java.text.MessageFormat.format(Bundle.getMessage("Error16"),
385                            new Object[]{memName}),
386                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
387            return null;
388        }
389        memoryName = memName;
390
391        // Go through the memory icons on the panel and see if any are linked to this layout block
392        if ((m != getMemory()) && (panels.size() > 0)) {
393            boolean updateall = false;
394            boolean found = false;
395            for (LayoutEditor panel : panels) {
396                for (MemoryIcon memIcon : panel.getMemoryLabelList()) {
397                    if (memIcon.getLayoutBlock() == this) {
398                        if (!updateall && !found) {
399                            int n = JmriJOptionPane.showConfirmDialog(
400                                    openFrame,
401                                    "Would you like to update all memory icons on the panel linked to the block to use the new one?",
402                                    "Update Memory Icons",
403                                    JmriJOptionPane.YES_NO_OPTION);
404                            // TODO I18N in Bundle.properties
405                            found = true;
406                            if (n == JmriJOptionPane.YES_OPTION ) {
407                                updateall = true;
408                            }
409                        }
410                        if (updateall) {
411                            memIcon.setMemory(memoryName);
412                        }
413                    }
414                }
415            }
416        }
417        return m;
418    }
419
420    /**
421     * Get the color for drawing items in this block. Returns color based on
422     * block occupancy.
423     *
424     * @return color for block
425     */
426    public Color getBlockColor() {
427        if (getOccupancy() == OCCUPIED) {
428            return blockOccupiedColor;
429        } else if (useExtraColor) {
430            return blockExtraColor;
431        } else {
432            return blockTrackColor;
433        }
434    }
435
436    /**
437     * Get the Block corresponding to this LayoutBlock.
438     *
439     * @return block
440     */
441    public Block getBlock() {
442        return block;
443    }
444
445    /**
446     * Returns Memory name
447     *
448     * @return name of memory
449     */
450    public String getMemoryName() {
451        if (namedMemory != null) {
452            return namedMemory.getName();
453        }
454        return memoryName;
455    }
456
457    /**
458     * Get Memory.
459     *
460     * @return memory bean
461     */
462    public Memory getMemory() {
463        if (namedMemory == null) {
464            setMemoryName(memoryName);
465        }
466        if (namedMemory != null) {
467            return namedMemory.getBean();
468        }
469        return null;
470    }
471
472    /**
473     * Add Memory by name.
474     *
475     * @param name for memory
476     */
477    public void setMemoryName(String name) {
478        if ((name == null) || name.isEmpty()) {
479            namedMemory = null;
480            memoryName = "";
481            return;
482        }
483        memoryName = name;
484        Memory memory = InstanceManager.memoryManagerInstance().getMemory(name);
485        if (memory != null) {
486            namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, memory);
487        }
488    }
489
490    public void setMemory(Memory m, String name) {
491        if (m == null) {
492            namedMemory = null;
493            memoryName = name == null ? "" : name;
494            return;
495        }
496        namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, m);
497    }
498
499    /**
500     * Get occupancy Sensor name.
501     *
502     * @return name of occupancy sensor
503     */
504    public String getOccupancySensorName() {
505        if (occupancyNamedSensor == null) {
506            if (block != null) {
507                occupancyNamedSensor = block.getNamedSensor();
508            }
509        }
510        if (occupancyNamedSensor != null) {
511            return occupancyNamedSensor.getName();
512        }
513        return occupancySensorName;
514    }
515
516    /**
517     * Get occupancy Sensor.
518     * <p>
519     * If a sensor has not been assigned, try getting the sensor from the related
520     * block.
521     * <p>
522     * When setting the layout block sensor from the block itself using the OccupancySensorChange
523     * event, the automatic assignment has to be disabled for the sensor checking performed by
524     * {@link jmri.jmrit.display.layoutEditor.LayoutBlockManager#getBlockWithSensorAssigned}
525     *
526     * @return occupancy sensor or null
527     */
528    public Sensor getOccupancySensor() {
529        if (occupancyNamedSensor == null && setSensorFromBlockEnabled) {
530            if (block != null) {
531                occupancyNamedSensor = block.getNamedSensor();
532            }
533        }
534        if (occupancyNamedSensor != null) {
535            return occupancyNamedSensor.getBean();
536        }
537        return null;
538    }
539
540    /**
541     * Add occupancy sensor by name.
542     *
543     * @param name for senor to add
544     */
545    public void setOccupancySensorName(String name) {
546        if ((name == null) || name.isEmpty()) {
547            if (occupancyNamedSensor != null) {
548                occupancyNamedSensor.getBean().removePropertyChangeListener(mBlockListener);
549            }
550            occupancyNamedSensor = null;
551            occupancySensorName = "";
552
553            if (block != null) {
554                block.setNamedSensor(null);
555            }
556            return;
557        }
558        occupancySensorName = name;
559        Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(name);
560        if (sensor != null) {
561            occupancyNamedSensor = InstanceManager.getDefault(
562                    NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor);
563            if (block != null) {
564                block.setNamedSensor(occupancyNamedSensor);
565            }
566        }
567    }
568
569    /**
570     * Get occupied sensor state.
571     *
572     * @return occupied sensor state, defaults to Sensor.ACTIVE
573     */
574    public int getOccupiedSense() {
575        return occupiedSense;
576    }
577
578    /**
579     * Set occupied sensor state.
580     *
581     * @param sense eg. Sensor.INACTIVE
582     */
583    public void setOccupiedSense(int sense) {
584        occupiedSense = sense;
585    }
586
587    /**
588     * Test block occupancy.
589     *
590     * @return occupancy state
591     */
592    public int getOccupancy() {
593        if (occupancyNamedSensor == null) {
594            Sensor s = null;
595            if (!occupancySensorName.isEmpty()) {
596                s = InstanceManager.sensorManagerInstance().getSensor(occupancySensorName);
597            }
598            if (s == null) {
599                // no occupancy sensor, so base upon block occupancy state
600                if (block != null) {
601                    return block.getState();
602                }
603                // if no block or sensor return unknown
604                return UNKNOWN;
605            }
606            occupancyNamedSensor = InstanceManager.getDefault(
607                    NamedBeanHandleManager.class).getNamedBeanHandle(occupancySensorName, s);
608            if (block != null) {
609                block.setNamedSensor(occupancyNamedSensor);
610            }
611        }
612
613        Sensor s = getOccupancySensor();
614        if ( s == null) {
615            return UNKNOWN;
616        }
617
618        if (s.getKnownState() != occupiedSense) {
619            return EMPTY;
620        } else if (s.getKnownState() == occupiedSense) {
621            return OCCUPIED;
622        }
623        return UNKNOWN;
624    }
625
626    @Override
627    public int getState() {
628        return getOccupancy();
629    }
630
631    /**
632     * Does nothing, do not use.Dummy for completion of NamedBean interface
633     * @param i does nothing
634     */
635    @Override
636    public void setState(int i) {
637        log.error("this state does nothing {}", getDisplayName());
638    }
639
640    /**
641     * Get the panel with the highest connectivity to this Layout Block.
642     *
643     * @return panel with most connections to this block
644     */
645    public LayoutEditor getMaxConnectedPanel() {
646        LayoutEditor result = null;
647        // a block is attached and this LayoutBlock is used
648        if ((block != null) && (panels.size() > 0)) {
649            // initialize connectivity as defined in first Layout Editor panel
650            int maxConnectivity = Integer.MIN_VALUE;
651            for (LayoutEditor panel : panels) {
652                List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
653                if (maxConnectivity < c.size()) {
654                    maxConnectivity = c.size();
655                    result = panel;
656                }
657            }
658        }
659        return result;
660    }
661
662    /**
663     * Check/Update Path objects for the attached Block
664     * <p>
665     * If multiple panels are present, Paths are set according to the panel with
666     * the highest connectivity (most LayoutConnectivity objects).
667     */
668    public void updatePaths() {
669        // Update paths is called by the panel, turnouts, xings, track segments etc
670        if ((block != null) && !panels.isEmpty()) {
671            // a block is attached and this LayoutBlock is used
672            // initialize connectivity as defined in first Layout Editor panel
673            LayoutEditor panel = panels.get(0);
674            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
675
676            // if more than one panel, find panel with the highest connectivity
677            if (panels.size() > 1) {
678                for (int i = 1; i < panels.size(); i++) {
679                    if (c.size() < panels.get(i).getLEAuxTools().
680                            getConnectivityList(this).size()) {
681                        panel = panels.get(i);
682                        c = panel.getLEAuxTools().getConnectivityList(this);
683                    }
684                }
685
686                // Now try to determine if this block is across two panels due to a linked point
687                PositionablePoint point = panel.getFinder().findPositionableLinkPoint(this);
688                if ((point != null) && (point.getLinkedEditor() != null) && panels.contains(point.getLinkedEditor())) {
689                    c = panel.getLEAuxTools().getConnectivityList(this);
690                    c.addAll(point.getLinkedEditor().getLEAuxTools().getConnectivityList(this));
691                } else {
692                    // check that this connectivity is compatible with that of other panels.
693                    for (LayoutEditor tPanel : panels) {
694                        if ((tPanel != panel) && InstanceManager.getDefault(
695                                LayoutBlockManager.class).warn()
696                                && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) {
697                            // send user an error message
698                            int response = JmriJOptionPane.showOptionDialog(null,
699                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
700                                    new Object[]{getUserName(), tPanel.getLayoutName(), panel.getLayoutName()}),
701                                    Bundle.getMessage("WarningTitle"),
702                                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
703                                    null,
704                                    new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
705                                    Bundle.getMessage("ButtonOK"));
706                            if (response == 1 ) { // ButtokOKPlus pressed, user elected to disable messages
707                                InstanceManager.getDefault(
708                                        LayoutBlockManager.class).turnOffWarning();
709                            }
710                        }
711                    }
712                }
713            }
714            // update block Paths to reflect connectivity as needed
715            updateBlockPaths(c, panel);
716        }
717    }
718
719    /**
720     * Check/Update Path objects for the attached Block using the connectivity
721     * in the specified Layout Editor panel.
722     *
723     * @param panel to extract paths
724     */
725    public void updatePathsUsingPanel(LayoutEditor panel) {
726        if (panel == null) {
727            log.error("Null panel in call to updatePathsUsingPanel");
728            return;
729        }
730        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
731        updateBlockPaths(c, panel);
732
733    }
734
735    private void updateBlockPaths(List<LayoutConnectivity> c, LayoutEditor panel) {
736        if (enableAddRouteLogging) {
737            log.info("From {} updateBlockPaths Called", this.getDisplayName());
738        }
739        auxTools = panel.getLEAuxTools();
740        List<Path> paths = block.getPaths();
741        boolean[] used = new boolean[c.size()];
742        int[] need = new int[paths.size()];
743        Arrays.fill(used, false);
744        Arrays.fill(need, -1);
745
746        // cycle over existing Paths, checking against LayoutConnectivity
747        for (int i = 0; i < paths.size(); i++) {
748            Path p = paths.get(i);
749
750            // cycle over LayoutConnectivity matching to this Path
751            for (int j = 0; ((j < c.size()) && (need[i] == -1)); j++) {
752                if (!used[j]) {
753                    // this LayoutConnectivity not used yet
754                    LayoutConnectivity lc = c.get(j);
755                    if ((lc.getBlock1().getBlock() == p.getBlock()) || (lc.getBlock2().getBlock() == p.getBlock())) {
756                        // blocks match - record
757                        used[j] = true;
758                        need[i] = j;
759                    }
760                }
761            }
762        }
763
764        // update needed Paths
765        for (int i = 0; i < paths.size(); i++) {
766            if (need[i] >= 0) {
767                Path p = paths.get(i);
768                LayoutConnectivity lc = c.get(need[i]);
769                if (lc.getBlock1() == this) {
770                    p.setToBlockDirection(lc.getDirection());
771                    p.setFromBlockDirection(lc.getReverseDirection());
772                } else {
773                    p.setToBlockDirection(lc.getReverseDirection());
774                    p.setFromBlockDirection(lc.getDirection());
775                }
776                List<BeanSetting> beans = new ArrayList<>(p.getSettings());
777                for (BeanSetting bean : beans) {
778                    p.removeSetting(bean);
779                }
780                auxTools.addBeanSettings(p, lc, this);
781            }
782        }
783        // delete unneeded Paths
784        for (int i = 0; i < paths.size(); i++) {
785            if (need[i] < 0) {
786                block.removePath(paths.get(i));
787                if (InstanceManager.getDefault(
788                        LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
789                    removeAdjacency(paths.get(i));
790                }
791            }
792        }
793
794        // add Paths as required
795        for (int j = 0; j < c.size(); j++) {
796            if (!used[j]) {
797                // there is no corresponding Path, add one.
798                LayoutConnectivity lc = c.get(j);
799                Path newp;
800
801                if (lc.getBlock1() == this) {
802                    newp = new Path(lc.getBlock2().getBlock(), lc.getDirection(),
803                            lc.getReverseDirection());
804                } else {
805                    newp = new Path(lc.getBlock1().getBlock(), lc.getReverseDirection(),
806                            lc.getDirection());
807                }
808                block.addPath(newp);
809
810                if (enableAddRouteLogging) {
811                    log.info("From {} addPath({})", this.getDisplayName(), newp.toString());
812                }
813
814                if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
815                    addAdjacency(newp);
816                }
817                auxTools.addBeanSettings(newp, lc, this);
818            }
819        }
820
821        // djd debugging - lists results of automatic initialization of Paths and BeanSettings
822        if (log.isDebugEnabled()) {
823            block.getPaths().stream().forEach((p) -> log.debug("From {} to {}", getDisplayName(), p.toString()));
824        }
825    }
826
827    /**
828     * Make sure all the layout connectivity objects in test are in main.
829     *
830     * @param main the main list of LayoutConnectivity objects
831     * @param test the test list of LayoutConnectivity objects
832     * @return true if all test layout connectivity objects are in main
833     */
834    private boolean compareConnectivity(List<LayoutConnectivity> main, List<LayoutConnectivity> test) {
835        boolean result = false;     // assume failure (pessimsit!)
836        if (!main.isEmpty() && !test.isEmpty()) {
837            result = true;          // assume success (optimist!)
838            // loop over connectivities in test list
839            for (LayoutConnectivity tc : test) {
840                LayoutBlock tlb1 = tc.getBlock1(), tlb2 = tc.getBlock2();
841                // loop over main list to make sure the same blocks are connected
842                boolean found = false;  // assume failure (pessimsit!)
843                for (LayoutConnectivity mc : main) {
844                    LayoutBlock mlb1 = mc.getBlock1(), mlb2 = mc.getBlock2();
845                    if (((tlb1 == mlb1) && (tlb2 == mlb2))
846                            || ((tlb1 == mlb2) && (tlb2 == mlb1))) {
847                        found = true;   // success!
848                        break;
849                    }
850                }
851                if (!found) {
852                    result = false;
853                    break;
854                }
855            }
856        } else if (main.isEmpty() && test.isEmpty()) {
857            result = true;          // OK if both have no neighbors, common for turntable rays
858        }
859        return result;
860    }
861
862    /**
863     * Handle tasks when block changes
864     *
865     * @param e propChgEvent
866     */
867    void handleBlockChange(PropertyChangeEvent e) {
868        // Update memory object if there is one
869        Memory m = getMemory();
870        if ((m != null) && (block != null) && !suppressNameUpdate) {
871            // copy block value to memory if there is a value
872            Object val = block.getValue();
873            if (val != null) {
874                if (!(val instanceof RosterEntry) && !(val instanceof Reportable)) {
875                    val = val.toString();
876                }
877            }
878            m.setValue(val);
879        }
880
881        if (e.getPropertyName().equals("UserName")) {
882            setUserName(e.getNewValue().toString());
883            InstanceManager.getDefault(NamedBeanHandleManager.class).
884                    renameBean(e.getOldValue().toString(), e.getNewValue().toString(), this);
885        }
886
887        if (e.getPropertyName().equals("OccupancySensorChange")) {
888            if (e.getNewValue() == null){
889                // Remove Sensor
890                setOccupancySensorName(null);
891            } else {
892                // Set/change sensor
893                Sensor sensor = (Sensor) e.getNewValue();
894                setSensorFromBlockEnabled = false;
895                if (validateSensor(sensor.getSystemName(), null) == null) {
896                    // Sensor change rejected, reset block sensor assignment
897                    Sensor origSensor = (Sensor) e.getOldValue();
898                    block.setSensor(origSensor == null ? "" : origSensor.getSystemName());
899                }
900                setSensorFromBlockEnabled = true;
901            }
902        }
903
904        // Redraw all Layout Editor panels using this Layout Block
905        redrawLayoutBlockPanels();
906
907        if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
908            stateUpdate();
909        }
910    }
911
912    /**
913     * Deactivate block listener for redraw of panels and update of memories on
914     * change of state
915     */
916    private void deactivateBlock() {
917        if ((mBlockListener != null) && (block != null)) {
918            block.removePropertyChangeListener(mBlockListener);
919        }
920        mBlockListener = null;
921    }
922
923    /**
924     * Set/reset update of memory name when block goes from occupied to
925     * unoccupied or vice versa. If set is true, name update is suppressed. If
926     * set is false, name update works normally.
927     *
928     * @param set true, update suppress. false, update normal
929     */
930    public void setSuppressNameUpdate(boolean set) {
931        suppressNameUpdate = set;
932    }
933
934
935    private final NamedBeanComboBox<Memory> memoryComboBox = new NamedBeanComboBox<>(
936            InstanceManager.getDefault(MemoryManager.class), null, DisplayOptions.DISPLAYNAME);
937
938    private final JTextField metricField = new JTextField(10);
939
940    private final JComboBox<String> senseBox = new JComboBox<>();
941
942    // TODO I18N in Bundle.properties
943    private int senseActiveIndex;
944    private int senseInactiveIndex;
945
946    private JColorChooser trackColorChooser = null;
947    private JColorChooser occupiedColorChooser = null;
948    private JColorChooser extraColorChooser = null;
949
950    public void editLayoutBlock(Component callingPane) {
951        LayoutBlockEditAction beanEdit = new LayoutBlockEditAction();
952        if (block == null) {
953            // Block may not have been initialised due to an error so manually set it in the edit window
954            String userName = getUserName();
955            if ((userName != null) && !userName.isEmpty()) {
956                Block b = InstanceManager.getDefault(BlockManager.class).getBlock(userName);
957                if (b != null) {
958                    beanEdit.setBean(b);
959                }
960            }
961        } else {
962            beanEdit.setBean(block);
963        }
964        beanEdit.actionPerformed(null);
965    }
966
967    private final String[] working = {"Bi-Directional", "Receive Only", "Send Only"};
968
969    // TODO I18N in ManagersBundle.properties
970    protected List<JComboBox<String>> neighbourDir;
971
972    protected class LayoutBlockEditAction extends BlockEditAction {
973
974        @Override
975        public String helpTarget() {
976            return "package.jmri.jmrit.display.EditLayoutBlock";
977        }  // NOI18N
978
979        @Override
980        protected void initPanels() {
981            super.initPanels();
982            BeanItemPanel ld = layoutDetails();
983            if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
984                blockRoutingDetails();
985            }
986            setSelectedComponent(ld);
987        }
988
989        BeanItemPanel layoutDetails() {
990            BeanItemPanel layout = new BeanItemPanel();
991            layout.setName(Bundle.getMessage("LayoutEditor"));
992
993            LayoutEditor.setupComboBox(memoryComboBox, false, true, false);
994
995            layout.addItem(new BeanEditItem(new JLabel("" + useCount), Bundle.getMessage("UseCount"), null));
996            layout.addItem(new BeanEditItem(memoryComboBox, Bundle.getMessage("BeanNameMemory"),
997                    Bundle.getMessage("MemoryVariableTip")));
998
999            senseBox.removeAllItems();
1000            senseBox.addItem(Bundle.getMessage("SensorStateActive"));
1001            senseActiveIndex = 0;
1002            senseBox.addItem(Bundle.getMessage("SensorStateInactive"));
1003            senseInactiveIndex = 1;
1004
1005            layout.addItem(new BeanEditItem(senseBox, Bundle.getMessage("OccupiedSense"), Bundle.getMessage("OccupiedSenseHint")));
1006
1007            trackColorChooser = new JColorChooser(blockTrackColor);
1008            trackColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1009            AbstractColorChooserPanel[] trackColorPanels = {new SplitButtonColorChooserPanel()};
1010            trackColorChooser.setChooserPanels(trackColorPanels);
1011            layout.addItem(new BeanEditItem(trackColorChooser, Bundle.getMessage("TrackColor"), Bundle.getMessage("TrackColorHint")));
1012
1013            occupiedColorChooser = new JColorChooser(blockOccupiedColor);
1014            occupiedColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1015            AbstractColorChooserPanel[] occupiedColorPanels = {new SplitButtonColorChooserPanel()};
1016            occupiedColorChooser.setChooserPanels(occupiedColorPanels);
1017            layout.addItem(new BeanEditItem(occupiedColorChooser, Bundle.getMessage("OccupiedColor"), Bundle.getMessage("OccupiedColorHint")));
1018
1019            extraColorChooser = new JColorChooser(blockExtraColor);
1020            extraColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1021            AbstractColorChooserPanel[] extraColorPanels = {new SplitButtonColorChooserPanel()};
1022            extraColorChooser.setChooserPanels(extraColorPanels);
1023            layout.addItem(new BeanEditItem(extraColorChooser, Bundle.getMessage("ExtraColor"), Bundle.getMessage("ExtraColorHint")));
1024
1025            layout.setSaveItem(new AbstractAction() {
1026                @Override
1027                public void actionPerformed(ActionEvent e) {
1028                    boolean needsRedraw = false;
1029                    int k = senseBox.getSelectedIndex();
1030                    int oldSense = occupiedSense;
1031
1032                    if (k == senseActiveIndex) {
1033                        occupiedSense = Sensor.ACTIVE;
1034                    } else {
1035                        occupiedSense = Sensor.INACTIVE;
1036                    }
1037
1038                    if (oldSense != occupiedSense) {
1039                        needsRedraw = true;
1040                    }
1041                    // check if track color changed
1042                    Color oldColor = blockTrackColor;
1043                    blockTrackColor = trackColorChooser.getColor();
1044                    if (oldColor != blockTrackColor) {
1045                        needsRedraw = true;
1046                        JmriColorChooser.addRecentColor(blockTrackColor);
1047                    }
1048                    // check if occupied color changed
1049                    oldColor = blockOccupiedColor;
1050                    blockOccupiedColor = occupiedColorChooser.getColor();
1051                    if (oldColor != blockOccupiedColor) {
1052                        needsRedraw = true;
1053                        JmriColorChooser.addRecentColor(blockOccupiedColor);
1054                    }
1055                    // check if extra color changed
1056                    oldColor = blockExtraColor;
1057                    blockExtraColor = extraColorChooser.getColor();
1058                    if (oldColor != blockExtraColor) {
1059                        needsRedraw = true;
1060                        JmriColorChooser.addRecentColor(blockExtraColor);
1061                    }
1062                    // check if Memory changed
1063                    String newName = memoryComboBox.getSelectedItemDisplayName();
1064                    if (newName == null) {
1065                        newName = "";
1066                    }
1067                    if (!memoryName.equals(newName)) {
1068                        // memory has changed
1069                        setMemory(validateMemory(newName, null), newName);
1070                        if (getMemory() == null) {
1071                            // invalid memory entered
1072                            memoryName = "";
1073                            memoryComboBox.setSelectedItem(null);
1074                            return;
1075                        } else {
1076                            memoryComboBox.setSelectedItem(getMemory());
1077                            needsRedraw = true;
1078                        }
1079                    }
1080
1081                    if (needsRedraw) {
1082                        redrawLayoutBlockPanels();
1083                    }
1084                }
1085            });
1086
1087            layout.setResetItem(new AbstractAction() {
1088                @Override
1089                public void actionPerformed(ActionEvent e) {
1090                    memoryComboBox.setSelectedItem(getMemory());
1091                    trackColorChooser.setColor(blockTrackColor);
1092                    occupiedColorChooser.setColor(blockOccupiedColor);
1093                    extraColorChooser.setColor(blockExtraColor);
1094                    if (occupiedSense == Sensor.ACTIVE) {
1095                        senseBox.setSelectedIndex(senseActiveIndex);
1096                    } else {
1097                        senseBox.setSelectedIndex(senseInactiveIndex);
1098                    }
1099                }
1100            });
1101            bei.add(layout);
1102            return layout;
1103        }
1104
1105        BeanItemPanel blockRoutingDetails() {
1106            BeanItemPanel routing = new BeanItemPanel();
1107            routing.setName("Routing");
1108
1109            routing.addItem(new BeanEditItem(metricField, "Block Metric", "set the cost for going over this block"));
1110
1111            routing.addItem(new BeanEditItem(null, null, "Set the direction of the connection to the neighbouring block"));
1112            neighbourDir = new ArrayList<>(getNumberOfNeighbours());
1113            for (int i = 0; i < getNumberOfNeighbours(); i++) {
1114                JComboBox<String> dir = new JComboBox<>(working);
1115                routing.addItem(new BeanEditItem(dir, getNeighbourAtIndex(i).getDisplayName(), null));
1116                neighbourDir.add(dir);
1117            }
1118
1119            routing.setResetItem(new AbstractAction() {
1120                @Override
1121                public void actionPerformed(ActionEvent e) {
1122                    metricField.setText(Integer.toString(metric));
1123                    for (int i = 0; i < getNumberOfNeighbours(); i++) {
1124                        JComboBox<String> dir = neighbourDir.get(i);
1125                        Block blk = neighbours.get(i).getBlock();
1126                        if (block.isBlockDenied(blk)) {
1127                            dir.setSelectedIndex(2);
1128                        } else if (blk.isBlockDenied(block)) {
1129                            dir.setSelectedIndex(1);
1130                        } else {
1131                            dir.setSelectedIndex(0);
1132                        }
1133                    }
1134                }
1135            });
1136
1137            routing.setSaveItem(new AbstractAction() {
1138                @Override
1139                public void actionPerformed(ActionEvent e) {
1140                    int m = Integer.parseInt(metricField.getText().trim());
1141                    if (m != metric) {
1142                        setBlockMetric(m);
1143                    }
1144                    if (neighbourDir != null) {
1145                        for (int i = 0; i < neighbourDir.size(); i++) {
1146                            int neigh = neighbourDir.get(i).getSelectedIndex();
1147                            neighbours.get(i).getBlock().removeBlockDenyList(block);
1148                            block.removeBlockDenyList(neighbours.get(i).getBlock());
1149                            switch (neigh) {
1150                                case 0: {
1151                                    updateNeighbourPacketFlow(neighbours.get(i), RXTX);
1152                                    break;
1153                                }
1154
1155                                case 1: {
1156                                    neighbours.get(i).getBlock().addBlockDenyList(block.getDisplayName());
1157                                    updateNeighbourPacketFlow(neighbours.get(i), TXONLY);
1158                                    break;
1159                                }
1160
1161                                case 2: {
1162                                    block.addBlockDenyList(neighbours.get(i).getBlock().getDisplayName());
1163                                    updateNeighbourPacketFlow(neighbours.get(i), RXONLY);
1164                                    break;
1165                                }
1166
1167                                default: {
1168                                    break;
1169                                }
1170                            }
1171                            /* switch */
1172                        }
1173                    }
1174                }
1175            });
1176            bei.add(routing);
1177            return routing;
1178        }
1179    }
1180
1181    /**
1182     * Remove this object from display and persistance.
1183     */
1184    void remove() {
1185        // if an occupancy sensor has been activated, deactivate it
1186        deactivateBlock();
1187        // remove from persistance by flagging inactive
1188        active = false;
1189    }
1190
1191    boolean active = true;
1192
1193    /**
1194     * "active" is true if the object is still displayed, and should be stored.
1195     *
1196     * @return active
1197     */
1198    public boolean isActive() {
1199        return active;
1200    }
1201
1202    /*
1203      The code below relates to the layout block routing protocol
1204     */
1205    /**
1206     * Set the block metric based upon the track segment that the block is
1207     * associated with if the (200 if Side, 50 if Main). If the block is
1208     * assigned against multiple track segments all with different types then
1209     * the highest type will be used. In theory no reason why it couldn't be a
1210     * compromise.
1211     */
1212    void setBlockMetric() {
1213        if (!defaultMetric) {
1214            return;
1215        }
1216        if (enableUpdateRouteLogging) {
1217            log.info("From '{}' default set block metric called", this.getDisplayName());
1218        }
1219        LayoutEditor panel = getMaxConnectedPanel();
1220        if (panel == null) {
1221            if (enableUpdateRouteLogging) {
1222                log.info("From '{}' unable to set metric as we are not connected to a panel yet", this.getDisplayName());
1223            }
1224            return;
1225        }
1226        String userName = getUserName();
1227        if (userName == null) {
1228            log.info("From '{}': unable to get user name", this.getDisplayName());
1229            return;
1230        }
1231        List<TrackSegment> ts = panel.getFinder().findTrackSegmentByBlock(userName);
1232        int mainline = 0;
1233        int side = 0;
1234
1235        for (TrackSegment t : ts) {
1236            if (t.isMainline()) {
1237                mainline++;
1238            } else {
1239                side++;
1240            }
1241        }
1242
1243        if (mainline > side) {
1244            metric = 50;
1245        } else if (mainline < side) {
1246            metric = 200;
1247        } else {
1248            // They must both be equal so will set as a mainline.
1249            metric = 50;
1250        }
1251
1252        if (enableUpdateRouteLogging) {
1253            log.info("From '{}' metric set to {}", this.getDisplayName(), metric);
1254        }
1255
1256        // What we need to do here, is resend our routing packets with the new metric
1257        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID());
1258        firePropertyChange("routing", null, update);
1259    }
1260
1261    private boolean defaultMetric = true;
1262
1263    public boolean useDefaultMetric() {
1264        return defaultMetric;
1265    }
1266
1267    public void useDefaultMetric(boolean boo) {
1268        if (boo == defaultMetric) {
1269            return;
1270        }
1271        defaultMetric = boo;
1272        if (boo) {
1273            setBlockMetric();
1274        }
1275    }
1276
1277    /**
1278     * Set a metric cost against a block, this is used in the calculation of a
1279     * path between two location on the layout, a lower path cost is always
1280     * preferred For Layout blocks defined as Mainline the default metric is 50.
1281     * For Layout blocks defined as a Siding the default metric is 200.
1282     *
1283     * @param m metric value
1284     */
1285    public void setBlockMetric(int m) {
1286        if (metric == m) {
1287            return;
1288        }
1289        metric = m;
1290        defaultMetric = false;
1291        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID());
1292        firePropertyChange("routing", null, update);
1293    }
1294
1295    /**
1296     * Get the layout block metric cost
1297     *
1298     * @return metric cost of block
1299     */
1300    public int getBlockMetric() {
1301        return metric;
1302    }
1303
1304    // re work this so that is makes beter us of existing code.
1305    // This is no longer required currently, but might be used at a later date.
1306    public void addAllThroughPaths() {
1307        if (enableAddRouteLogging) {
1308            log.info("Add all ThroughPaths {}", this.getDisplayName());
1309        }
1310
1311        if ((block != null) && (panels.size() > 0)) {
1312            // a block is attached and this LayoutBlock is used
1313            // initialize connectivity as defined in first Layout Editor panel
1314            LayoutEditor panel = panels.get(0);
1315            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
1316
1317            // if more than one panel, find panel with the highest connectivity
1318            if (panels.size() > 1) {
1319                for (int i = 1; i < panels.size(); i++) {
1320                    if (c.size() < panels.get(i).getLEAuxTools().
1321                            getConnectivityList(this).size()) {
1322                        panel = panels.get(i);
1323                        c = panel.getLEAuxTools().getConnectivityList(this);
1324                    }
1325                }
1326
1327                // check that this connectivity is compatible with that of other panels.
1328                for (LayoutEditor tPanel : panels) {
1329                    if ((tPanel != panel)
1330                            && InstanceManager.getDefault(LayoutBlockManager.class).
1331                                    warn() && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) {
1332
1333                        // send user an error message
1334                        int response = JmriJOptionPane.showOptionDialog(null,
1335                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
1336                                        new Object[]{getUserName(), tPanel.getLayoutName(),
1337                                            panel.getLayoutName()}), Bundle.getMessage("WarningTitle"),
1338                                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
1339                                null,
1340                                new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
1341                                Bundle.getMessage("ButtonOK"));
1342                        if (response == 1) { // array position 1 ButtonOKPlus pressed, user elected to disable messages
1343                            InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning();
1344                        }
1345                    }
1346                }
1347            }
1348            auxTools = panel.getLEAuxTools();
1349            List<LayoutConnectivity> d = auxTools.getConnectivityList(this);
1350            List<LayoutBlock> attachedBlocks = new ArrayList<>();
1351
1352            for (LayoutConnectivity connectivity : d) {
1353                if (connectivity.getBlock1() != this) {
1354                    attachedBlocks.add(connectivity.getBlock1());
1355                } else {
1356                    attachedBlocks.add(connectivity.getBlock2());
1357                }
1358            }
1359            // Will need to re-look at this to cover both way and single way routes
1360            for (LayoutBlock attachedBlock : attachedBlocks) {
1361                if (enableAddRouteLogging) {
1362                    log.info("From {} block is attached {}", this.getDisplayName(), attachedBlock.getDisplayName());
1363                }
1364
1365                for (LayoutBlock layoutBlock : attachedBlocks) {
1366                    addThroughPath(attachedBlock.getBlock(), layoutBlock.getBlock(), panel);
1367                }
1368            }
1369        }
1370    }
1371
1372    // TODO: if the block already exists, we still may want to re-work the through paths
1373    // With this bit we need to get our neighbour to send new routes
1374    private void addNeighbour(Block addBlock, int direction, int workingDirection) {
1375        boolean layoutConnectivityBefore = layoutConnectivity;
1376
1377        if (enableAddRouteLogging) {
1378            log.info("From {} asked to add block {} as new neighbour {}", this.getDisplayName(),
1379                    addBlock.getDisplayName(), decodePacketFlow(workingDirection));
1380        }
1381
1382        if (getAdjacency(addBlock) != null) {
1383            if (enableAddRouteLogging) {
1384                log.info("Block is already registered");
1385            }
1386            addThroughPath(getAdjacency(addBlock));
1387        } else {
1388            Adjacencies adj = new Adjacencies(addBlock, direction, workingDirection);
1389            neighbours.add(adj);
1390
1391            // Add the neighbour to our routing table.
1392            LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(addBlock);
1393            LayoutEditor editor = getMaxConnectedPanel();
1394
1395            if ((editor != null) && (connection == null)) {
1396                // We should be able to determine block metric now as the tracksegment should be valid
1397                connection = editor.getConnectivityUtil();
1398            }
1399
1400            // Need to inform our neighbours of our new addition
1401            // We only add an entry into the routing table if we are able to reach the next working block.
1402            // If we only transmit routes to it, then we can not route to it therefore it is not added
1403            Routes route = null;
1404
1405            if ((workingDirection == RXTX) || (workingDirection == RXONLY)) {
1406                if (blk != null) {
1407                    route = new Routes(addBlock, this.getBlock(), 1, direction, blk.getBlockMetric(), addBlock.getLengthMm());
1408                } else {
1409                    route = new Routes(addBlock, this.getBlock(), 1, direction, 0, 0);
1410                }
1411                routes.add(route);
1412            }
1413
1414            if (blk != null) {
1415                boolean mutual = blk.informNeighbourOfAttachment(this, this.getBlock(), workingDirection);
1416
1417                // The propertychange listener will have to be modified depending upon RX or TX selection.
1418                // if we only transmit routes to this neighbour then we do not want to listen to thier broadcast messages
1419                if ((workingDirection == RXTX) || (workingDirection == RXONLY)) {
1420                    blk.addPropertyChangeListener(this);
1421                    // log.info("From {} add property change {}", this.getDisplayName(), blk.getDisplayName());
1422                } else {
1423                    blk.removePropertyChangeListener(this);
1424                }
1425
1426                int neighwork = blk.getAdjacencyPacketFlow(this.getBlock());
1427                if (enableAddRouteLogging) {
1428                    log.info("{}.getAdjacencyPacketFlow({}): {}, {}",
1429                            blk.getDisplayName(), this.getBlock().getDisplayName(), decodePacketFlow(neighwork), neighwork);
1430                }
1431
1432                if (neighwork != -1) {
1433                    if (enableAddRouteLogging) {
1434                        log.info("From {} Updating flow direction to {} for block {} choice of {} {}", this.getDisplayName(),
1435                                decodePacketFlow(determineAdjPacketFlow(workingDirection, neighwork)),
1436                                blk.getDisplayName(), decodePacketFlow(workingDirection), decodePacketFlow(neighwork));
1437                    }
1438                    int newPacketFlow = determineAdjPacketFlow(workingDirection, neighwork);
1439                    adj.setPacketFlow(newPacketFlow);
1440
1441                    if (newPacketFlow == TXONLY) {
1442                        for (int j = routes.size() - 1; j > -1; j--) {
1443                            Routes ro = routes.get(j);
1444                            if ((ro.getDestBlock() == addBlock)
1445                                    && (ro.getNextBlock() == this.getBlock())) {
1446                                adj.removeRouteAdvertisedToNeighbour(ro);
1447                                routes.remove(j);
1448                            }
1449                        }
1450                        RoutingPacket newUpdate = new RoutingPacket(REMOVAL, addBlock, -1, -1, -1, -1, getNextPacketID());
1451                        neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(addBlock));
1452                        firePropertyChange("routing", null, newUpdate);
1453                    }
1454                } else {
1455                    if (enableAddRouteLogging) {
1456                        log.info("From {} neighbour {} working direction is not valid",
1457                                this.getDisplayName(), addBlock.getDisplayName());
1458                    }
1459                    return;
1460                }
1461                adj.setMutual(mutual);
1462
1463                if (route != null) {
1464                    route.stateChange();
1465                }
1466                addThroughPath(getAdjacency(addBlock));
1467                // We get our new neighbour to send us a list of valid routes that they have.
1468                // This might have to be re-written as a property change event?
1469                // Also only inform our neighbour if they have us down as a mutual, otherwise it will just reject the packet.
1470                if (((workingDirection == RXTX) || (workingDirection == TXONLY)) && mutual) {
1471                    blk.informNeighbourOfValidRoutes(getBlock());
1472                }
1473            } else if (enableAddRouteLogging) {
1474                log.info("From {} neighbour {} has no layoutBlock associated, metric set to {}",
1475                        this.getDisplayName(), addBlock.getDisplayName(), adj.getMetric());
1476            }
1477        }
1478
1479        /* If the connectivity before has not completed and produced an error with
1480           setting up through Paths, we will cycle through them */
1481        if (enableAddRouteLogging) {
1482            log.info("From {} layout connectivity before {}", this.getDisplayName(), layoutConnectivityBefore);
1483        }
1484        if (!layoutConnectivityBefore) {
1485            for (Adjacencies neighbour : neighbours) {
1486                addThroughPath(neighbour);
1487            }
1488        }
1489        /* We need to send our new neighbour our copy of the routing table however
1490           we can only send valid routes that would be able to traverse as definded by
1491           through paths table */
1492    }
1493
1494    private boolean informNeighbourOfAttachment(LayoutBlock lBlock, Block block, int workingDirection) {
1495        Adjacencies adj = getAdjacency(block);
1496        if (adj == null) {
1497            if (enableAddRouteLogging) {
1498                log.info("From {} neighbour {} has informed us of its attachment to us, however we do not yet have it registered",
1499                        this.getDisplayName(), lBlock.getDisplayName());
1500            }
1501            return false;
1502        }
1503
1504        if (!adj.isMutual()) {
1505            if (enableAddRouteLogging) {
1506                log.info("From {} neighbour {} wants us to {}; we have it set as {}",
1507                        this.getDisplayName(), block.getDisplayName(),
1508                        decodePacketFlow(workingDirection), decodePacketFlow(adj.getPacketFlow()));
1509            }
1510
1511            // Simply if both the neighbour and us both want to do the same thing with sending routing information,
1512            // in one direction then no routes will be passed
1513            int newPacketFlow = determineAdjPacketFlow(adj.getPacketFlow(), workingDirection);
1514            if (enableAddRouteLogging) {
1515                log.info("From {} neighbour {} passed {} we have {} this will be updated to {}", this.getDisplayName(), block.getDisplayName(), decodePacketFlow(workingDirection), decodePacketFlow(adj.getPacketFlow()), decodePacketFlow(newPacketFlow));
1516            }
1517            adj.setPacketFlow(newPacketFlow);
1518
1519            // If we are only set to transmit routing information to the adj, then
1520            // we will not have it appearing in the routing table
1521            if (newPacketFlow != TXONLY) {
1522                Routes neighRoute = getValidRoute(this.getBlock(), adj.getBlock());
1523                // log.info("From " + this.getDisplayName() + " neighbour " + adj.getBlock().getDisplayName() + " valid routes returned as " + neighRoute);
1524                if (neighRoute == null) {
1525                    log.info("Null route so will bomb out");
1526                    return false;
1527                }
1528
1529                if (neighRoute.getMetric() != adj.getMetric()) {
1530                    if (enableAddRouteLogging) {
1531                        log.info("From {} The value of the metric we have for this route is not correct {}, stored {} v {}", this.getDisplayName(), this.getBlock().getDisplayName(), neighRoute.getMetric(), adj.getMetric());
1532                    }
1533                    neighRoute.setMetric(adj.getMetric());
1534                    // This update might need to be more selective
1535                    RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, (adj.getMetric() + metric), -1, -1, getNextPacketID());
1536                    firePropertyChange("routing", null, update);
1537                }
1538
1539                if (neighRoute.getMetric() != (int) adj.getLength()) {
1540                    if (enableAddRouteLogging) {
1541                        log.info("From {} The value of the length we have for this route is not correct {}, stored {} v {}", this.getDisplayName(), this.getBlock().getDisplayName(), neighRoute.getMetric(), adj.getMetric());
1542                    }
1543                    neighRoute.setLength(adj.getLength());
1544                    // This update might need to be more selective
1545                    RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, -1,
1546                            adj.getLength() + block.getLengthMm(), -1, getNextPacketID());
1547                    firePropertyChange("routing", null, update);
1548                }
1549                Routes r = getRouteByDestBlock(block);
1550                if (r != null) {
1551                    r.setMetric(lBlock.getBlockMetric());
1552                } else {
1553                    log.warn("No getRouteByDestBlock('{}')", block.getDisplayName());
1554                }
1555            }
1556
1557            if (enableAddRouteLogging) {
1558                log.info("From {} We were not a mutual adjacency with {} but now are", this.getDisplayName(), lBlock.getDisplayName());
1559            }
1560
1561            if ((newPacketFlow == RXTX) || (newPacketFlow == RXONLY)) {
1562                lBlock.addPropertyChangeListener(this);
1563            } else {
1564                lBlock.removePropertyChangeListener(this);
1565            }
1566
1567            if (newPacketFlow == TXONLY) {
1568                for (int j = routes.size() - 1; j > -1; j--) {
1569                    Routes ro = routes.get(j);
1570                    if ((ro.getDestBlock() == block) && (ro.getNextBlock() == this.getBlock())) {
1571                        adj.removeRouteAdvertisedToNeighbour(ro);
1572                        routes.remove(j);
1573                    }
1574                }
1575
1576                for (int j = throughPaths.size() - 1; j > -1; j--) {
1577                    if ((throughPaths.get(j).getDestinationBlock() == block)) {
1578                        if (enableAddRouteLogging) {
1579                            log.info("From {} removed throughpath {} {}", this.getDisplayName(), throughPaths.get(j).getSourceBlock().getDisplayName(), throughPaths.get(j).getDestinationBlock().getDisplayName());
1580                        }
1581                        throughPaths.remove(j);
1582                    }
1583                }
1584                RoutingPacket newUpdate = new RoutingPacket(REMOVAL, block, -1, -1, -1, -1, getNextPacketID());
1585                neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(block));
1586                firePropertyChange("routing", null, newUpdate);
1587            }
1588
1589            adj.setMutual(true);
1590            addThroughPath(adj);
1591
1592            // As we are now mutual we will send our neigh a list of valid routes.
1593            if ((newPacketFlow == RXTX) || (newPacketFlow == TXONLY)) {
1594                if (enableAddRouteLogging) {
1595                    log.info("From {} inform neighbour of valid routes", this.getDisplayName());
1596                }
1597                informNeighbourOfValidRoutes(block);
1598            }
1599        }
1600        return true;
1601    }
1602
1603    private int determineAdjPacketFlow(int our, int neigh) {
1604        // Both are the same
1605        if (enableUpdateRouteLogging) {
1606            log.info("From {} values passed our {} neigh {}", this.getDisplayName(), decodePacketFlow(our), decodePacketFlow(neigh));
1607        }
1608        if ((our == RXTX) && (neigh == RXTX)) {
1609            return RXTX;
1610        }
1611
1612        /*First off reverse the neighbour flow, as it will be telling us if it will allow or deny traffic from us.
1613           So if it is set to RX, then we can TX to it.*/
1614        if (neigh == RXONLY) {
1615            neigh = TXONLY;
1616        } else if (neigh == TXONLY) {
1617            neigh = RXONLY;
1618        }
1619
1620        if (our == neigh) {
1621            return our;
1622        }
1623        return NONE;
1624    }
1625
1626    private void informNeighbourOfValidRoutes(Block newblock) {
1627        // java.sql.Timestamp t1 = new java.sql.Timestamp(System.nanoTime());
1628        List<Block> validFromPath = new ArrayList<>();
1629        if (enableAddRouteLogging) {
1630            log.info("From {} new block {}", this.getDisplayName(), newblock.getDisplayName());
1631        }
1632
1633        for (ThroughPaths tp : throughPaths) {
1634            if (enableAddRouteLogging) {
1635                log.info("From {} B through routes {} {}", this.getDisplayName(), tp.getSourceBlock().getDisplayName(), tp.getDestinationBlock().getDisplayName());
1636            }
1637
1638            if (tp.getSourceBlock() == newblock) {
1639                validFromPath.add(tp.getDestinationBlock());
1640            } else if (tp.getDestinationBlock() == newblock) {
1641                validFromPath.add(tp.getSourceBlock());
1642            }
1643        }
1644
1645        if (enableAddRouteLogging) {
1646            log.info("From {} ===== valid from size path {} ====", this.getDisplayName(), validFromPath.size());
1647            log.info(newblock.getDisplayName());
1648        }
1649
1650        // We only send packets on to our neighbour that are registered as being on a valid through path and are mutual.
1651        LayoutBlock lBnewblock = null;
1652        Adjacencies adj = getAdjacency(newblock);
1653        if (adj.isMutual()) {
1654            if (enableAddRouteLogging) {
1655                log.info("From {} adj with {} is mutual", this.getDisplayName(), newblock.getDisplayName());
1656            }
1657            lBnewblock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(newblock);
1658        } else if (enableAddRouteLogging) {
1659            log.info("From {} adj with {} is NOT mutual", this.getDisplayName(), newblock.getDisplayName());
1660        }
1661
1662        if (lBnewblock == null) {
1663            return;
1664        }
1665
1666        for (Routes ro : new ArrayList<>(routes)) {
1667            if (enableAddRouteLogging) {
1668                log.info("next:{} dest:{}", ro.getNextBlock().getDisplayName(), ro.getDestBlock().getDisplayName());
1669            }
1670
1671            if (ro.getNextBlock() == getBlock()) {
1672                if (enableAddRouteLogging) {
1673                    log.info("From {} ro next block is this", this.getDisplayName());
1674                }
1675                if (validFromPath.contains(ro.getDestBlock())) {
1676                    if (enableAddRouteLogging) {
1677                        log.info("From {} route to {} we have it with a metric of {} we will add our metric of {} this will be sent to {} a", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getMetric(), metric, lBnewblock.getDisplayName());
1678                    } // we added +1 to hop count and our metric.
1679
1680                    RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID());
1681                    lBnewblock.addRouteFromNeighbour(this, update);
1682                }
1683            } else {
1684                // Don't know if this might need changing so that we only send out our best
1685                // route to the neighbour, rather than cycling through them all.
1686                if (validFromPath.contains(ro.getNextBlock())) {
1687                    if (enableAddRouteLogging) {
1688                        log.info("From {} route to {} we have it with a metric of {} we will add our metric of {} this will be sent to {} b", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getMetric(), metric, lBnewblock.getDisplayName());
1689                    } // we added +1 to hop count and our metric.
1690                    if (adj.advertiseRouteToNeighbour(ro)) {
1691                        if (enableAddRouteLogging) {
1692                            log.info("Told to advertise to neighbour");
1693                        }
1694                        // this should keep track of the routes we sent to our neighbour.
1695                        adj.addRouteAdvertisedToNeighbour(ro);
1696                        RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID());
1697                        lBnewblock.addRouteFromNeighbour(this, update);
1698                    } else {
1699                        if (enableAddRouteLogging) {
1700                            log.info("Not advertised to neighbour");
1701                        }
1702                    }
1703                } else if (enableAddRouteLogging) {
1704                    log.info("failed valid from path Not advertised/added");
1705                }
1706            }
1707        }
1708    }
1709
1710    static long time = 0;
1711
1712    /**
1713     * Work out our direction of route flow correctly.
1714     */
1715    private void addAdjacency(Path addPath) {
1716        if (enableAddRouteLogging) {
1717            log.info("From {} path to be added {} {}", this.getDisplayName(), addPath.getBlock().getDisplayName(), Path.decodeDirection(addPath.getToBlockDirection()));
1718        }
1719
1720        Block destBlockToAdd = addPath.getBlock();
1721        int ourWorkingDirection = RXTX;
1722        if (destBlockToAdd == null) {
1723            log.error("Found null destination block for path from {}", this.getDisplayName());
1724            return;
1725        }
1726
1727        if (this.getBlock().isBlockDenied(destBlockToAdd.getDisplayName())) {
1728            ourWorkingDirection = RXONLY;
1729        } else if (destBlockToAdd.isBlockDenied(this.getBlock().getDisplayName())) {
1730            ourWorkingDirection = TXONLY;
1731        }
1732
1733        if (enableAddRouteLogging) {
1734            log.info("From {} to block {} we should therefore be... {}", this.getDisplayName(), addPath.getBlock().getDisplayName(), decodePacketFlow(ourWorkingDirection));
1735        }
1736        addNeighbour(addPath.getBlock(), addPath.getToBlockDirection(), ourWorkingDirection);
1737
1738    }
1739
1740    // Might be possible to refactor the removal to use a bit of common code.
1741    private void removeAdjacency(Path removedPath) {
1742        Block ablock = removedPath.getBlock();
1743        if (ablock != null) {
1744            if (enableDeleteRouteLogging) {
1745                log.info("From {} Adjacency to be removed {} {}", this.getDisplayName(), ablock.getDisplayName(), Path.decodeDirection(removedPath.getToBlockDirection()));
1746            }
1747            LayoutBlock layoutBlock = InstanceManager.getDefault(
1748                    LayoutBlockManager.class).getLayoutBlock(ablock);
1749            if (layoutBlock != null) {
1750                removeAdjacency(layoutBlock);
1751            }
1752        } else {
1753            log.debug("removeAdjacency() removedPath.getBlock() is null");
1754        }
1755    }
1756
1757    private void removeAdjacency(LayoutBlock layoutBlock) {
1758        if (enableDeleteRouteLogging) {
1759            log.info("From {} Adjacency to be removed {}", this.getDisplayName(), layoutBlock.getDisplayName());
1760        }
1761        Block removedBlock = layoutBlock.getBlock();
1762
1763        // Work our way backward through the list of neighbours
1764        // We need to work out which routes to remove first.
1765        // here we simply remove the routes which are advertised from the removed neighbour
1766        List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(removedBlock);
1767
1768        for (int i = neighbours.size() - 1; i > -1; i--) {
1769            // Use to check against direction but don't now.
1770            if ((neighbours.get(i).getBlock() == removedBlock)) {
1771                // Was previously before the for loop.
1772                // Pos move the remove list and remove thoughpath out of this for loop.
1773                layoutBlock.removePropertyChangeListener(this);
1774                if (enableDeleteRouteLogging) {
1775                    log.info("From {} block {} found and removed", this.getDisplayName(), removedBlock.getDisplayName());
1776                }
1777                LayoutBlock layoutBlockToNotify = InstanceManager.getDefault(
1778                        LayoutBlockManager.class).getLayoutBlock(neighbours.get(i).getBlock());
1779                if (layoutBlockToNotify==null){ // move to provides?
1780                    log.error("Unable to notify neighbours for block {}",neighbours.get(i).getBlock());
1781                    continue;
1782                }
1783                getAdjacency(neighbours.get(i).getBlock()).dispose();
1784                neighbours.remove(i);
1785                layoutBlockToNotify.notifiedNeighbourNoLongerMutual(this);
1786            }
1787        }
1788
1789        for (int i = throughPaths.size() - 1; i > -1; i--) {
1790            if (throughPaths.get(i).getSourceBlock() == removedBlock) {
1791                // only mark for removal if the source isn't in the adjcency table
1792                if (getAdjacency(throughPaths.get(i).getSourceBlock()) == null) {
1793                    if (enableDeleteRouteLogging) {
1794                        log.info("remove {} to {}", throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName());
1795                    }
1796                    throughPaths.remove(i);
1797                }
1798            } else if (throughPaths.get(i).getDestinationBlock() == removedBlock) {
1799                // only mark for removal if the destination isn't in the adjcency table
1800                if (getAdjacency(throughPaths.get(i).getDestinationBlock()) == null) {
1801                    if (enableDeleteRouteLogging) {
1802                        log.info("remove {} to {}", throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName());
1803                    }
1804                    throughPaths.remove(i);
1805                }
1806            }
1807        }
1808
1809        if (enableDeleteRouteLogging) {
1810            log.info("From {} neighbour has been removed - Number of routes to this neighbour removed{}", this.getDisplayName(), tmpBlock.size());
1811        }
1812        notifyNeighboursOfRemoval(tmpBlock, removedBlock);
1813    }
1814
1815    // This is used when a property event change is triggered for a removed route.
1816    // Not sure that bulk removals will be necessary
1817    private void removeRouteFromNeighbour(LayoutBlock src, RoutingPacket update) {
1818        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
1819        Block srcblk = src.getBlock();
1820        Block destblk = update.getBlock();
1821        String msgPrefix = "From " + this.getDisplayName() + " notify block " + srcblk.getDisplayName() + " ";
1822
1823        if (enableDeleteRouteLogging) {
1824            log.info("{} remove route from neighbour called", msgPrefix);
1825        }
1826
1827        if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(srcblk) == this) {
1828            if (enableDeleteRouteLogging) {
1829                log.info("From {} source block is the same as our block! {}", this.getDisplayName(), destblk.getDisplayName());
1830            }
1831            return;
1832        }
1833
1834        if (enableDeleteRouteLogging) {
1835            log.info("{} (Direct Notification) neighbour {} has removed route to {}", msgPrefix, srcblk.getDisplayName(), destblk.getDisplayName());
1836            log.info("{} routes in table {} Remove route from neighbour", msgPrefix, routes.size());
1837        }
1838        List<Routes> routesToRemove = new ArrayList<>();
1839        for (int i = routes.size() - 1; i > -1; i--) {
1840            Routes ro = routes.get(i);
1841            if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destblk)) {
1842                routesToRemove.add(new Routes(routes.get(i).getDestBlock(), routes.get(i).getNextBlock(), 0, 0, 0, 0));
1843                if (enableDeleteRouteLogging) {
1844                    log.info("{} route to {} from block {} to be removed triggered by propertyChange", msgPrefix, ro.getDestBlock().getDisplayName(), ro.getNextBlock().getDisplayName());
1845                }
1846                routes.remove(i);
1847                // We only fire off routing update the once
1848            }
1849        }
1850        notifyNeighboursOfRemoval(routesToRemove, srcblk);
1851    }
1852
1853    private List<Routes> removeRouteReceivedFromNeighbour(Block removedBlock) {
1854        List<Routes> tmpBlock = new ArrayList<>();
1855
1856        // here we simply remove the routes which are advertised from the removed neighbour
1857        for (int j = routes.size() - 1; j > -1; j--) {
1858            Routes ro = routes.get(j);
1859            if (enableDeleteRouteLogging) {
1860                log.info("From {} route to check {} from Block {}", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName());
1861            }
1862
1863            if (ro.getDestBlock() == removedBlock) {
1864                if (enableDeleteRouteLogging) {
1865                    log.info("From {} route to {} from block {} to be removed triggered by adjancey removal as dest block has been removed", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName());
1866                }
1867
1868                if (!tmpBlock.contains(ro)) {
1869                    tmpBlock.add(ro);
1870                }
1871                routes.remove(j);
1872                // This will need to be removed fromth directly connected
1873            } else if (ro.getNextBlock() == removedBlock) {
1874                if (enableDeleteRouteLogging) {
1875                    log.info("From {} route to {} from block {} to be removed triggered by adjancey removal", this.getDisplayName(), routes.get(j).getDestBlock().getDisplayName(), routes.get(j).getNextBlock().getDisplayName());
1876                }
1877
1878                if (!tmpBlock.contains(ro)) {
1879                    tmpBlock.add(ro);
1880                }
1881                routes.remove(j);
1882                // This will also need to be removed from the directly connected list as well.
1883            }
1884        }
1885        return tmpBlock;
1886    }
1887
1888    private void updateNeighbourPacketFlow(Block neighbour, int flow) {
1889        // Packet flow from neighbour will need to be reversed.
1890        Adjacencies neighAdj = getAdjacency(neighbour);
1891
1892        if (flow == RXONLY) {
1893            flow = TXONLY;
1894        } else if (flow == TXONLY) {
1895            flow = RXONLY;
1896        }
1897
1898        if (neighAdj.getPacketFlow() == flow) {
1899            return;
1900        }
1901        updateNeighbourPacketFlow(neighAdj, flow);
1902    }
1903
1904    protected void updateNeighbourPacketFlow(Adjacencies neighbour, final int flow) {
1905        if (neighbour.getPacketFlow() == flow) {
1906            return;
1907        }
1908
1909        final LayoutBlock neighLBlock = neighbour.getLayoutBlock();
1910        Runnable r = () -> neighLBlock.updateNeighbourPacketFlow(block, flow);
1911
1912        Block neighBlock = neighbour.getBlock();
1913        int oldPacketFlow = neighbour.getPacketFlow();
1914
1915        neighbour.setPacketFlow(flow);
1916
1917        SwingUtilities.invokeLater(r);
1918
1919        if (flow == TXONLY) {
1920            neighBlock.addBlockDenyList(this.block);
1921            neighLBlock.removePropertyChangeListener(this);
1922
1923            // This should remove routes learned from our neighbour
1924            List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(neighBlock);
1925
1926            notifyNeighboursOfRemoval(tmpBlock, neighBlock);
1927
1928            // Need to also remove all through paths to this neighbour
1929            for (int i = throughPaths.size() - 1; i > -1; i--) {
1930                if (throughPaths.get(i).getDestinationBlock() == neighBlock) {
1931                    throughPaths.remove(i);
1932                    firePropertyChange("through-path-removed", null, null);
1933                }
1934            }
1935
1936            // We potentially will need to re-advertise routes to this neighbour
1937            if (oldPacketFlow == RXONLY) {
1938                addThroughPath(neighbour);
1939            }
1940        } else if (flow == RXONLY) {
1941            neighLBlock.addPropertyChangeListener(this);
1942            neighBlock.removeBlockDenyList(this.block);
1943            this.block.addBlockDenyList(neighBlock);
1944
1945            for (int i = throughPaths.size() - 1; i > -1; i--) {
1946                if (throughPaths.get(i).getSourceBlock() == neighBlock) {
1947                    throughPaths.remove(i);
1948                    firePropertyChange("through-path-removed", null, null);
1949                }
1950            }
1951
1952            // Might need to rebuild through paths.
1953            if (oldPacketFlow == TXONLY) {
1954                routes.add(new Routes(neighBlock, this.getBlock(),
1955                        1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm()));
1956                addThroughPath(neighbour);
1957            }
1958            // We would need to withdraw the routes that we advertise to the neighbour
1959        } else if (flow == RXTX) {
1960            neighBlock.removeBlockDenyList(this.block);
1961            this.block.removeBlockDenyList(neighBlock);
1962            neighLBlock.addPropertyChangeListener(this);
1963
1964            // Might need to rebuild through paths.
1965            if (oldPacketFlow == TXONLY) {
1966                routes.add(new Routes(neighBlock, this.getBlock(),
1967                        1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm()));
1968            }
1969            addThroughPath(neighbour);
1970        }
1971    }
1972
1973    private void notifyNeighboursOfRemoval(List<Routes> routesToRemove, Block notifyingblk) {
1974        String msgPrefix = "From " + this.getDisplayName() + " notify block " + notifyingblk.getDisplayName() + " ";
1975
1976        if (enableDeleteRouteLogging) {
1977            log.info("{} notifyNeighboursOfRemoval called for routes from {} ===", msgPrefix, notifyingblk.getDisplayName());
1978        }
1979        boolean notifyvalid = false;
1980
1981        for (int i = neighbours.size() - 1; i > -1; i--) {
1982            if (neighbours.get(i).getBlock() == notifyingblk) {
1983                notifyvalid = true;
1984            }
1985        }
1986
1987        if (enableDeleteRouteLogging) {
1988            log.info("{} The notifying block is still valid? {}", msgPrefix, notifyvalid);
1989        }
1990
1991        for (int j = routesToRemove.size() - 1; j > -1; j--) {
1992            boolean stillexist = false;
1993            Block destBlock = routesToRemove.get(j).getDestBlock();
1994            Block sourceBlock = routesToRemove.get(j).getNextBlock();
1995            RoutingPacket newUpdate = new RoutingPacket(REMOVAL, destBlock, -1, -1, -1, -1, getNextPacketID());
1996
1997            if (enableDeleteRouteLogging) {
1998                log.info("From {} notify block {} checking {} from {}", this.getDisplayName(), notifyingblk.getDisplayName(), destBlock.getDisplayName(), sourceBlock.getDisplayName());
1999            }
2000            List<Routes> validroute = new ArrayList<>();
2001            List<Routes> destRoutes = getDestRoutes(destBlock);
2002            for (Routes r : destRoutes) {
2003                // We now know that we still have a valid route to the dest
2004                if (r.getNextBlock() == this.getBlock()) {
2005                    if (enableDeleteRouteLogging) {
2006                        log.info("{} The destBlock {} is our neighbour", msgPrefix, destBlock.getDisplayName());
2007                    }
2008                    validroute.add(new Routes(r.getDestBlock(), r.getNextBlock(), 0, 0, 0, 0));
2009                    stillexist = true;
2010                } else {
2011                    // At this stage do we need to check if the valid route comes from a neighbour?
2012                    if (enableDeleteRouteLogging) {
2013                        log.info("{} we still have a route to {} via {} in our list", msgPrefix, destBlock.getDisplayName(), r.getNextBlock().getDisplayName());
2014                    }
2015                    validroute.add(new Routes(destBlock, r.getNextBlock(), 0, 0, 0, 0));
2016                    stillexist = true;
2017                }
2018            }
2019            // We may need to find out who else we could of sent the route to by checking in the through paths
2020
2021            if (stillexist) {
2022                if (enableDeleteRouteLogging) {
2023                    log.info("{}A Route still exists", msgPrefix);
2024                    log.info("{} the number of routes installed to block {} is {}", msgPrefix, destBlock.getDisplayName(), validroute.size());
2025                }
2026
2027                if (validroute.size() == 1) {
2028                    // Specific routing update.
2029                    Block nextHop = validroute.get(0).getNextBlock();
2030                    LayoutBlock layoutBlock;
2031                    if (validroute.get(0).getNextBlock() != this.getBlock()) {
2032                        layoutBlock = InstanceManager.getDefault(
2033                                LayoutBlockManager.class).getLayoutBlock(nextHop);
2034                        if (enableDeleteRouteLogging) {
2035                            log.info("{} We only have a single valid route left to {} So will tell {} we no longer have it", msgPrefix, destBlock.getDisplayName(), layoutBlock.getDisplayName());
2036                        }
2037
2038                        if (layoutBlock != null) {
2039                            layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2040                        }
2041                        getAdjacency(nextHop).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2042                    }
2043
2044                    // At this point we could probably do with checking for other valid paths from the notifyingblock
2045                    // Have a feeling that this is pretty much the same as above!
2046                    List<Block> validNeighboursToNotify = new ArrayList<>();
2047
2048                    // Problem we have here is that although we only have one valid route, one of our neighbours
2049                    // could still hold a valid through path.
2050                    for (int i = neighbours.size() - 1; i > -1; i--) {
2051                        // Need to ignore if the dest block is our neighour in this instance
2052                        if ((neighbours.get(i).getBlock() != destBlock) && (neighbours.get(i).getBlock() != nextHop)) {
2053                            if (validThroughPath(notifyingblk, neighbours.get(i).getBlock())) {
2054                                Block neighblock = neighbours.get(i).getBlock();
2055
2056                                if (enableDeleteRouteLogging) {
2057                                    log.info("{} we could of potentially sent the route to {}", msgPrefix, neighblock.getDisplayName());
2058                                }
2059
2060                                if (!validThroughPath(nextHop, neighblock)) {
2061                                    if (enableDeleteRouteLogging) {
2062                                        log.info("{} there is no other valid path so will mark for removal", msgPrefix);
2063                                    }
2064                                    validNeighboursToNotify.add(neighblock);
2065                                } else {
2066                                    if (enableDeleteRouteLogging) {
2067                                        log.info("{} there is another valid path so will NOT mark for removal", msgPrefix);
2068                                    }
2069                                }
2070                            }
2071                        }
2072                    }
2073
2074                    if (enableDeleteRouteLogging) {
2075                        log.info("{} the next block is our selves so we won't remove!", msgPrefix);
2076                        log.info("{} do we need to find out if we could of send the route to another neighbour such as?", msgPrefix);
2077                    }
2078
2079                    for (Block value : validNeighboursToNotify) {
2080                        // If the neighbour has a valid through path to the dest
2081                        // we will not notify the neighbour of our loss of route
2082                        if (!validThroughPath(value, destBlock)) {
2083                            layoutBlock = InstanceManager.getDefault(LayoutBlockManager.class).
2084                                    getLayoutBlock(value);
2085                            if (layoutBlock != null) {
2086                                layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2087                            }
2088                            getAdjacency(value).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2089                        } else {
2090                            if (enableDeleteRouteLogging) {
2091                                log.info("{}{} has a valid path to {}", msgPrefix, value.getDisplayName(), destBlock.getDisplayName());
2092                            }
2093                        }
2094                    }
2095                } else {
2096                    // Need to deal with having multiple routes left.
2097                    if (enableDeleteRouteLogging) {
2098                        log.info("{} routes left to block {}", msgPrefix, destBlock.getDisplayName());
2099                    }
2100
2101                    for (Routes item : validroute) {
2102                        // We need to see if we have valid routes.
2103                        if (validThroughPath(notifyingblk, item.getNextBlock())) {
2104                            if (enableDeleteRouteLogging) {
2105                                log.info("{} to {} Is a valid route", msgPrefix, item.getNextBlock().getDisplayName());
2106                            }
2107                            // Will mark the route for potential removal
2108                            item.setMiscFlags(0x02);
2109                        } else {
2110                            if (enableDeleteRouteLogging) {
2111                                log.info("{} to {} Is not a valid route", msgPrefix, item.getNextBlock().getDisplayName());
2112                            }
2113                            // Mark the route to not be removed.
2114                            item.setMiscFlags(0x01);
2115
2116                            // Given that the route to this is not valid, we do not want to
2117                            // be notifying this next block about the loss of route.
2118                        }
2119                    }
2120
2121                    // We have marked all the routes for either potential notification of route removal, or definate no removal;
2122                    // Now need to get through the list and cross reference each one.
2123                    for (int i = 0; i < validroute.size(); i++) {
2124                        if (validroute.get(i).getMiscFlags() == 0x02) {
2125                            Block nextblk = validroute.get(i).getNextBlock();
2126
2127                            if (enableDeleteRouteLogging) {
2128                                log.info("{} route from {} has been flagged for removal", msgPrefix, nextblk.getDisplayName());
2129                            }
2130
2131                            // Need to cross reference it with the routes that are left.
2132                            boolean leaveroute = false;
2133                            for (Routes value : validroute) {
2134                                if (value.getMiscFlags() == 0x01) {
2135                                    if (validThroughPath(nextblk, value.getNextBlock())) {
2136                                        if (enableDeleteRouteLogging) {
2137                                            log.info("{} we have a valid path from {} to {}", msgPrefix, nextblk.getDisplayName(), value.getNextBlock());
2138                                        }
2139                                        leaveroute = true;
2140                                    }
2141                                }
2142                            }
2143
2144                            if (!leaveroute) {
2145                                LayoutBlock layoutBlock = InstanceManager.getDefault(
2146                                        LayoutBlockManager.class).getLayoutBlock(nextblk);
2147                                if (enableDeleteRouteLogging) {
2148                                    log.info("{}############ We need to send notification to {} to remove route ########### haven't found an example of this yet!", msgPrefix, nextblk.getDisplayName());
2149                                }
2150                                if (layoutBlock==null) { // change to provides
2151                                    log.error("Unable to fetch block {}",nextblk);
2152                                    continue;
2153                                }
2154                                layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2155                                getAdjacency(nextblk).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2156
2157                            } else {
2158                                if (enableDeleteRouteLogging) {
2159                                    log.info("{} a valid path through exists {} so we will not remove route.", msgPrefix, nextblk.getDisplayName());
2160                                }
2161                            }
2162                        }
2163                    }
2164                }
2165            } else {
2166                if (enableDeleteRouteLogging) {
2167                    log.info("{} We have no other routes to {} Therefore we will broadast this to our neighbours", msgPrefix, destBlock.getDisplayName());
2168                }
2169
2170                for (Adjacencies adj : neighbours) {
2171                    adj.removeRouteAdvertisedToNeighbour(destBlock);
2172                }
2173                firePropertyChange("routing", null, newUpdate);
2174            }
2175        }
2176
2177        if (enableDeleteRouteLogging) {
2178            log.info("{} finshed check and notifying of removed routes from {} ===", msgPrefix, notifyingblk.getDisplayName());
2179        }
2180    }
2181
2182    private void addThroughPath(Adjacencies adj) {
2183        Block newAdj = adj.getBlock();
2184        int packetFlow = adj.getPacketFlow();
2185
2186        if (enableAddRouteLogging) {
2187            log.debug("From {} addThroughPathCalled with adj {}", this.getDisplayName(), adj.getBlock().getDisplayName());
2188        }
2189
2190        for (Adjacencies neighbour : neighbours) {
2191            // cycle through all the neighbours
2192            if (neighbour.getBlock() != newAdj) {
2193                int neighPacketFlow = neighbour.getPacketFlow();
2194
2195                if (enableAddRouteLogging) {
2196                    log.info("From {} our direction: {}, neighbour direction: {}", this.getDisplayName(), decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow));
2197                }
2198
2199                if ((packetFlow == RXTX) && (neighPacketFlow == RXTX)) {
2200                    // if both are RXTX then add flow in both directions
2201                    addThroughPath(neighbour.getBlock(), newAdj);
2202                    addThroughPath(newAdj, neighbour.getBlock());
2203                } else if ((packetFlow == RXONLY) && (neighPacketFlow == TXONLY)) {
2204                    addThroughPath(neighbour.getBlock(), newAdj);
2205                } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXONLY)) {
2206                    addThroughPath(newAdj, neighbour.getBlock());
2207                } else if ((packetFlow == RXTX) && (neighPacketFlow == TXONLY)) {   // was RX
2208                    addThroughPath(neighbour.getBlock(), newAdj);
2209                } else if ((packetFlow == RXTX) && (neighPacketFlow == RXONLY)) {   // was TX
2210                    addThroughPath(newAdj, neighbour.getBlock());
2211                } else if ((packetFlow == RXONLY) && (neighPacketFlow == RXTX)) {
2212                    addThroughPath(neighbour.getBlock(), newAdj);
2213                } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXTX)) {
2214                    addThroughPath(newAdj, neighbour.getBlock());
2215                } else {
2216                    if (enableAddRouteLogging) {
2217                        log.info("Invalid combination {} and {}", decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow));
2218                    }
2219                }
2220            }
2221        }
2222    }
2223
2224    /**
2225     * Add a path between two blocks, but without spec a panel.
2226     */
2227    private void addThroughPath(Block srcBlock, Block dstBlock) {
2228        if (enableAddRouteLogging) {
2229            log.info("Block {}.addThroughPath(src:{}, dst: {})",
2230                    this.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2231        }
2232
2233        if ((block != null) && (panels.size() > 0)) {
2234            // a block is attached and this LayoutBlock is used
2235            // initialize connectivity as defined in first Layout Editor panel
2236            LayoutEditor panel = panels.get(0);
2237            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
2238
2239            // if more than one panel, find panel with the highest connectivity
2240            if (panels.size() > 1) {
2241                for (int i = 1; i < panels.size(); i++) {
2242                    if (c.size() < panels.get(i).getLEAuxTools().
2243                            getConnectivityList(this).size()) {
2244                        panel = panels.get(i);
2245                        c = panel.getLEAuxTools().getConnectivityList(this);
2246                    }
2247                }
2248
2249                // check that this connectivity is compatible with that of other panels.
2250                for (LayoutEditor tPanel : panels) {
2251                    if ((tPanel != panel) && InstanceManager.getDefault(LayoutBlockManager.class).
2252                            warn() && (!compareConnectivity(c,
2253                                    tPanel.getLEAuxTools().getConnectivityList(this)))) {
2254                        // send user an error message
2255                        int response = JmriJOptionPane.showOptionDialog(null,
2256                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
2257                                        new Object[]{getUserName(), tPanel.getLayoutName(),
2258                                            panel.getLayoutName()}), Bundle.getMessage("WarningTitle"),
2259                                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
2260                                null,
2261                                new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
2262                                Bundle.getMessage("ButtonOK"));
2263                        if (response == 1 ) { // array position 1 ButtonOKPlus pressed, user elected to disable messages
2264                            InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning();
2265                        }
2266                    }
2267                }
2268            }
2269            // update block Paths to reflect connectivity as needed
2270            addThroughPath(srcBlock, dstBlock, panel);
2271        }
2272    }
2273
2274    private LayoutEditorAuxTools auxTools = null;
2275    private ConnectivityUtil connection = null;
2276    private boolean layoutConnectivity = true;
2277
2278    /**
2279     * Add a through path on this layout block, going from the source block to
2280     * the destination block, using a specific panel. Note: If the reverse path
2281     * is required, then this needs to be added seperately.
2282     */
2283    // Was public
2284    private void addThroughPath(Block srcBlock, Block dstBlock, LayoutEditor panel) {
2285        // Reset connectivity flag.
2286        layoutConnectivity = true;
2287
2288        if (srcBlock == dstBlock) {
2289            // Do not do anything if the blocks are the same!
2290            return;
2291        }
2292
2293        if (enableAddRouteLogging) {
2294            log.info("Block {}.addThroughPath(src:{}, dst: {}, <panel>)",
2295                    this.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2296        }
2297
2298        // Initally check to make sure that the through path doesn't already exist.
2299        // no point in going through the checks if the path already exists.
2300        boolean add = true;
2301        for (ThroughPaths throughPath : throughPaths) {
2302            if (throughPath.getSourceBlock() == srcBlock) {
2303                if (throughPath.getDestinationBlock() == dstBlock) {
2304                    add = false;
2305                }
2306            }
2307        }
2308
2309        if (!add) {
2310            return;
2311        }
2312
2313        if (enableAddRouteLogging) {
2314            log.info("Block {}, src: {}, dst: {}",
2315                    block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2316        }
2317        connection = panel.getConnectivityUtil();
2318        List<LayoutTrackExpectedState<LayoutTurnout>> stod;
2319
2320        try {
2321            MDC.put("loggingDisabled", connection.getClass().getCanonicalName());
2322            stod = connection.getTurnoutList(block, srcBlock, dstBlock, true);
2323            MDC.remove("loggingDisabled");
2324        } catch (java.lang.NullPointerException ex) {
2325            MDC.remove("loggingDisabled");
2326            if (enableAddRouteLogging) {
2327                log.error("Exception ({}) caught while trying to discover turnout connectivity\nBlock: {}, srcBlock ({}) to dstBlock ({})", ex.toString(), block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2328                log.error("@ Line # {}", ex.getStackTrace()[1].getLineNumber());
2329            }
2330            return;
2331        }
2332
2333        if (!connection.isTurnoutConnectivityComplete()) {
2334            layoutConnectivity = false;
2335        }
2336        List<LayoutTrackExpectedState<LayoutTurnout>> tmpdtos;
2337
2338        try {
2339            MDC.put("loggingDisabled", connection.getClass().getName());
2340            tmpdtos = connection.getTurnoutList(block, dstBlock, srcBlock, true);
2341            MDC.remove("loggingDisabled");
2342        } catch (java.lang.NullPointerException ex) {
2343            MDC.remove("loggingDisabled");
2344            if (enableAddRouteLogging) {
2345                log.error("Exception ({}) caught while trying to discover turnout connectivity\nBlock: {}, dstBlock ({}) to  srcBlock ({})", ex.toString(), block.getDisplayName(), dstBlock.getDisplayName(), srcBlock.getDisplayName());
2346                log.error("@ Line # {}", ex.getStackTrace()[1].getLineNumber());
2347            }
2348            return;
2349        }
2350
2351        if (!connection.isTurnoutConnectivityComplete()) {
2352            layoutConnectivity = false;
2353        }
2354
2355        if (stod.size() == tmpdtos.size()) {
2356            // Need to reorder the tmplist (dst-src) to be the same order as src-dst
2357            List<LayoutTrackExpectedState<LayoutTurnout>> dtos = new ArrayList<>();
2358            for (int i = tmpdtos.size(); i > 0; i--) {
2359                dtos.add(tmpdtos.get(i - 1));
2360            }
2361
2362            // check to make sure that we pass through the same turnouts
2363            if (enableAddRouteLogging) {
2364                log.info("From {} destination size {} v source size {}", this.getDisplayName(), dtos.size(), stod.size());
2365            }
2366
2367            for (int i = 0; i < dtos.size(); i++) {
2368                if (dtos.get(i).getObject() != stod.get(i).getObject()) {
2369                    if (enableAddRouteLogging) {
2370                        log.info("{} != {}: will quit", dtos.get(i).getObject(), stod.get(i).getObject());
2371                    }
2372                    return;
2373                }
2374            }
2375
2376            for (int i = 0; i < dtos.size(); i++) {
2377                int x = stod.get(i).getExpectedState();
2378                int y = dtos.get(i).getExpectedState();
2379
2380                if (x != y) {
2381                    if (enableAddRouteLogging) {
2382                        log.info("{} not on setting equal will quit {}, {}", block.getDisplayName(), x, y);
2383                    }
2384                    return;
2385                } else if (x == Turnout.UNKNOWN) {
2386                    if (enableAddRouteLogging) {
2387                        log.info("{} turnout state returned as UNKNOWN", block.getDisplayName());
2388                    }
2389                    return;
2390                }
2391            }
2392            Set<LayoutTurnout> set = new HashSet<>();
2393
2394            for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : stod) {
2395                boolean val = set.add(layoutTurnoutLayoutTrackExpectedState.getObject());
2396                if (val == false) {
2397                    // Duplicate found. will not add
2398                    return;
2399                }
2400            }
2401            // for (LayoutTurnout turn : stod) {
2402            //    if (turn.type == LayoutTurnout.DOUBLE_XOVER) {
2403            //        // Further checks might be required.
2404            //    }
2405            //}
2406            addThroughPathPostChecks(srcBlock, dstBlock, stod);
2407        } else {
2408            // We know that a path that contains a double cross-over, is not reported correctly,
2409            // therefore we shall do some additional checks and add it.
2410            if (enableAddRouteLogging) {
2411                log.info("sizes are not the same therefore, we will do some further checks");
2412            }
2413            List<LayoutTrackExpectedState<LayoutTurnout>> maxt;
2414            if (stod.size() >= tmpdtos.size()) {
2415                maxt = stod;
2416            } else {
2417                maxt = tmpdtos;
2418            }
2419
2420            Set<LayoutTrackExpectedState<LayoutTurnout>> set = new HashSet<>(maxt);
2421
2422            if (set.size() == maxt.size()) {
2423                if (enableAddRouteLogging) {
2424                    log.info("All turnouts are unique so potentially a valid path");
2425                }
2426                boolean allowAddition = false;
2427                for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : maxt) {
2428                    LayoutTurnout turn = layoutTurnoutLayoutTrackExpectedState.getObject();
2429                    if (turn.type == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
2430                        allowAddition = true;
2431                        // The double crossover gets reported in the opposite setting.
2432                        if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == 2) {
2433                            layoutTurnoutLayoutTrackExpectedState.setExpectedState(4);
2434                        } else {
2435                            layoutTurnoutLayoutTrackExpectedState.setExpectedState(2);
2436                        }
2437                    }
2438                }
2439
2440                if (allowAddition) {
2441                    if (enableAddRouteLogging) {
2442                        log.info("addition allowed");
2443                    }
2444                    addThroughPathPostChecks(srcBlock, dstBlock, maxt);
2445                } else if (enableAddRouteLogging) {
2446                    log.info("No double cross-over so not a valid path");
2447                }
2448            }
2449        }
2450    }   // addThroughPath
2451
2452    private void addThroughPathPostChecks(Block srcBlock,
2453            Block dstBlock, List<LayoutTrackExpectedState<LayoutTurnout>> stod) {
2454        List<Path> paths = block.getPaths();
2455        Path srcPath = null;
2456
2457        for (Path item : paths) {
2458            if (item.getBlock() == srcBlock) {
2459                srcPath = item;
2460            }
2461        }
2462        Path dstPath = null;
2463
2464        for (Path value : paths) {
2465            if (value.getBlock() == dstBlock) {
2466                dstPath = value;
2467            }
2468        }
2469        ThroughPaths path = new ThroughPaths(srcBlock, srcPath, dstBlock, dstPath);
2470        path.setTurnoutList(stod);
2471
2472        if (enableAddRouteLogging) {
2473            log.info("From {} added Throughpath {} {}", this.getDisplayName(), path.getSourceBlock().getDisplayName(), path.getDestinationBlock().getDisplayName());
2474        }
2475        throughPaths.add(path);
2476        firePropertyChange("through-path-added", null, null);
2477
2478        // update our neighbours of the new valid paths;
2479        informNeighbourOfValidRoutes(srcBlock);
2480        informNeighbourOfValidRoutes(dstBlock);
2481    }
2482
2483    void notifiedNeighbourNoLongerMutual(LayoutBlock srcBlock) {
2484        if (enableDeleteRouteLogging) {
2485            log.info("From {}Notification from neighbour that it is no longer our friend {}", this.getDisplayName(), srcBlock.getDisplayName());
2486        }
2487        Block blk = srcBlock.getBlock();
2488
2489        for (int i = neighbours.size() - 1; i > -1; i--) {
2490            // Need to check if the block we are being informed about has already been removed or not
2491            if (neighbours.get(i).getBlock() == blk) {
2492                removeAdjacency(srcBlock);
2493                break;
2494            }
2495        }
2496    }
2497
2498    public static final int RESERVED = 0x08;
2499
2500    void stateUpdate() {
2501        // Need to find a way to fire off updates to the various tables
2502        if (enableUpdateRouteLogging) {
2503            log.debug("From {} A block state change ({}) has occurred", this.getDisplayName(), getBlockStatusString());
2504        }
2505        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, -1, -1, getBlockStatus(), getNextPacketID());
2506        firePropertyChange("routing", null, update);
2507    }
2508
2509    int getBlockStatus() {
2510        if (getOccupancy() == OCCUPIED) {
2511            useExtraColor = false;
2512            // Our section of track is occupied
2513            return OCCUPIED;
2514        } else if (useExtraColor) {
2515            return RESERVED;
2516        } else if (getOccupancy() == EMPTY) {
2517            return EMPTY;
2518        } else {
2519            return UNKNOWN;
2520        }
2521    }
2522
2523    String getBlockStatusString() {
2524        String result = "UNKNOWN";
2525        if (getOccupancy() == OCCUPIED) {
2526            result = "OCCUPIED";
2527        } else if (useExtraColor) {
2528            result = "RESERVED";
2529        } else if (getOccupancy() == EMPTY) {
2530            result = "EMPTY";
2531        }
2532        return result;
2533    }
2534
2535    Integer getNextPacketID() {
2536        Integer lastID;
2537
2538        if (updateReferences.isEmpty()) {
2539            lastID = 0;
2540        } else {
2541            int lastIDPos = updateReferences.size() - 1;
2542            lastID = updateReferences.get(lastIDPos) + 1;
2543        }
2544
2545        if (lastID > 2000) {
2546            lastID = 0;
2547        }
2548        updateReferences.add(lastID);
2549
2550        /*As we are originating a packet, we will added to the acted upion list
2551         thus making sure if the packet gets back to us we do knowing with it.*/
2552        actedUponUpdates.add(lastID);
2553
2554        if (updateReferences.size() > 500) {
2555            // log.info("flush update references");
2556            updateReferences.subList(0, 250).clear();
2557        }
2558
2559        if (actedUponUpdates.size() > 500) {
2560            actedUponUpdates.subList(0, 250).clear();
2561        }
2562        return lastID;
2563    }
2564
2565    boolean updatePacketActedUpon(Integer packetID) {
2566        return actedUponUpdates.contains(packetID);
2567    }
2568
2569    public List<Block> getActiveNextBlocks(Block source) {
2570        List<Block> currentPath = new ArrayList<>();
2571
2572        for (ThroughPaths path : throughPaths) {
2573            if ((path.getSourceBlock() == source) && (path.isPathActive())) {
2574                currentPath.add(path.getDestinationBlock());
2575            }
2576        }
2577        return currentPath;
2578    }
2579
2580    public Path getThroughPathSourcePathAtIndex(int i) {
2581        return throughPaths.get(i).getSourcePath();
2582    }
2583
2584    public Path getThroughPathDestinationPathAtIndex(int i) {
2585        return throughPaths.get(i).getDestinationPath();
2586    }
2587
2588    public boolean validThroughPath(Block sourceBlock, Block destinationBlock) {
2589        for (ThroughPaths throughPath : throughPaths) {
2590            if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) {
2591                return true;
2592            } else if ((throughPath.getSourceBlock() == destinationBlock) && (throughPath.getDestinationBlock() == sourceBlock)) {
2593                return true;
2594            }
2595        }
2596        return false;
2597    }
2598
2599    public int getThroughPathIndex(Block sourceBlock, Block destinationBlock) {
2600        for (int i = 0; i < throughPaths.size(); i++) {
2601            if ((throughPaths.get(i).getSourceBlock() == sourceBlock)
2602                    && (throughPaths.get(i).getDestinationBlock() == destinationBlock)) {
2603                return i;
2604            } else if ((throughPaths.get(i).getSourceBlock() == destinationBlock)
2605                    && (throughPaths.get(i).getDestinationBlock() == sourceBlock)) {
2606                return i;
2607            }
2608        }
2609        return -1;
2610    }
2611
2612    List<Adjacencies> neighbours = new ArrayList<>();
2613
2614    List<ThroughPaths> throughPaths = new ArrayList<>();
2615
2616    // A sub class that holds valid routes through the block.
2617    // Possibly want to store the path direction in here as well.
2618    // or we store the ref to the path, so we can get the directions.
2619    List<Routes> routes = new ArrayList<>();
2620
2621    String decodePacketFlow(int value) {
2622        switch (value) {
2623            case RXTX: {
2624                return "Bi-Direction Operation";
2625            }
2626
2627            case RXONLY: {
2628                return "Uni-Directional - Trains can only exit to this block (RX) ";
2629            }
2630
2631            case TXONLY: {
2632                return "Uni-Directional - Trains can not be sent down this block (TX) ";
2633            }
2634
2635            case NONE: {
2636                return "None routing updates will be passed";
2637            }
2638            default:
2639                log.warn("Unhandled packet flow value: {}", value);
2640                break;
2641        }
2642        return "Unknown";
2643    }
2644
2645    /**
2646     * Provide an output to the console of all the valid paths through this
2647     * block.
2648     */
2649    public void printValidThroughPaths() {
2650        log.info("Through paths for block {}", this.getDisplayName());
2651        log.info("Current Block, From Block, To Block");
2652        for (ThroughPaths tp : throughPaths) {
2653            String activeStr = "";
2654            if (tp.isPathActive()) {
2655                activeStr = ", *";
2656            }
2657            log.info("From {}, {}, {}{}", this.getDisplayName(), (tp.getSourceBlock()).getDisplayName(), (tp.getDestinationBlock()).getDisplayName(), activeStr);
2658        }
2659    }
2660
2661    /**
2662     * Provide an output to the console of all our neighbouring blocks.
2663     */
2664    public void printAdjacencies() {
2665        log.info("Adjacencies for block {}", this.getDisplayName());
2666        log.info("Neighbour, Direction, mutual, relationship, metric");
2667        for (Adjacencies neighbour : neighbours) {
2668            log.info(" neighbor: {}, {}, {}, {}, {}",neighbour.getBlock().getDisplayName(), Path.decodeDirection(neighbour.getDirection()), neighbour.isMutual(), decodePacketFlow(neighbour.getPacketFlow()), neighbour.getMetric());
2669        }
2670    }
2671
2672    /**
2673     * Provide an output to the console of all the remote blocks reachable from
2674     * our block.
2675     */
2676    public void printRoutes() {
2677        log.info("Routes for block {}", this.getDisplayName());
2678        log.info("Destination, Next Block, Hop Count, Direction, State, Metric");
2679        for (Routes r : routes) {
2680            String nexthop = r.getNextBlock().getDisplayName();
2681
2682            if (r.getNextBlock() == this.getBlock()) {
2683                nexthop = "Directly Connected";
2684            }
2685            String activeString = "";
2686            if (r.isRouteCurrentlyValid()) {
2687                activeString = ", *";
2688            }
2689
2690            log.info(" neighbor: {}, {}, {}, {}, {}, {}{}", (r.getDestBlock()).getDisplayName(), nexthop, r.getHopCount(), Path.decodeDirection(r.getDirection()), r.getState(), r.getMetric(), activeString);
2691        }
2692    }
2693
2694    /**
2695     * Provide an output to the console of how to reach a specific block from
2696     * our block.
2697     *
2698     * @param inBlockName to find in route
2699     */
2700    public void printRoutes(String inBlockName) {
2701        log.info("Routes for block {}", this.getDisplayName());
2702        log.info("Our Block, Destination, Next Block, Hop Count, Direction, Metric");
2703        for (Routes route : routes) {
2704            if (route.getDestBlock().getDisplayName().equals(inBlockName)) {
2705                log.info("From {}, {}, {}, {}, {}, {}", this.getDisplayName(), (route.getDestBlock()).getDisplayName(), (route.getNextBlock()).getDisplayName(), route.getHopCount(), Path.decodeDirection(route.getDirection()), route.getMetric());
2706            }
2707        }
2708    }
2709
2710    /**
2711     * @param destBlock is the destination of the block we are following
2712     * @param direction is the direction of travel from the previous block
2713     * @return next block
2714     */
2715    public Block getNextBlock(Block destBlock, int direction) {
2716        int bestMetric = 965000;
2717        Block bestBlock = null;
2718
2719        for (Routes r : routes) {
2720            if ((r.getDestBlock() == destBlock) && (r.getDirection() == direction)) {
2721                if (r.getMetric() < bestMetric) {
2722                    bestMetric = r.getMetric();
2723                    bestBlock = r.getNextBlock();
2724                    // bestBlock=r.getDestBlock();
2725                }
2726            }
2727        }
2728        return bestBlock;
2729    }
2730
2731    /**
2732     * Used if we already know the block prior to our block, and the destination
2733     * block. direction, is optional and is used where the previousBlock is
2734     * equal to our block.
2735     *
2736     * @param previousBlock start block
2737     * @param destBlock     finish block
2738     * @return next block
2739     */
2740    @CheckForNull
2741    public Block getNextBlock(Block previousBlock, Block destBlock) {
2742        int bestMetric = 965000;
2743        Block bestBlock = null;
2744
2745        for (Routes r : routes) {
2746            if (r.getDestBlock() == destBlock) {
2747                // Check that the route through from the previous block, to the next hop is valid
2748                if (validThroughPath(previousBlock, r.getNextBlock())) {
2749                    if (r.getMetric() < bestMetric) {
2750                        bestMetric = r.getMetric();
2751                        // bestBlock=r.getDestBlock();
2752                        bestBlock = r.getNextBlock();
2753                    }
2754                }
2755            }
2756        }
2757        return bestBlock;
2758    }
2759
2760    public int getConnectedBlockRouteIndex(Block destBlock, int direction) {
2761        for (int i = 0; i < routes.size(); i++) {
2762            if (routes.get(i).getNextBlock() == this.getBlock()) {
2763                log.info("Found a block that is directly connected");
2764
2765                if ((routes.get(i).getDestBlock() == destBlock)) {
2766                    log.info("In getConnectedBlockRouteIndex,  {}", Integer.toString(routes.get(i).getDirection() & direction));
2767                    if ((routes.get(i).getDirection() & direction) != 0) {
2768                        return i;
2769                    }
2770                }
2771            }
2772
2773            if (log.isDebugEnabled()) {
2774                log.debug("From {}, {}, nexthop {}, {}, {}, {}", this.getDisplayName(), (routes.get(i).getDestBlock()).getDisplayName(), routes.get(i).getHopCount(), Path.decodeDirection(routes.get(i).getDirection()), routes.get(i).getState(), routes.get(i).getMetric());
2775            }
2776        }
2777        return -1;
2778    }
2779
2780    // Need to work on this to deal with the method of routing
2781    public int getNextBlockByIndex(Block destBlock, int direction, int offSet) {
2782        for (int i = offSet; i < routes.size(); i++) {
2783            Routes ro = routes.get(i);
2784            if ((ro.getDestBlock() == destBlock)) {
2785                log.info("getNextBlockByIndex {}", Integer.toString(ro.getDirection() & direction));
2786                if ((ro.getDirection() & direction) != 0) {
2787                    return i;
2788                }
2789            }
2790        }
2791        return -1;
2792    }
2793
2794    // Need to work on this to deal with the method of routing
2795    /*
2796     *
2797     */
2798    public int getNextBlockByIndex(Block previousBlock, Block destBlock, int offSet) {
2799        for (int i = offSet; i < routes.size(); i++) {
2800            Routes ro = routes.get(i);
2801            // log.info(r.getDestBlock().getDisplayName() + " vs " + destBlock.getDisplayName());
2802            if (ro.getDestBlock() == destBlock) {
2803                // Check that the route through from the previous block, to the next hop is valid
2804                if (validThroughPath(previousBlock, ro.getNextBlock())) {
2805                    log.debug("valid through path");
2806                    return i;
2807                }
2808
2809                if (ro.getNextBlock() == this.getBlock()) {
2810                    log.debug("getNextBlock is this block therefore directly connected");
2811                    return i;
2812                }
2813            }
2814        }
2815        return -1;
2816    }
2817
2818    /**
2819     * last index - the index of the last block we returned ie we last returned
2820     * index 10, so we don't want to return it again. The block returned will
2821     * have a hopcount or metric equal to or greater than the one of the last
2822     * block returned. if the exclude block list is empty this is the first
2823     * time, it has been used. The parameters for the best last block are based
2824     * upon the last entry in the excludedBlock list.
2825     *
2826     * @param previousBlock starting block
2827     * @param destBlock     finish block
2828     * @param excludeBlock  blocks to skip
2829     * @param routingMethod value to match metric
2830     * @return next block
2831     */
2832    public int getNextBestBlock(Block previousBlock, Block destBlock, List<Integer> excludeBlock, LayoutBlockConnectivityTools.Metric routingMethod) {
2833        if (enableSearchRouteLogging) {
2834            log.info("From {} find best route from {} to {} index {} routingMethod {}", this.getDisplayName(), previousBlock.getDisplayName(), destBlock.getDisplayName(), excludeBlock, routingMethod);
2835        }
2836        int bestCount = 965255; // set stupidly high
2837        int bestIndex = -1;
2838        int lastValue = 0;
2839        List<Block> nextBlocks = new ArrayList<>(5);
2840        if (!excludeBlock.isEmpty() && (excludeBlock.get(excludeBlock.size() - 1) < routes.size())) {
2841            if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2842                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getMetric();
2843            } else /* if (routingMethod==LayoutBlockManager.HOPCOUNT)*/ {
2844                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getHopCount();
2845            }
2846
2847            for (int i : excludeBlock) {
2848                nextBlocks.add(routes.get(i).getNextBlock());
2849            }
2850
2851            if (enableSearchRouteLogging) {
2852                log.info("last index is {} {}", excludeBlock.get(excludeBlock.size() - 1),
2853                        routes.get(excludeBlock.get(excludeBlock.size() - 1)).getDestBlock().getDisplayName());
2854            }
2855        }
2856
2857        for (int i = 0; i < routes.size(); i++) {
2858            if (!excludeBlock.contains(i)) {
2859                Routes ro = routes.get(i);
2860                if (!nextBlocks.contains(ro.getNextBlock())) {
2861                    // if(ro.getNextBlock()!=nextBlock){
2862                    int currentValue;
2863                    if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2864                        currentValue = routes.get(i).getMetric();
2865                    } else /*if (routingMethod==InstanceManager.getDefault(
2866                        LayoutBlockManager.class).HOPCOUNT)*/ {
2867                        currentValue = routes.get(i).getHopCount();  // was lastindex changed to i
2868                    }
2869
2870                    if (currentValue >= lastValue) {
2871                        if (ro.getDestBlock() == destBlock) {
2872                            if (enableSearchRouteLogging) {
2873                                log.info("Match on dest blocks");
2874                                // Check that the route through from the previous block, to the next hop is valid
2875                                log.info("Is valid through path previous block {} to {}", previousBlock.getDisplayName(), ro.getNextBlock().getDisplayName());
2876                            }
2877
2878                            if (validThroughPath(previousBlock, ro.getNextBlock())) {
2879                                if (enableSearchRouteLogging) {
2880                                    log.info("valid through path");
2881                                }
2882
2883                                if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2884                                    if (ro.getMetric() < bestCount) {
2885                                        bestIndex = i;
2886                                        bestCount = ro.getMetric();
2887                                    }
2888                                } else /*if (routingMethod==InstanceManager.getDefault(
2889                                    LayoutBlockManager.class).HOPCOUNT)*/ {
2890                                    if (ro.getHopCount() < bestCount) {
2891                                        bestIndex = i;
2892                                        bestCount = ro.getHopCount();
2893                                    }
2894                                }
2895                            }
2896
2897                            if (ro.getNextBlock() == this.getBlock()) {
2898                                if (enableSearchRouteLogging) {
2899                                    log.info("getNextBlock is this block therefore directly connected");
2900                                }
2901                                return i;
2902                            }
2903                        }
2904                    }
2905                }
2906            }
2907        }
2908
2909        if (enableSearchRouteLogging) {
2910            log.info("returning {} best count {}", bestIndex, bestCount);
2911        }
2912        return bestIndex;
2913    }
2914
2915    @CheckForNull
2916    Routes getRouteByDestBlock(Block blk) {
2917        for (int i = routes.size() - 1; i > -1; i--) {
2918            if (routes.get(i).getDestBlock() == blk) {
2919                return routes.get(i);
2920            }
2921        }
2922        return null;
2923    }
2924
2925    @Nonnull
2926    List<Routes> getRouteByNeighbour(Block blk) {
2927        List<Routes> rtr = new ArrayList<>();
2928        for (Routes route : routes) {
2929            if (route.getNextBlock() == blk) {
2930                rtr.add(route);
2931            }
2932        }
2933        return rtr;
2934    }
2935
2936    int getAdjacencyPacketFlow(Block blk) {
2937        for (Adjacencies neighbour : neighbours) {
2938            if (neighbour.getBlock() == blk) {
2939                return neighbour.getPacketFlow();
2940            }
2941        }
2942        return -1;
2943    }
2944
2945    boolean isValidNeighbour(Block blk) {
2946        for (Adjacencies neighbour : neighbours) {
2947            if (neighbour.getBlock() == blk) {
2948                return true;
2949            }
2950        }
2951        return false;
2952    }
2953
2954    @Override
2955    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
2956        if (listener == this) {
2957            log.debug("adding ourselves as a listener for some strange reason! Skipping");
2958            return;
2959        }
2960        super.addPropertyChangeListener(listener);
2961    }
2962
2963    @Override
2964    public void propertyChange(PropertyChangeEvent e) {
2965
2966        switch (e.getPropertyName()) {
2967            case "NewRoute": {
2968                if (enableUpdateRouteLogging) {
2969                    log.info("==Event type {} New {}", e.getPropertyName(), ((LayoutBlock) e.getNewValue()).getDisplayName());
2970                }
2971                break;
2972            }
2973            case "through-path-added": {
2974                if (enableUpdateRouteLogging) {
2975                    log.info("neighbour has new through path");
2976                }
2977                break;
2978            }
2979            case "through-path-removed": {
2980                if (enableUpdateRouteLogging) {
2981                    log.info("neighbour has through removed");
2982                }
2983                break;
2984            }
2985            case "routing": {
2986                if (e.getSource() instanceof LayoutBlock) {
2987                    LayoutBlock sourceLayoutBlock = (LayoutBlock) e.getSource();
2988                    if (enableUpdateRouteLogging) {
2989                        log.info("From {} we have a routing packet update from neighbour {}", this.getDisplayName(), sourceLayoutBlock.getDisplayName());
2990                    }
2991                    RoutingPacket update = (RoutingPacket) e.getNewValue();
2992                    int updateType = update.getPacketType();
2993                    switch (updateType) {
2994                        case ADDITION: {
2995                            if (enableUpdateRouteLogging) {
2996                                log.info("\t    updateType: Addition");
2997                            }
2998                            // InstanceManager.getDefault(
2999                            // LayoutBlockManager.class).setLastRoutingChange();
3000                            addRouteFromNeighbour(sourceLayoutBlock, update);
3001                            break;
3002                        }
3003                        case UPDATE: {
3004                            if (enableUpdateRouteLogging) {
3005                                log.info("\t    updateType: Update");
3006                            }
3007                            updateRoutingInfo(sourceLayoutBlock, update);
3008                            break;
3009                        }
3010                        case REMOVAL: {
3011                            if (enableUpdateRouteLogging) {
3012                                log.info("\t    updateType: Removal");
3013                            }
3014                            InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3015                            removeRouteFromNeighbour(sourceLayoutBlock, update);
3016                            break;
3017                        }
3018                        default: {
3019                            break;
3020                        }
3021                    }   // switch (updateType)
3022                }   // if (e.getSource() instanceof LayoutBlock)
3023                break;
3024            }
3025            default: {
3026                log.debug("Unhandled propertyChange({}): ", e);
3027                break;
3028            }
3029        }   // switch (e.getPropertyName())
3030    }   // propertyChange
3031
3032    /**
3033     * Get valid Routes, based upon the next block and destination block
3034     *
3035     * @param nxtBlock next block
3036     * @param dstBlock final block
3037     * @return routes that fit, or null
3038     */
3039    @CheckForNull
3040    Routes getValidRoute(Block nxtBlock, Block dstBlock) {
3041        if ((nxtBlock != null) && (dstBlock != null)) {
3042            List<Routes> rtr = getRouteByNeighbour(nxtBlock);
3043
3044            if (rtr.isEmpty()) {
3045                log.debug("From {}, no routes returned for getRouteByNeighbour({})",
3046                        this.getDisplayName(),
3047                        nxtBlock.getDisplayName());
3048                return null;
3049            }
3050
3051            for (Routes rt : rtr) {
3052                if (rt.getDestBlock() == dstBlock) {
3053                    log.debug("From {}, found dest {}.", this.getDisplayName(), dstBlock.getDisplayName());
3054                    return rt;
3055                }
3056            }
3057            log.debug("From {}, no routes to {}.", this.getDisplayName(), nxtBlock.getDisplayName());
3058        } else {
3059            log.warn("getValidRoute({}, {}",
3060                    (nxtBlock != null) ? nxtBlock.getDisplayName() : "<null>",
3061                    (dstBlock != null) ? dstBlock.getDisplayName() : "<null>");
3062        }
3063        return null;
3064    }
3065
3066    /**
3067     * Is the route to the destination block, going via our neighbouring block
3068     * valid. ie Does the block have a route registered via neighbour
3069     * "protecting" to the destination block.
3070     *
3071     * @param protecting  neighbour block that might protect
3072     * @param destination block
3073     * @return true if we have valid path to block
3074     */
3075    public boolean isRouteToDestValid(Block protecting, Block destination) {
3076        if (protecting == destination) {
3077            log.debug("protecting and destination blocks are the same therefore we need to check if we have a valid neighbour");
3078
3079            // We are testing for a directly connected block.
3080            if (getAdjacency(protecting) != null) {
3081                return true;
3082            }
3083        } else if (getValidRoute(protecting, destination) != null) {
3084            return true;
3085        }
3086        return false;
3087    }
3088
3089    /**
3090     * Get a list of valid Routes to our destination block
3091     *
3092     * @param dstBlock target to find
3093     * @return routes between this and dstBlock
3094     */
3095    List<Routes> getDestRoutes(Block dstBlock) {
3096        List<Routes> rtr = new ArrayList<>();
3097        for (Routes route : routes) {
3098            if (route.getDestBlock() == dstBlock) {
3099                rtr.add(route);
3100            }
3101        }
3102        return rtr;
3103    }
3104
3105    /**
3106     * Get a list of valid Routes via our next block
3107     *
3108     * @param nxtBlock target block
3109     * @return list of routes to target block
3110     */
3111    List<Routes> getNextRoutes(Block nxtBlock) {
3112        List<Routes> rtr = new ArrayList<>();
3113        for (Routes route : routes) {
3114            if (route.getNextBlock() == nxtBlock) {
3115                rtr.add(route);
3116            }
3117        }
3118        return rtr;
3119    }
3120
3121    void updateRoutingInfo(Routes route) {
3122        if (route.getHopCount() >= 254) {
3123            return;
3124        }
3125        Block destBlock = route.getDestBlock();
3126
3127        RoutingPacket update = new RoutingPacket(UPDATE, destBlock, getBestRouteByHop(destBlock).getHopCount() + 1,
3128                ((getBestRouteByMetric(destBlock).getMetric()) + metric),
3129                ((getBestRouteByMetric(destBlock).getMetric())
3130                + block.getLengthMm()), -1,
3131                getNextPacketID());
3132        firePropertyChange("routing", null, update);
3133    }
3134
3135    // This lot might need changing to only forward on the best route details.
3136    void updateRoutingInfo(LayoutBlock src, RoutingPacket update) {
3137        if (enableUpdateRouteLogging) {
3138            log.info("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", this.getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3139        }
3140        Block srcblk = src.getBlock();
3141        Adjacencies adj = getAdjacency(srcblk);
3142
3143        if (adj == null) {
3144            if (enableUpdateRouteLogging) {
3145                log.info("From {} packet is from a src that is not registered {}", this.getDisplayName(), srcblk.getDisplayName());
3146            }
3147            // If the packet is from a src that is not registered as a neighbour
3148            // Then we will simply reject it.
3149            return;
3150        }
3151
3152        if (updatePacketActedUpon(update.getPacketId())) {
3153            if (adj.updatePacketActedUpon(update.getPacketId())) {
3154                if (enableUpdateRouteLogging) {
3155                    log.info("Reject packet update as we have already acted up on it from this neighbour");
3156                }
3157                return;
3158            }
3159        }
3160
3161        if (enableUpdateRouteLogging) {
3162            log.info("From {} an Update packet from neighbour {}", this.getDisplayName(), src.getDisplayName());
3163        }
3164
3165        Block updateBlock = update.getBlock();
3166
3167        // Block srcblk = src.getBlock();
3168        // Need to add in a check to make sure that we have a route registered from the source neighbour
3169        // for the block that they are referring too.
3170        if (updateBlock == this.getBlock()) {
3171            if (enableUpdateRouteLogging) {
3172                log.info("Reject packet update as it is a route advertised by our selves");
3173            }
3174            return;
3175        }
3176
3177        Routes ro;
3178        boolean neighbour = false;
3179        if (updateBlock == srcblk) {
3180            // Very likely that this update is from a neighbour about its own status.
3181            ro = getValidRoute(this.getBlock(), updateBlock);
3182            neighbour = true;
3183        } else {
3184            ro = getValidRoute(srcblk, updateBlock);
3185        }
3186
3187        if (ro == null) {
3188            if (enableUpdateRouteLogging) {
3189                log.info("From {} update is from a source that we do not have listed as a route to the destination", this.getDisplayName());
3190                log.info("From {} update packet is for a block that we do not have route registered for {}", this.getDisplayName(), updateBlock.getDisplayName());
3191            }
3192            // If the packet is for a dest that is not in the routing table
3193            // Then we will simply reject it.
3194            return;
3195        }
3196        /*This prevents us from entering into an update loop.
3197           We only add it to our list once it has passed through as being a valid
3198           packet, otherwise we may get the same packet id back, but from a valid source
3199           which would end up be rejected*/
3200
3201        actedUponUpdates.add(update.getPacketId());
3202        adj.addPacketReceivedFromNeighbour(update.getPacketId());
3203
3204        int hopCount = update.getHopCount();
3205        int packetmetric = update.getMetric();
3206        int blockstate = update.getBlockState();
3207        float length = update.getLength();
3208
3209        // Need to add in a check for a block that is directly connected.
3210        if (hopCount != -1) {
3211            // Was increase hop count before setting it
3212            // int oldHop = ro.getHopCount();
3213            if (ro.getHopCount() != hopCount) {
3214                if (enableUpdateRouteLogging) {
3215                    log.info("{} Hop counts to {} not the same so will change from {} to {}", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getHopCount(), hopCount);
3216                }
3217                ro.setHopCount(hopCount);
3218                hopCount++;
3219            } else {
3220                // No point in forwarding on the update if the hopcount hasn't changed
3221                hopCount = -1;
3222            }
3223        }
3224
3225        // bad to use values as errors, but it's pre-existing code, and code wins
3226        if ((int) length != -1) {
3227            // Length is added at source
3228            float oldLength = ro.getLength();
3229            if (!MathUtil.equals(oldLength, length)) {
3230                ro.setLength(length);
3231                boolean forwardUpdate = true;
3232
3233                if (ro != getBestRouteByLength(update.getBlock())) {
3234                    forwardUpdate = false;
3235                }
3236
3237                if (enableUpdateRouteLogging) {
3238                    log.info("From {} updating length from {} to {}", this.getDisplayName(), oldLength, length);
3239                }
3240
3241                if (neighbour) {
3242                    length = srcblk.getLengthMm();
3243                    adj.setLength(length);
3244
3245                    // ro.setLength(length);
3246                    // Also if neighbour we need to update the cost of the routes via it to reflect the new metric 02/20/2011
3247                    if (forwardUpdate) {
3248                        List<Routes> neighbourRoute = getNextRoutes(srcblk);
3249
3250                        // neighbourRoutes, contains all the routes that have been advertised by the neighbour
3251                        // that will need to have their metric updated to reflect the change.
3252                        for (Routes nRo : neighbourRoute) {
3253                            // Need to remove old metric to the neigbour, then add the new one on
3254                            float updateLength = nRo.getLength();
3255                            updateLength = (updateLength - oldLength) + length;
3256
3257                            if (enableUpdateRouteLogging) {
3258                                log.info("From {} update metric for route {} from {} to {}", this.getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getLength(), updateLength);
3259                            }
3260                            nRo.setLength(updateLength);
3261                            List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk);
3262                            RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), -1, -1, updateLength + block.getLengthMm(), -1, getNextPacketID());
3263                            updateRoutesToNeighbours(messageRecipients, nRo, newUpdate);
3264                        }
3265                    }
3266                } else if (forwardUpdate) {
3267                    // This can cause a loop, if the layout is in a loop, so we send out the same packetID.
3268                    List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3269                    RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1,
3270                            length + block.getLengthMm(), -1, update.getPacketId());
3271                    updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3272                }
3273                length += metric;
3274            } else {
3275                length = -1;
3276            }
3277        }
3278
3279        if (packetmetric != -1) {
3280            // Metric is added at source
3281            // Keep a reference of the old metric.
3282            int oldmetric = ro.getMetric();
3283            if (oldmetric != packetmetric) {
3284                ro.setMetric(packetmetric);
3285
3286                if (enableUpdateRouteLogging) {
3287                    log.info("From {} updating metric from {} to {}", this.getDisplayName(), oldmetric, packetmetric);
3288                }
3289                boolean forwardUpdate = true;
3290
3291                if (ro != getBestRouteByMetric(update.getBlock())) {
3292                    forwardUpdate = false;
3293                }
3294
3295                // if the metric update is for a neighbour then we will go directly to the neighbour for the value,
3296                // rather than trust what is in the message at this stage.
3297                if (neighbour) {
3298                    packetmetric = src.getBlockMetric();
3299                    adj.setMetric(packetmetric);
3300
3301                    if (forwardUpdate) {
3302                        // ro.setMetric(packetmetric);
3303                        // Also if neighbour we need to update the cost of the routes via it to
3304                        // reflect the new metric 02/20/2011
3305                        List<Routes> neighbourRoute = getNextRoutes(srcblk);
3306
3307                        // neighbourRoutes, contains all the routes that have been advertised by the neighbour that
3308                        // will need to have their metric updated to reflect the change.
3309                        for (Routes nRo : neighbourRoute) {
3310                            // Need to remove old metric to the neigbour, then add the new one on
3311                            int updatemet = nRo.getMetric();
3312                            updatemet = (updatemet - oldmetric) + packetmetric;
3313
3314                            if (enableUpdateRouteLogging) {
3315                                log.info("From {} update metric for route {} from {} to {}", this.getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getMetric(), updatemet);
3316                            }
3317                            nRo.setMetric(updatemet);
3318                            List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk);
3319                            RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), hopCount, updatemet + metric, -1, -1, getNextPacketID());
3320                            updateRoutesToNeighbours(messageRecipients, nRo, newUpdate);
3321                        }
3322                    }
3323                } else if (forwardUpdate) {
3324                    // This can cause a loop, if the layout is in a loop, so we send out the same packetID.
3325                    List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3326                    RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount,
3327                            packetmetric + metric, -1, -1, update.getPacketId());
3328                    updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3329                }
3330                packetmetric += metric;
3331                // Think we need a list of routes that originate from this source neighbour
3332            } else {
3333                // No point in forwarding on the update if the metric hasn't changed
3334                packetmetric = -1;
3335                // Potentially when we do this we need to update all the routes that go via this block, not just this route.
3336            }
3337        }
3338
3339        if (blockstate != -1) {
3340            // We will update all the destination blocks with the new state, it
3341            // saves re-firing off new updates block status
3342            boolean stateUpdated = false;
3343            List<Routes> rtr = getDestRoutes(updateBlock);
3344
3345            for (Routes rt : rtr) {
3346                if (rt.getState() != blockstate) {
3347                    stateUpdated = true;
3348                    rt.stateChange();
3349                }
3350            }
3351
3352            if (stateUpdated) {
3353                RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, -1, blockstate, getNextPacketID());
3354                firePropertyChange("routing", null, newUpdate);
3355            }
3356        }
3357
3358        // We need to expand on this so that any update to routing metric is propergated correctly
3359        if ((packetmetric != -1) || (hopCount != -1) || (length != -1)) {
3360            // We only want to send the update on to neighbours that we have advertised the route to.
3361            List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3362            RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, packetmetric,
3363                    length, blockstate, update.getPacketId());
3364            updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3365        }
3366        // Was just pass on hop count
3367    }
3368
3369    void updateRoutesToNeighbours(List<Block> messageRecipients, Routes ro, RoutingPacket update) {
3370        for (Block messageRecipient : messageRecipients) {
3371            Adjacencies adj = getAdjacency(messageRecipient);
3372            if (adj.advertiseRouteToNeighbour(ro)) {
3373                adj.addRouteAdvertisedToNeighbour(ro);
3374                LayoutBlock recipient = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(messageRecipient);
3375                if (recipient != null) {
3376                    recipient.updateRoutingInfo(this, update);
3377                }
3378            }
3379        }
3380    }
3381
3382    Routes getBestRouteByMetric(Block dest) {
3383        // int bestHopCount = 255;
3384        int bestMetric = 965000;
3385        int bestIndex = -1;
3386
3387        List<Routes> destRoutes = getDestRoutes(dest);
3388        for (int i = 0; i < destRoutes.size(); i++) {
3389            if (destRoutes.get(i).getMetric() < bestMetric) {
3390                bestMetric = destRoutes.get(i).getMetric();
3391                bestIndex = i;
3392            }
3393        }
3394
3395        if (bestIndex == -1) {
3396            return null;
3397        }
3398        return destRoutes.get(bestIndex);
3399    }
3400
3401    Routes getBestRouteByHop(Block dest) {
3402        int bestHopCount = 255;
3403        // int bestMetric = 965000;
3404        int bestIndex = -1;
3405
3406        List<Routes> destRoutes = getDestRoutes(dest);
3407        for (int i = 0; i < destRoutes.size(); i++) {
3408            if (destRoutes.get(i).getHopCount() < bestHopCount) {
3409                bestHopCount = destRoutes.get(i).getHopCount();
3410                bestIndex = i;
3411            }
3412        }
3413
3414        if (bestIndex == -1) {
3415            return null;
3416        }
3417        return destRoutes.get(bestIndex);
3418    }
3419
3420    Routes getBestRouteByLength(Block dest) {
3421        // int bestHopCount = 255;
3422        // int bestMetric = 965000;
3423        // long bestLength = 999999999;
3424        int bestIndex = -1;
3425        List<Routes> destRoutes = getDestRoutes(dest);
3426        float bestLength = destRoutes.get(0).getLength();
3427
3428        for (int i = 0; i < destRoutes.size(); i++) {
3429            if (destRoutes.get(i).getLength() < bestLength) {
3430                bestLength = destRoutes.get(i).getLength();
3431                bestIndex = i;
3432            }
3433        }
3434
3435        if (bestIndex == -1) {
3436            return null;
3437        }
3438        return destRoutes.get(bestIndex);
3439    }
3440
3441    void addRouteToNeighbours(Routes ro) {
3442        if (enableAddRouteLogging) {
3443            log.info("From {} Add route to neighbour", this.getDisplayName());
3444        }
3445        Block nextHop = ro.getNextBlock();
3446        List<LayoutBlock> validFromPath = new ArrayList<>();
3447
3448        if (enableAddRouteLogging) {
3449            log.info("From {} new block {}", this.getDisplayName(), nextHop.getDisplayName());
3450        }
3451
3452        for (int i = 0; i < throughPaths.size(); i++) {
3453            LayoutBlock validBlock = null;
3454
3455            if (enableAddRouteLogging) {
3456                log.info("Through routes index {}", i);
3457                log.info("From {} A through routes {} {}", this.getDisplayName(), throughPaths.get(i).getSourceBlock().getDisplayName(), throughPaths.get(i).getDestinationBlock().getDisplayName());
3458            }
3459
3460            /*As the through paths include each possible path, ie 2 > 3 and 3 > 2
3461               as seperate entries then we only need to forward the new route to those
3462               source blocks that have a desination of the next hop*/
3463            if (throughPaths.get(i).getDestinationBlock() == nextHop) {
3464                if (getAdjacency(throughPaths.get(i).getSourceBlock()).isMutual()) {
3465                    validBlock = InstanceManager.getDefault(
3466                            LayoutBlockManager.class).
3467                            getLayoutBlock(throughPaths.get(i).getSourceBlock());
3468                }
3469            }
3470
3471            // only need to add it the once.  Not sure if the contains is required.
3472            if ((validBlock != null) && (!validFromPath.contains(validBlock))) {
3473                validFromPath.add(validBlock);
3474            }
3475        }
3476
3477        if (enableAddRouteLogging) {
3478            log.info("From {} ===== valid from size path {} ==== (addroutetoneigh)", this.getDisplayName(), validFromPath.size());
3479
3480            validFromPath.forEach((valid) -> log.info("fromPath: {}", valid.getDisplayName()));
3481            log.info("Next Hop {}", nextHop.getDisplayName());
3482        }
3483        RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1,
3484                ro.getMetric() + metric,
3485                (ro.getLength() + getBlock().getLengthMm()), -1, getNextPacketID());
3486
3487        for (LayoutBlock layoutBlock : validFromPath) {
3488            Adjacencies adj = getAdjacency(layoutBlock.getBlock());
3489            if (adj.advertiseRouteToNeighbour(ro)) {
3490                // getBestRouteByHop(destBlock).getHopCount()+1, ((getBestRouteByMetric(destBlock).getMetric())+metric),
3491                //((getBestRouteByMetric(destBlock).getMetric())+block.getLengthMm())
3492                if (enableAddRouteLogging) {
3493                    log.info("From {} Sending update to {} As this has a better hop count or metric", this.getDisplayName(), layoutBlock.getDisplayName());
3494                }
3495                adj.addRouteAdvertisedToNeighbour(ro);
3496                layoutBlock.addRouteFromNeighbour(this, update);
3497            }
3498        }
3499    }
3500
3501    void addRouteFromNeighbour(LayoutBlock src, RoutingPacket update) {
3502        if (enableAddRouteLogging) {
3503            // log.info("From " + this.getDisplayName() + " packet to be added from neighbour " + src.getDisplayName());
3504            log.info("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}", this.getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(), update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3505        }
3506        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3507        Block destBlock = update.getBlock();
3508        Block srcblk = src.getBlock();
3509
3510        if (destBlock == this.getBlock()) {
3511            if (enableAddRouteLogging) {
3512                log.info("Reject packet update as it is to a route advertised by our selves");
3513            }
3514            return;
3515        }
3516
3517        Adjacencies adj = getAdjacency(srcblk);
3518        if (adj == null) {
3519            if (enableAddRouteLogging) {
3520                log.info("From {} packet is from a src that is not registered {}", this.getDisplayName(), srcblk.getDisplayName());
3521            }
3522            // If the packet is from a src that is not registered as a neighbour
3523            // Then we will simply reject it.
3524            return;
3525        } else if (adj.getPacketFlow() == TXONLY) {
3526            if (enableAddRouteLogging) {
3527                log.info("From {} packet is from a src {} that is registered as one that we should be transmitting to only", this.getDisplayName(), src.getDisplayName());
3528            }
3529            // we should only be transmitting routes to this neighbour not receiving them
3530            return;
3531        }
3532        int hopCount = update.getHopCount();
3533        int updatemetric = update.getMetric();
3534        float length = update.getLength();
3535
3536        if (hopCount > 255) {
3537            if (enableAddRouteLogging) {
3538                log.info("From {} hop count exceeded {}", this.getDisplayName(), destBlock.getDisplayName());
3539            }
3540            return;
3541        }
3542
3543        for (Routes ro : routes) {
3544            if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destBlock)) {
3545                if (enableAddRouteLogging) {
3546                    log.info("From {} Route to {} is already configured", this.getDisplayName(), destBlock.getDisplayName());
3547                    log.info("{} v {}", ro.getHopCount(), hopCount);
3548                    log.info("{} v {}", ro.getMetric(), updatemetric);
3549                }
3550                updateRoutingInfo(src, update);
3551                return;
3552            }
3553        }
3554
3555        if (enableAddRouteLogging) {
3556            log.info("From {} We should be adding route {}", this.getDisplayName(), destBlock.getDisplayName());
3557        }
3558
3559        // We need to propergate out the routes that we have added to our neighbour
3560        int direction = adj.getDirection();
3561        Routes route = new Routes(destBlock, srcblk, hopCount, direction, updatemetric, length);
3562        routes.add(route);
3563
3564        // Need to propergate the route down to our neighbours
3565        addRouteToNeighbours(route);
3566    }
3567
3568    /* this should look after removal of a specific next hop from our neighbour*/
3569    /**
3570     * Get the direction of travel to our neighbouring block.
3571     *
3572     * @param neigh neighbor block
3573     * @return direction to get to neighbor block
3574     */
3575    public int getNeighbourDirection(LayoutBlock neigh) {
3576        if (neigh == null) {
3577            return Path.NONE;
3578        }
3579        Block neighbourBlock = neigh.getBlock();
3580        return getNeighbourDirection(neighbourBlock);
3581    }
3582
3583    public int getNeighbourDirection(Block neighbourBlock) {
3584        for (Adjacencies neighbour : neighbours) {
3585            if (neighbour.getBlock() == neighbourBlock) {
3586                return neighbour.getDirection();
3587            }
3588        }
3589        return Path.NONE;
3590    }
3591
3592    Adjacencies getAdjacency(Block blk) {
3593        for (Adjacencies neighbour : neighbours) {
3594            if (neighbour.getBlock() == blk) {
3595                return neighbour;
3596            }
3597        }
3598        return null;
3599    }
3600
3601    final static int ADDITION = 0x00;
3602    final static int UPDATE = 0x02;
3603    final static int REMOVAL = 0x04;
3604
3605    final static int RXTX = 0x00;
3606    final static int RXONLY = 0x02;
3607    final static int TXONLY = 0x04;
3608    final static int NONE = 0x08;
3609    int metric = 100;
3610
3611    private static class RoutingPacket {
3612
3613        int packetType;
3614        Block block;
3615        int hopCount = -1;
3616        int packetMetric = -1;
3617        int blockstate = -1;
3618        float length = -1;
3619        Integer packetRef = -1;
3620
3621        RoutingPacket(int packetType, Block blk, int hopCount, int packetMetric, float length, int blockstate, Integer packetRef) {
3622            this.packetType = packetType;
3623            this.block = blk;
3624            this.hopCount = hopCount;
3625            this.packetMetric = packetMetric;
3626            this.blockstate = blockstate;
3627            this.packetRef = packetRef;
3628            this.length = length;
3629        }
3630
3631        int getPacketType() {
3632            return packetType;
3633        }
3634
3635        Block getBlock() {
3636            return block;
3637        }
3638
3639        int getHopCount() {
3640            return hopCount;
3641        }
3642
3643        int getMetric() {
3644            return packetMetric;
3645        }
3646
3647        int getBlockState() {
3648            return blockstate;
3649        }
3650
3651        float getLength() {
3652            return length;
3653        }
3654
3655        Integer getPacketId() {
3656            return packetRef;
3657        }
3658    }
3659
3660    /**
3661     * Get the number of neighbor blocks attached to this block.
3662     *
3663     * @return count of neighbor
3664     */
3665    public int getNumberOfNeighbours() {
3666        return neighbours.size();
3667    }
3668
3669    /**
3670     * Get the neighboring block at index i.
3671     *
3672     * @param i index to neighbor
3673     * @return neighbor block
3674     */
3675    public Block getNeighbourAtIndex(int i) {
3676        return neighbours.get(i).getBlock();
3677    }
3678
3679    /**
3680     * Get the direction of travel to neighbouring block at index i.
3681     *
3682     * @param i index in neighbors
3683     * @return neighbor block
3684     */
3685    public int getNeighbourDirection(int i) {
3686        return neighbours.get(i).getDirection();
3687    }
3688
3689    /**
3690     * Get the metric/cost to neighbouring block at index i.
3691     *
3692     * @param i index in neighbors
3693     * @return metric of neighbor
3694     */
3695    public int getNeighbourMetric(int i) {
3696        return neighbours.get(i).getMetric();
3697    }
3698
3699    /**
3700     * Get the flow of traffic to and from neighbouring block at index i RXTX -
3701     * Means Traffic can flow both ways between the blocks RXONLY - Means we can
3702     * only receive traffic from our neighbour, we can not send traffic to it
3703     * TXONLY - Means we do not receive traffic from our neighbour, but can send
3704     * traffic to it.
3705     *
3706     * @param i index in neighbors
3707     * @return direction of traffic
3708     */
3709    public String getNeighbourPacketFlowAsString(int i) {
3710        return decodePacketFlow(neighbours.get(i).getPacketFlow());
3711    }
3712
3713    /**
3714     * Is our neighbouring block at index i a mutual neighbour, ie both blocks
3715     * have each other registered as neighbours and are exchanging information.
3716     *
3717     * @param i index of neighbor
3718     * @return true if both are mutual neighbors
3719     */
3720    public boolean isNeighbourMutual(int i) {
3721        return neighbours.get(i).isMutual();
3722    }
3723
3724    int getNeighbourIndex(Adjacencies adj) {
3725        for (int i = 0; i < neighbours.size(); i++) {
3726            if (neighbours.get(i) == adj) {
3727                return i;
3728            }
3729        }
3730        return -1;
3731    }
3732
3733    private class Adjacencies {
3734
3735        Block adjBlock;
3736        LayoutBlock adjLayoutBlock;
3737        int direction;
3738        int packetFlow = RXTX;
3739        boolean mutualAdjacency = false;
3740
3741        HashMap<Block, Routes> adjDestRoutes = new HashMap<>();
3742        List<Integer> actedUponUpdates = new ArrayList<>(501);
3743
3744        Adjacencies(Block block, int dir, int packetFlow) {
3745            adjBlock = block;
3746            direction = dir;
3747            this.packetFlow = packetFlow;
3748        }
3749
3750        Block getBlock() {
3751            return adjBlock;
3752        }
3753
3754        LayoutBlock getLayoutBlock() {
3755            return adjLayoutBlock;
3756        }
3757
3758        int getDirection() {
3759            return direction;
3760        }
3761
3762        // If a set true on mutual, then we could go through the list of what to send out to neighbour
3763        void setMutual(boolean mut) {
3764            if (mut == mutualAdjacency) {   // No change will exit
3765                return;
3766            }
3767            mutualAdjacency = mut;
3768            if (mutualAdjacency) {
3769                adjLayoutBlock = InstanceManager.getDefault(
3770                        LayoutBlockManager.class).getLayoutBlock(adjBlock);
3771            }
3772        }
3773
3774        boolean isMutual() {
3775            return mutualAdjacency;
3776        }
3777
3778        int getPacketFlow() {
3779            return packetFlow;
3780        }
3781
3782        void setPacketFlow(int flow) {
3783            if (flow != packetFlow) {
3784                int oldFlow = packetFlow;
3785                packetFlow = flow;
3786                firePropertyChange("neighbourpacketflow", oldFlow, packetFlow);
3787            }
3788        }
3789
3790        // The metric could just be read directly from the neighbour as we have no
3791        // need to specifically keep a copy of it here this is here just to fire off the change
3792        void setMetric(int met) {
3793            firePropertyChange("neighbourmetric", null, getNeighbourIndex(this));
3794        }
3795
3796        int getMetric() {
3797            if (adjLayoutBlock != null) {
3798                return adjLayoutBlock.getBlockMetric();
3799            }
3800            adjLayoutBlock = InstanceManager.getDefault(
3801                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3802            if (adjLayoutBlock != null) {
3803                return adjLayoutBlock.getBlockMetric();
3804            }
3805
3806            if (log.isDebugEnabled()) {
3807                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3808            }
3809            return -1;
3810        }
3811
3812        void setLength(float len) {
3813            firePropertyChange("neighbourlength", null, getNeighbourIndex(this));
3814        }
3815
3816        float getLength() {
3817            if (adjLayoutBlock != null) {
3818                return adjLayoutBlock.getBlock().getLengthMm();
3819            }
3820            adjLayoutBlock = InstanceManager.getDefault(
3821                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3822            if (adjLayoutBlock != null) {
3823                return adjLayoutBlock.getBlock().getLengthMm();
3824            }
3825
3826            if (log.isDebugEnabled()) {
3827                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3828            }
3829            return -1;
3830        }
3831
3832        void removeRouteAdvertisedToNeighbour(Routes removeRoute) {
3833            Block dest = removeRoute.getDestBlock();
3834
3835            if (adjDestRoutes.get(dest) == removeRoute) {
3836                adjDestRoutes.remove(dest);
3837            }
3838        }
3839
3840        void removeRouteAdvertisedToNeighbour(Block block) {
3841            adjDestRoutes.remove(block);
3842        }
3843
3844        void addRouteAdvertisedToNeighbour(Routes addedRoute) {
3845            adjDestRoutes.put(addedRoute.getDestBlock(), addedRoute);
3846        }
3847
3848        boolean advertiseRouteToNeighbour(Routes routeToAdd) {
3849            if (!isMutual()) {
3850                log.debug("In block {}: Neighbour is not mutual so will not advertise it (Routes {})", getDisplayName(), routeToAdd);
3851
3852                return false;
3853            }
3854
3855            // Just wonder if this should forward on the new packet to the neighbour?
3856            Block dest = routeToAdd.getDestBlock();
3857            if (!adjDestRoutes.containsKey(dest)) {
3858                log.debug("In block {}: We are not currently advertising a route to the destination to neighbour: {}", getDisplayName(), dest.getDisplayName());
3859
3860                return true;
3861            }
3862
3863            if (routeToAdd.getHopCount() > 255) {
3864                log.debug("Hop count is gereater than 255 we will therefore do nothing with this route");
3865                return false;
3866            }
3867            Routes existingRoute = adjDestRoutes.get(dest);
3868            if (existingRoute.getMetric() > routeToAdd.getMetric()) {
3869                return true;
3870            }
3871            if (existingRoute.getHopCount() > routeToAdd.getHopCount()) {
3872                return true;
3873            }
3874
3875            if (existingRoute == routeToAdd) {
3876                // We return true as the metric might have changed
3877                return false;
3878            }
3879            return false;
3880        }
3881
3882        boolean updatePacketActedUpon(Integer packetID) {
3883            return actedUponUpdates.contains(packetID);
3884        }
3885
3886        void addPacketReceivedFromNeighbour(Integer packetID) {
3887            actedUponUpdates.add(packetID);
3888            if (actedUponUpdates.size() > 500) {
3889                actedUponUpdates.subList(0, 250).clear();
3890            }
3891        }
3892
3893        void dispose() {
3894            adjBlock = null;
3895            adjLayoutBlock = null;
3896            mutualAdjacency = false;
3897            adjDestRoutes = null;
3898            actedUponUpdates = null;
3899        }
3900    }
3901
3902    /**
3903     * Get the number of routes that the block has registered.
3904     *
3905     * @return count of routes
3906     */
3907    public int getNumberOfRoutes() {
3908        return routes.size();
3909    }
3910
3911    /**
3912     * Get the direction of route i.
3913     *
3914     * @param i index in routes
3915     * @return direction
3916     */
3917    public int getRouteDirectionAtIndex(int i) {
3918        return routes.get(i).getDirection();
3919    }
3920
3921    /**
3922     * Get the destination block at route i
3923     *
3924     * @param i index in routes
3925     * @return dest block from route
3926     */
3927    public Block getRouteDestBlockAtIndex(int i) {
3928        return routes.get(i).getDestBlock();
3929    }
3930
3931    /**
3932     * Get the next block at route i
3933     *
3934     * @param i index in routes
3935     * @return next block from route
3936     */
3937    public Block getRouteNextBlockAtIndex(int i) {
3938        return routes.get(i).getNextBlock();
3939    }
3940
3941    /**
3942     * Get the hop count of route i.<br>
3943     * The Hop count is the number of other blocks that we traverse to get to
3944     * the destination
3945     *
3946     * @param i index in routes
3947     * @return hop count
3948     */
3949    public int getRouteHopCountAtIndex(int i) {
3950        return routes.get(i).getHopCount();
3951    }
3952
3953    /**
3954     * Get the length of route i.<br>
3955     * The length is the combined length of all the blocks that we traverse to
3956     * get to the destination
3957     *
3958     * @param i index in routes
3959     * @return length of block in route
3960     */
3961    public float getRouteLengthAtIndex(int i) {
3962        return routes.get(i).getLength();
3963    }
3964
3965    /**
3966     * Get the metric/cost at route i
3967     *
3968     * @param i index in routes
3969     * @return metric
3970     */
3971    public int getRouteMetric(int i) {
3972        return routes.get(i).getMetric();
3973    }
3974
3975    /**
3976     * Get the state (Occupied, unoccupied) of the destination layout block at
3977     * index i
3978     *
3979     * @param i index in routes
3980     * @return state of block
3981     */
3982    public int getRouteState(int i) {
3983        return routes.get(i).getState();
3984    }
3985
3986    /**
3987     * Is the route to the destination potentially valid from our block.
3988     *
3989     * @param i index in route
3990     * @return true if route is valid
3991     */
3992    // TODO: Java standard pattern for boolean getters is "isRouteValid()"
3993    public boolean getRouteValid(int i) {
3994        return routes.get(i).isRouteCurrentlyValid();
3995    }
3996
3997    /**
3998     * Get the state of the destination layout block at index i as a string.
3999     *
4000     * @param i index in routes
4001     * @return dest status
4002     */
4003    public String getRouteStateAsString(int i) {
4004        int state = routes.get(i).getState();
4005        switch (state) {
4006            case OCCUPIED: {
4007                return Bundle.getMessage("TrackOccupied"); // i18n using NamedBeanBundle.properties TODO remove duplicate keys
4008            }
4009
4010            case RESERVED: {
4011                return Bundle.getMessage("StateReserved"); // "Reserved"
4012            }
4013
4014            case EMPTY: {
4015                return Bundle.getMessage("StateFree");  // "Free"
4016            }
4017
4018            default: {
4019                return Bundle.getMessage("BeanStateUnknown"); // "Unknown"
4020            }
4021        }
4022    }
4023
4024    int getRouteIndex(Routes r) {
4025        for (int i = 0; i < routes.size(); i++) {
4026            if (routes.get(i) == r) {
4027                return i;
4028            }
4029        }
4030        return -1;
4031    }
4032
4033    /**
4034     * Get the number of layout blocks to our desintation block going from the
4035     * next directly connected block. If the destination block and nextblock are
4036     * the same and the block is also registered as a neighbour then 1 is
4037     * returned. If no valid route to the destination block can be found via the
4038     * next block then -1 is returned. If more than one route exists to the
4039     * destination then the route with the lowest count is returned.
4040     *
4041     * @param destination final block
4042     * @param nextBlock   adjcent block
4043     * @return hop count to final, -1 if not available
4044     */
4045    public int getBlockHopCount(Block destination, Block nextBlock) {
4046        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4047            return 1;
4048        }
4049
4050        for (Routes route : routes) {
4051            if (route.getDestBlock() == destination) {
4052                if (route.getNextBlock() == nextBlock) {
4053                    return route.getHopCount();
4054                }
4055            }
4056        }
4057        return -1;
4058    }
4059
4060    /**
4061     * Get the metric to our desintation block going from the next directly
4062     * connected block. If the destination block and nextblock are the same and
4063     * the block is also registered as a neighbour then 1 is returned. If no
4064     * valid route to the destination block can be found via the next block then
4065     * -1 is returned. If more than one route exists to the destination then the
4066     * route with the lowest count is returned.
4067     *
4068     * @param destination final block
4069     * @param nextBlock   adjcent block
4070     * @return metric to final block, -1 if not available
4071     */
4072    public int getBlockMetric(Block destination, Block nextBlock) {
4073        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4074            return 1;
4075        }
4076
4077        for (Routes route : routes) {
4078            if (route.getDestBlock() == destination) {
4079                if (route.getNextBlock() == nextBlock) {
4080                    return route.getMetric();
4081                }
4082            }
4083        }
4084        return -1;
4085    }
4086
4087    /**
4088     * Get the distance to our desintation block going from the next directly
4089     * connected block. If the destination block and nextblock are the same and
4090     * the block is also registered as a neighbour then 1 is returned. If no
4091     * valid route to the destination block can be found via the next block then
4092     * -1 is returned. If more than one route exists to the destination then the
4093     * route with the lowest count is returned.
4094     *
4095     * @param destination final block
4096     * @param nextBlock   adjcent block
4097     * @return lenght to final, -1 if not viable
4098     */
4099    public float getBlockLength(Block destination, Block nextBlock) {
4100        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4101            return 1;
4102        }
4103
4104        for (Routes route : routes) {
4105            if (route.getDestBlock() == destination) {
4106                if (route.getNextBlock() == nextBlock) {
4107                    return route.getLength();
4108                }
4109            }
4110        }
4111        return -1;
4112    }
4113
4114    // TODO This needs a propertychange listener adding
4115    private class Routes implements PropertyChangeListener {
4116
4117        int direction;
4118        Block destBlock;
4119        Block nextBlock;
4120        int hopCount;
4121        int routeMetric;
4122        float length;
4123
4124        // int state =-1;
4125        int miscflags = 0x00;
4126        boolean validCurrentRoute = false;
4127
4128        public Routes(Block dstBlock, Block nxtBlock, int hop, int dir, int met, float len) {
4129            destBlock = dstBlock;
4130            nextBlock = nxtBlock;
4131            hopCount = hop;
4132            direction = dir;
4133            routeMetric = met;
4134            length = len;
4135            init();
4136        }
4137
4138        final void init() {
4139            validCurrentRoute = checkIsRouteOnValidThroughPath(this);
4140            firePropertyChange("length", null, null);
4141            destBlock.addPropertyChangeListener(this);
4142        }
4143
4144        @Override
4145        public String toString() {
4146            return "Routes(dst:" + destBlock + ", nxt:" + nextBlock
4147                    + ", hop:" + hopCount + ", dir:" + direction
4148                    + ", met:" + routeMetric + ", len: " + length + ")";
4149        }
4150
4151        @Override
4152        public void propertyChange(PropertyChangeEvent e) {
4153            if (e.getPropertyName().equals("state")) {
4154                stateChange();
4155            }
4156        }
4157
4158        public Block getDestBlock() {
4159            return destBlock;
4160        }
4161
4162        public Block getNextBlock() {
4163            return nextBlock;
4164        }
4165
4166        public int getHopCount() {
4167            return hopCount;
4168        }
4169
4170        public int getDirection() {
4171            return direction;
4172        }
4173
4174        public int getMetric() {
4175            return routeMetric;
4176        }
4177
4178        public float getLength() {
4179            return length;
4180        }
4181
4182        public void setMetric(int met) {
4183            if (met == routeMetric) {
4184                return;
4185            }
4186            routeMetric = met;
4187            firePropertyChange("metric", null, getRouteIndex(this));
4188        }
4189
4190        public void setHopCount(int hop) {
4191            if (hopCount == hop) {
4192                return;
4193            }
4194            hopCount = hop;
4195            firePropertyChange("hop", null, getRouteIndex(this));
4196        }
4197
4198        public void setLength(float len) {
4199            if (len == length) {
4200                return;
4201            }
4202            length = len;
4203            firePropertyChange("length", null, getRouteIndex(this));
4204        }
4205
4206        // This state change is only here for the routing table view
4207        void stateChange() {
4208            firePropertyChange("state", null, getRouteIndex(this));
4209        }
4210
4211        int getState() {
4212            LayoutBlock destLBlock = InstanceManager.getDefault(
4213                    LayoutBlockManager.class).getLayoutBlock(destBlock);
4214            if (destLBlock != null) {
4215                return destLBlock.getBlockStatus();
4216            }
4217
4218            if (log.isDebugEnabled()) {
4219                log.debug("Layout Block {} returned as null", destBlock.getDisplayName());
4220            }
4221            return -1;
4222        }
4223
4224        void setValidCurrentRoute(boolean boo) {
4225            if (validCurrentRoute == boo) {
4226                return;
4227            }
4228            validCurrentRoute = boo;
4229            firePropertyChange("valid", null, getRouteIndex(this));
4230        }
4231
4232        boolean isRouteCurrentlyValid() {
4233            return validCurrentRoute;
4234        }
4235
4236        // Misc flags is not used in general routing, but is used for determining route removals
4237        void setMiscFlags(int f) {
4238            miscflags = f;
4239        }
4240
4241        int getMiscFlags() {
4242            return miscflags;
4243        }
4244    }
4245
4246    /**
4247     * Get the number of valid through paths on this block.
4248     *
4249     * @return count of paths through this block
4250     */
4251    public int getNumberOfThroughPaths() {
4252        return throughPaths.size();
4253    }
4254
4255    /**
4256     * Get the source block at index i
4257     *
4258     * @param i index in throughPaths
4259     * @return source block
4260     */
4261    public Block getThroughPathSource(int i) {
4262        return throughPaths.get(i).getSourceBlock();
4263    }
4264
4265    /**
4266     * Get the destination block at index i
4267     *
4268     * @param i index in throughPaths
4269     * @return final block
4270     */
4271    public Block getThroughPathDestination(int i) {
4272        return throughPaths.get(i).getDestinationBlock();
4273    }
4274
4275    /**
4276     * Is the through path at index i active?
4277     *
4278     * @param i index in path
4279     * @return active or not
4280     */
4281    public Boolean isThroughPathActive(int i) {
4282        return throughPaths.get(i).isPathActive();
4283    }
4284
4285    private class ThroughPaths implements PropertyChangeListener {
4286
4287        Block sourceBlock;
4288        Block destinationBlock;
4289        Path sourcePath;
4290        Path destinationPath;
4291
4292        boolean pathActive = false;
4293
4294        HashMap<Turnout, Integer> _turnouts = new HashMap<>();
4295
4296        ThroughPaths(Block srcBlock, Path srcPath, Block destBlock, Path dstPath) {
4297            sourceBlock = srcBlock;
4298            destinationBlock = destBlock;
4299            sourcePath = srcPath;
4300            destinationPath = dstPath;
4301        }
4302
4303        Block getSourceBlock() {
4304            return sourceBlock;
4305        }
4306
4307        Block getDestinationBlock() {
4308            return destinationBlock;
4309        }
4310
4311        Path getSourcePath() {
4312            return sourcePath;
4313        }
4314
4315        Path getDestinationPath() {
4316            return destinationPath;
4317        }
4318
4319        boolean isPathActive() {
4320            return pathActive;
4321        }
4322
4323        void setTurnoutList(List<LayoutTrackExpectedState<LayoutTurnout>> turnouts) {
4324            if (!_turnouts.isEmpty()) {
4325                Set<Turnout> en = _turnouts.keySet();
4326                en.forEach((listTurnout) -> listTurnout.removePropertyChangeListener(this));
4327            }
4328
4329            // If we have no turnouts in this path, then this path is always active
4330            if (turnouts.isEmpty()) {
4331                pathActive = true;
4332                setRoutesValid(sourceBlock, true);
4333                setRoutesValid(destinationBlock, true);
4334                return;
4335            }
4336            _turnouts = new HashMap<>(turnouts.size());
4337            for (LayoutTrackExpectedState<LayoutTurnout> turnout : turnouts) {
4338                if (turnout.getObject() instanceof LayoutSlip) {
4339                    int slipState = turnout.getExpectedState();
4340                    LayoutSlip ls = (LayoutSlip) turnout.getObject();
4341                    int taState = ls.getTurnoutState(slipState);
4342                    _turnouts.put(ls.getTurnout(), taState);
4343                    ls.getTurnout().addPropertyChangeListener(this, ls.getTurnoutName(), "Layout Block Routing");
4344
4345                    int tbState = ls.getTurnoutBState(slipState);
4346                    _turnouts.put(ls.getTurnoutB(), tbState);
4347                    ls.getTurnoutB().addPropertyChangeListener(this, ls.getTurnoutBName(), "Layout Block Routing");
4348                } else {
4349                    LayoutTurnout lt = turnout.getObject();
4350                    if (lt.getTurnout() != null) {
4351                        _turnouts.put(lt.getTurnout(), turnout.getExpectedState());
4352                        lt.getTurnout().addPropertyChangeListener(this, lt.getTurnoutName(), "Layout Block Routing");
4353                    } else {
4354                        log.error("{} has no physical turnout allocated, block = {}", lt, block.getDisplayName());
4355                    }
4356                }
4357            }
4358        }
4359
4360        @Override
4361        public void propertyChange(PropertyChangeEvent e) {
4362            if (e.getPropertyName().equals("KnownState")) {
4363                Turnout srcTurnout = (Turnout) e.getSource();
4364                int newVal = (Integer) e.getNewValue();
4365                int values = _turnouts.get(srcTurnout);
4366                boolean allset = false;
4367                pathActive = false;
4368
4369                if (newVal == values) {
4370                    allset = true;
4371
4372                    if (_turnouts.size() > 1) {
4373                        for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) {
4374                            if (srcTurnout != entry.getKey()) {
4375                                int state = entry.getKey().getState();
4376                                if (state != entry.getValue()) {
4377                                    allset = false;
4378                                    break;
4379                                }
4380                            }
4381                        }
4382                    }
4383                }
4384                updateActiveThroughPaths(this, allset);
4385                pathActive = allset;
4386            }
4387        }
4388    }
4389
4390    @Nonnull
4391    List<Block> getThroughPathSourceByDestination(Block dest) {
4392        List<Block> a = new ArrayList<>();
4393
4394        for (ThroughPaths throughPath : throughPaths) {
4395            if (throughPath.getDestinationBlock() == dest) {
4396                a.add(throughPath.getSourceBlock());
4397            }
4398        }
4399        return a;
4400    }
4401
4402    @Nonnull
4403    List<Block> getThroughPathDestinationBySource(Block source) {
4404        List<Block> a = new ArrayList<>();
4405
4406        for (ThroughPaths throughPath : throughPaths) {
4407            if (throughPath.getSourceBlock() == source) {
4408                a.add(throughPath.getDestinationBlock());
4409            }
4410        }
4411        return a;
4412    }
4413
4414    /**
4415     * When a route is created, check to see if the through path that this route
4416     * relates to is active.
4417     * @param r The route to check
4418     * @return true if that route is active
4419     */
4420    boolean checkIsRouteOnValidThroughPath(Routes r) {
4421        for (ThroughPaths t : throughPaths) {
4422            if (t.isPathActive()) {
4423                if (t.getDestinationBlock() == r.getNextBlock()) {
4424                    return true;
4425                }
4426                if (t.getSourceBlock() == r.getNextBlock()) {
4427                    return true;
4428                }
4429            }
4430        }
4431        return false;
4432    }
4433
4434    /**
4435     * Go through all the routes and refresh the valid flag.
4436     */
4437    public void refreshValidRoutes() {
4438        for (int i = 0; i < throughPaths.size(); i++) {
4439            ThroughPaths t = throughPaths.get(i);
4440            setRoutesValid(t.getDestinationBlock(), t.isPathActive());
4441            setRoutesValid(t.getSourceBlock(), t.isPathActive());
4442            firePropertyChange("path", null, i);
4443        }
4444    }
4445
4446    // We keep a track of what is paths are active, only so that we can easily mark
4447    // which routes are also potentially valid
4448    List<ThroughPaths> activePaths;
4449
4450    void updateActiveThroughPaths(ThroughPaths tp, boolean active) {
4451        if (enableUpdateRouteLogging) {
4452            log.info("We have been notified that a through path has changed state");
4453        }
4454
4455        if (activePaths == null) {
4456            activePaths = new ArrayList<>();
4457        }
4458
4459        if (active) {
4460            activePaths.add(tp);
4461            setRoutesValid(tp.getSourceBlock(), active);
4462            setRoutesValid(tp.getDestinationBlock(), active);
4463        } else {
4464            // We need to check if either our source or des is in use by another path.
4465            activePaths.remove(tp);
4466            boolean SourceInUse = false;
4467            boolean DestinationInUse = false;
4468
4469            for (ThroughPaths activePath : activePaths) {
4470                Block testSour = activePath.getSourceBlock();
4471                Block testDest = activePath.getDestinationBlock();
4472                if ((testSour == tp.getSourceBlock()) || (testDest == tp.getSourceBlock())) {
4473                    SourceInUse = true;
4474                }
4475                if ((testSour == tp.getDestinationBlock()) || (testDest == tp.getDestinationBlock())) {
4476                    DestinationInUse = true;
4477                }
4478            }
4479
4480            if (!SourceInUse) {
4481                setRoutesValid(tp.getSourceBlock(), active);
4482            }
4483
4484            if (!DestinationInUse) {
4485                setRoutesValid(tp.getDestinationBlock(), active);
4486            }
4487        }
4488
4489        for (int i = 0; i < throughPaths.size(); i++) {
4490            // This is processed simply for the throughpath table.
4491            if (tp == throughPaths.get(i)) {
4492                firePropertyChange("path", null, i);
4493            }
4494        }
4495    }
4496
4497    /**
4498     * Set the valid flag for routes that are on a valid through path.
4499     * @param nxtHopActive the start of the route
4500     * @param state the state to set into the valid flag
4501     */
4502    void setRoutesValid(Block nxtHopActive, boolean state) {
4503        List<Routes> rtr = getRouteByNeighbour(nxtHopActive);
4504        rtr.forEach((rt) -> rt.setValidCurrentRoute(state));
4505    }
4506
4507    @Override
4508    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
4509        if ("CanDelete".equals(evt.getPropertyName())) {    // NOI18N
4510            if (evt.getOldValue() instanceof Sensor) {
4511                if (evt.getOldValue().equals(getOccupancySensor())) {
4512                    throw new PropertyVetoException(getDisplayName(), evt);
4513                }
4514            }
4515
4516            if (evt.getOldValue() instanceof Memory) {
4517                if (evt.getOldValue().equals(getMemory())) {
4518                    throw new PropertyVetoException(getDisplayName(), evt);
4519                }
4520            }
4521        } else if ("DoDelete".equals(evt.getPropertyName())) {  // NOI18N
4522            // Do nothing at this stage
4523            if (evt.getOldValue() instanceof Sensor) {
4524                if (evt.getOldValue().equals(getOccupancySensor())) {
4525                    setOccupancySensorName(null);
4526                }
4527            }
4528
4529            if (evt.getOldValue() instanceof Memory) {
4530                if (evt.getOldValue().equals(getMemory())) {
4531                    setMemoryName(null);
4532                }
4533            }
4534        }
4535    }
4536
4537    @Override
4538    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
4539        List<NamedBeanUsageReport> report = new ArrayList<>();
4540        if (bean != null) {
4541            if (bean.equals(getBlock())) {
4542                report.add(new NamedBeanUsageReport("LayoutBlockBlock"));  // NOI18N
4543            }
4544            if (bean.equals(getMemory())) {
4545                report.add(new NamedBeanUsageReport("LayoutBlockMemory"));  // NOI18N
4546            }
4547            if (bean.equals(getOccupancySensor())) {
4548                report.add(new NamedBeanUsageReport("LayoutBlockSensor"));  // NOI18N
4549            }
4550            for (int i = 0; i < getNumberOfNeighbours(); i++) {
4551                if (bean.equals(getNeighbourAtIndex(i))) {
4552                    report.add(new NamedBeanUsageReport("LayoutBlockNeighbor", "Neighbor"));  // NOI18N
4553                }
4554            }
4555        }
4556        return report;
4557    }
4558
4559    @Override
4560    public String getBeanType() {
4561        return Bundle.getMessage("BeanNameLayoutBlock");
4562    }
4563
4564    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutBlock.class);
4565
4566}