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