001package jmri.jmrit.display.layoutEditor;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.annotation.CheckReturnValue;
007import javax.annotation.CheckForNull;
008
009import jmri.NamedBean;
010import jmri.Sensor;
011import jmri.SignalHead;
012import jmri.SignalMast;
013import jmri.Turnout;
014
015/**
016 * A collection of tools to find various objects within the layout model(s)
017 *
018 * (temporary) Consider renaming to LayoutModelFindItems, or even merge to LayoutModels
019 *
020 * @author Dave Duchamp Copyright (c) 2004-2007
021 * @author George Warner Copyright (c) 2017-2018
022 * @author Bob Jacobsen Copyright (c) 2019-2020
023 */
024final public class LayoutEditorFindItems {
025
026    private final LayoutModels layoutModels;
027
028    public LayoutEditorFindItems(LayoutModels models) {
029        layoutModels = models;
030    }
031
032    public TrackSegment findTrackSegmentByName(String name) {
033        if (!name.isEmpty()) {
034            for (TrackSegment t : layoutModels.getTrackSegments()) {
035                if (t.getId().equals(name)) {
036                    return t;
037                }
038            }
039        }
040        return null;
041    }
042
043    public PositionablePoint findPositionablePointByName(String name) {
044        if (!name.isEmpty()) {
045            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
046                if (p.getId().equals(name)) {
047                    return p;
048                }
049            }
050        }
051        return null;
052    }
053
054    public PositionablePoint findPositionablePointAtTrackSegments(TrackSegment tr1, TrackSegment tr2) {
055        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
056            if (((p.getConnect1() == tr1) && (p.getConnect2() == tr2))
057                    || ((p.getConnect1() == tr2) && (p.getConnect2() == tr1))) {
058                return p;
059            }
060        }
061        return null;
062    }
063
064    public PositionablePoint findPositionableLinkPoint(LayoutBlock blk1) {
065        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
066            if (p.getType() == PositionablePoint.PointType.EDGE_CONNECTOR) {
067                if ((p.getConnect1() != null && p.getConnect1().getLayoutBlock() == blk1)
068                        || (p.getConnect2() != null && p.getConnect2().getLayoutBlock() == blk1)) {
069                    return p;
070                }
071            }
072        }
073        return null;
074    }
075
076    /**
077     * Returns an array list of track segments matching the block name.
078     * @param name block name.
079     * @return array of segments, may be null.
080     */
081    public ArrayList<TrackSegment> findTrackSegmentByBlock(String name) {
082        if (name.isEmpty()) {
083            return null;
084        }
085        ArrayList<TrackSegment> ts = new ArrayList<>();
086        for (TrackSegment t : layoutModels.getTrackSegments()) {
087            if (t.getBlockName().equals(name)) {
088                ts.add(t);
089            }
090        }
091        return ts;
092    }
093
094    public PositionablePoint findPositionablePointByEastBoundSignal(String signalName) {
095        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
096            if (p.getEastBoundSignal().equals(signalName)) {
097                return p;
098            }
099        }
100        return null;
101    }
102
103    public PositionablePoint findPositionablePointByWestBoundSignal(String signalName) {
104        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
105            if (p.getWestBoundSignal().equals(signalName)) {
106                return p;
107            }
108        }
109        return null;
110    }
111
112    public PositionablePoint findPositionablePointByWestBoundBean(NamedBean bean) {
113        if (bean instanceof SignalMast) {
114            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
115                if (p.getWestBoundSignalMast() == bean) {
116                    return p;
117                }
118            }
119        } else if (bean instanceof Sensor) {
120            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
121                if (p.getWestBoundSensor() == bean) {
122                    return p;
123                }
124            }
125        } else if (bean instanceof SignalHead) {
126            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
127                if (p.getWestBoundSignal().equals(bean.getSystemName())) {
128                    return p;
129                }
130            }
131        }
132        return null;
133    }
134
135    public PositionablePoint findPositionablePointByEastBoundBean(NamedBean bean) {
136        if (bean instanceof SignalMast) {
137            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
138                if (p.getEastBoundSignalMast() == bean) {
139                    return p;
140                }
141            }
142        } else if (bean instanceof Sensor) {
143            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
144                if (p.getEastBoundSensor() == bean) {
145                    return p;
146                }
147            }
148        } else if (bean instanceof SignalHead) {
149            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
150                if (p.getEastBoundSignal().equals(bean.getSystemName())) {
151                    return p;
152                }
153            }
154        }
155        return null;
156    }
157
158    public PositionablePoint findPositionablePointByWestBoundSignalMast(String signalMastName) {
159        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
160            if (p.getWestBoundSignalMastName().equals(signalMastName)) {
161                return p;
162            }
163        }
164        return null;
165    }
166
167    public PositionablePoint findPositionablePointByBean(NamedBean bean) {
168        if (bean instanceof SignalMast) {
169            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
170                if (p.getWestBoundSignalMast() == bean
171                        || p.getEastBoundSignalMast() == bean) {
172                    return p;
173                }
174            }
175        } else if (bean instanceof Sensor) {
176            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
177                if (p.getWestBoundSensor() == bean
178                        || p.getEastBoundSensor() == bean) {
179                    return p;
180                }
181            }
182        } else if (bean instanceof SignalHead) {
183            for (PositionablePoint p : layoutModels.getPositionablePoints()) {
184                if (p.getEastBoundSignal().equals(bean.getSystemName())
185                        || p.getWestBoundSignal().equals(bean.getSystemName())) {
186                    return p;
187                }
188                if (bean.getUserName() != null && (p.getEastBoundSignal().equals(bean.getSystemName())
189                        || p.getWestBoundSignal().equals(bean.getSystemName()))) {
190                    return p;
191                }
192            }
193        }
194        return null;
195
196    }
197
198    @CheckReturnValue
199    public LayoutTurnout findLayoutTurnoutBySignalMast(String signalMastName) throws IllegalArgumentException {
200        return findLayoutTurnoutByBean(jmri.InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMastName));
201    }
202
203    @CheckReturnValue
204    public LayoutTurnout findLayoutTurnoutByBean(@CheckForNull NamedBean bean) {
205        List<LayoutTurnout> layoutTurnouts = layoutModels.getLayoutTurnouts();
206        if (bean instanceof SignalMast) {
207            for (LayoutTurnout t : layoutTurnouts) {
208                if (t.getSignalAMast() == bean
209                        || t.getSignalBMast() == bean
210                        || t.getSignalCMast() == bean
211                        || t.getSignalDMast() == bean) {
212                    return t;
213                }
214            }
215        } else if (bean instanceof Sensor) {
216            for (LayoutTurnout t : layoutTurnouts) {
217                if (t.getSensorA() == bean
218                        || t.getSensorB() == bean
219                        || t.getSensorC() == bean
220                        || t.getSensorD() == bean) {
221                    return t;
222                }
223            }
224        } else if (bean instanceof SignalHead) {
225            for (LayoutTurnout t : layoutTurnouts) {
226                if (t.getSignalA1Name().equals(bean.getSystemName())
227                        || t.getSignalA2Name().equals(bean.getSystemName())
228                        || t.getSignalA3Name().equals(bean.getSystemName())) {
229                    return t;
230                }
231
232                if (t.getSignalB1Name().equals(bean.getSystemName())
233                        || t.getSignalB2Name().equals(bean.getSystemName())) {
234                    return t;
235                }
236                if (t.getSignalC1Name().equals(bean.getSystemName())
237                        || t.getSignalC2Name().equals(bean.getSystemName())) {
238                    return t;
239                }
240                if (t.getSignalD1Name().equals(bean.getSystemName())
241                        || t.getSignalD2Name().equals(bean.getSystemName())) {
242                    return t;
243                }
244                if (bean.getUserName() != null) {
245                    if (t.getSignalA1Name().equals(bean.getUserName())
246                            || t.getSignalA2Name().equals(bean.getUserName())
247                            || t.getSignalA3Name().equals(bean.getUserName())) {
248                        return t;
249                    }
250
251                    if (t.getSignalB1Name().equals(bean.getUserName())
252                            || t.getSignalB2Name().equals(bean.getUserName())) {
253                        return t;
254                    }
255                    if (t.getSignalC1Name().equals(bean.getUserName())
256                            || t.getSignalC2Name().equals(bean.getUserName())) {
257                        return t;
258                    }
259                    if (t.getSignalD1Name().equals(bean.getUserName())
260                            || t.getSignalD2Name().equals(bean.getUserName())) {
261                        return t;
262                    }
263                }
264            }
265        } else if (bean instanceof Turnout) {
266            for (LayoutTurnout t : layoutTurnouts) {
267                if (bean.equals(t.getTurnout())) {
268                    return t;
269                }
270            }
271        }
272        return null;
273    }   // findLayoutTurnoutByBean
274
275    @CheckReturnValue
276    public LayoutTurnout findLayoutTurnoutBySensor(String sensorName) throws IllegalArgumentException {
277        return findLayoutTurnoutByBean(jmri.InstanceManager.sensorManagerInstance().provideSensor(sensorName));
278    }
279
280    public LevelXing findLevelXingBySignalMast(String signalMastName) throws IllegalArgumentException {
281        return findLevelXingByBean(jmri.InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMastName));
282    }
283
284    public LevelXing findLevelXingBySensor(String sensorName) throws IllegalArgumentException {
285        return findLevelXingByBean(jmri.InstanceManager.sensorManagerInstance().provideSensor(sensorName));
286    }
287
288    public LevelXing findLevelXingByBean(NamedBean bean) {
289        List<LevelXing> levelXings = layoutModels.getLevelXings();
290        if (bean instanceof SignalMast) {
291            for (LevelXing l : levelXings) {
292                if (l.getSignalAMast() == bean
293                        || l.getSignalBMast() == bean
294                        || l.getSignalCMast() == bean
295                        || l.getSignalDMast() == bean) {
296                    return l;
297                }
298            }
299        } else if (bean instanceof Sensor) {
300            for (LevelXing l : levelXings) {
301                if (l.getSensorA() == bean
302                        || l.getSensorB() == bean
303                        || l.getSensorC() == bean
304                        || l.getSensorD() == bean) {
305                    return l;
306                }
307            }
308
309        } else if (bean instanceof SignalHead) {
310            for (LevelXing l : levelXings) {
311                if (l.getSignalAName().equals(bean.getSystemName())
312                        || l.getSignalBName().equals(bean.getSystemName())
313                        || l.getSignalCName().equals(bean.getSystemName())
314                        || l.getSignalDName().equals(bean.getSystemName())) {
315                    return l;
316                }
317                if (bean.getUserName() != null && (l.getSignalAName().equals(bean.getUserName())
318                        || l.getSignalBName().equals(bean.getUserName())
319                        || l.getSignalCName().equals(bean.getUserName())
320                        || l.getSignalDName().equals(bean.getUserName()))) {
321                    return l;
322                }
323            }
324        }
325        return null;
326    }
327
328    public LayoutSlip findLayoutSlipByBean(NamedBean bean) {
329        List<LayoutSlip> layoutSlips = layoutModels.getLayoutSlips();
330        if (bean instanceof SignalMast) {
331            for (LayoutSlip l : layoutSlips) {
332                if (l.getSignalAMast() == bean
333                        || l.getSignalBMast() == bean
334                        || l.getSignalCMast() == bean
335                        || l.getSignalDMast() == bean) {
336                    return l;
337                }
338            }
339        } else if (bean instanceof Sensor) {
340            for (LayoutSlip l : layoutSlips) {
341                if (l.getSensorA() == bean
342                        || l.getSensorB() == bean
343                        || l.getSensorC() == bean
344                        || l.getSensorD() == bean) {
345                    return l;
346                }
347            }
348        } else if (bean instanceof SignalHead) {
349            for (LayoutSlip l : layoutSlips) {
350                if (l.getSignalA1Name().equals(bean.getSystemName())
351                        || l.getSignalA2Name().equals(bean.getSystemName())
352                        || l.getSignalA3Name().equals(bean.getSystemName())) {
353                    return l;
354                }
355
356                if (l.getSignalB1Name().equals(bean.getSystemName())
357                        || l.getSignalB2Name().equals(bean.getSystemName())) {
358                    return l;
359                }
360                if (l.getSignalC1Name().equals(bean.getSystemName())
361                        || l.getSignalC2Name().equals(bean.getSystemName())) {
362                    return l;
363                }
364                if (l.getSignalD1Name().equals(bean.getSystemName())
365                        || l.getSignalD2Name().equals(bean.getSystemName())) {
366                    return l;
367                }
368                if (l.getSignalA1Name().equals(bean.getUserName())
369                        || l.getSignalA2Name().equals(bean.getUserName())
370                        || l.getSignalA3Name().equals(bean.getUserName())) {
371                    return l;
372                }
373                if (bean.getUserName() != null) {
374                    if (l.getSignalB1Name().equals(bean.getUserName())
375                            || l.getSignalB2Name().equals(bean.getUserName())) {
376                        return l;
377                    }
378                    if (l.getSignalC1Name().equals(bean.getUserName())
379                            || l.getSignalC2Name().equals(bean.getUserName())) {
380                        return l;
381                    }
382                    if (l.getSignalD1Name().equals(bean.getUserName())
383                            || l.getSignalD2Name().equals(bean.getUserName())) {
384                        return l;
385                    }
386                }
387            }
388        } else if (bean instanceof Turnout) {
389            for (LayoutSlip l : layoutSlips) {
390                if (bean.equals(l.getTurnout())) {
391                    return l;
392                }
393                if (bean.equals(l.getTurnoutB())) {
394                    return l;
395                }
396            }
397        }
398        return null;
399    }
400
401    public LayoutSlip findLayoutSlipBySignalMast(String signalMastName) throws IllegalArgumentException {
402        return findLayoutSlipByBean(jmri.InstanceManager.getDefault(jmri.SignalMastManager.class).provideSignalMast(signalMastName));
403    }
404
405    public LayoutSlip findLayoutSlipBySensor(String sensorName) throws IllegalArgumentException {
406        return findLayoutSlipByBean(jmri.InstanceManager.sensorManagerInstance().provideSensor(sensorName));
407    }
408
409    public PositionablePoint findPositionablePointByEastBoundSensor(String sensorName) {
410        PositionablePoint result = null;
411        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
412            if (p.getEastBoundSensorName().equals(sensorName)) {
413                result = p;
414                break;
415            }
416        }
417        return result;
418    }
419
420    public PositionablePoint findPositionablePointByWestBoundSensor(String sensorName) {
421        PositionablePoint result = null;
422        for (PositionablePoint p : layoutModels.getPositionablePoints()) {
423            if (p.getWestBoundSensorName().equals(sensorName)) {
424                result = p;
425                break;
426            }
427        }
428        return result;
429    }
430
431    @CheckReturnValue
432    public LayoutTurnout findLayoutTurnoutByName(String name) {
433        LayoutTurnout result = null;
434        if ((name != null) && !name.isEmpty()) {
435            for (LayoutTurnout t : layoutModels.getLayoutTurnouts()) {
436                if (t.getName().equals(name)) {
437                    result = t;
438                    break;
439                }
440            }
441        }
442        return result;
443    }
444
445    @CheckReturnValue
446    public LayoutTurnout findLayoutTurnoutByTurnoutName(String turnoutName) {
447        LayoutTurnout result = null;
448        if ((turnoutName != null) && !turnoutName.isEmpty()) {
449            for (LayoutTurnout t : layoutModels.getLayoutTurnouts()) {
450                if (t.getTurnoutName().equals(turnoutName)) {
451                    result = t;
452                }
453            }
454        }
455        return result;
456    }
457
458    public LevelXing findLevelXingByName(String name) {
459        LevelXing result = null;
460        if ((name != null) && !name.isEmpty()) {
461            for (LevelXing x : layoutModels.getLevelXings()) {
462                if (x.getId().equals(name)) {
463                    result = x;
464                    break;
465                }
466            }
467        }
468        return result;
469    }
470
471    public LayoutSlip findLayoutSlipByName(String name) {
472        LayoutSlip result = null;
473        if ((name != null) && !name.isEmpty()) {
474            for (LayoutSlip x : layoutModels.getLayoutSlips()) {
475                if (x.getName().equals(name)) {
476                    result = x;
477                    break;
478                }
479            }
480        }
481        return result;
482    }
483
484    public LayoutTurntable findLayoutTurntableByName(String name) {
485        LayoutTurntable result = null;
486        if ((name != null) && !name.isEmpty()) {
487            for (LayoutTurntable x : layoutModels.getLayoutTurntables()) {
488                if (x.getId().equals(name)) {
489                    result = x;
490                    break;
491                }
492            }
493        }
494        return result;
495    }
496
497    public LayoutShape findLayoutShapeByName(String name) {
498        LayoutShape result = null;
499        if ((name != null) && !name.isEmpty()) {
500            for (LayoutShape x : layoutModels.getLayoutShapes()) {
501                if (x.getName().equals(name)) {
502                    result = x;
503                    break;
504                }
505            }
506        }
507        return result;
508    }
509
510    // data encapsulation means that no one external to an object should
511    // care about its type... we treat all objects as equal and it's up
512    // to each object to implement methods specific to that type.
513    //
514    // JMRI is full of pages of "if (type == XXX) {...} else if (type == XXX)", etc.
515    // all that should be refactored to "object.doVerbWith(params);"...
516    // and again how each object (class) implements "doVerbWith" is up
517    // that class.
518    //
519    // This would get rid of all the object specific code that's not
520    // implemented in those specific classes and vastly simplify
521    // the rest of JMRI.
522    // [/rant] (brought to you by geowar)
523    //
524    // Long story short (too late); we can start this transition to
525    // a "type-less" system by replacing this routine with a type-less one:
526    // (BTW: AFAICT this routine is only called by the setObjects routine in TrackSegment.java)
527    //
528    /*
529    * @deprecated since 4.7.1 use @link{findObjectByName()} instead.
530     */
531    @Deprecated
532    public LayoutTrack findObjectByTypeAndName(HitPointType type, String name) {
533        if (name.isEmpty()) {
534            return null;
535        }
536        switch (type) {
537            case NONE:
538                return null;
539            case POS_POINT:
540                return findPositionablePointByName(name);
541            case TURNOUT_A:
542            case TURNOUT_B:
543            case TURNOUT_C:
544            case TURNOUT_D:
545                return findLayoutTurnoutByName(name);
546            case LEVEL_XING_A:
547            case LEVEL_XING_B:
548            case LEVEL_XING_C:
549            case LEVEL_XING_D:
550                return findLevelXingByName(name);
551            case SLIP_A:
552            case SLIP_B:
553            case SLIP_C:
554            case SLIP_D:
555                return findLayoutSlipByName(name);
556            case TRACK:
557                return findTrackSegmentByName(name);
558            default:
559                if (HitPointType.isTurntableRayHitType(type)) {
560                    return findLayoutTurntableByName(name);
561                }
562        }
563        log.error("did not find Object '{}' of type {}", name, type);
564        return null;
565    }
566
567    /**
568     * find object by name
569     *
570     * @param name the name of the object that you are looking for
571     * @return object the named object
572     */
573    // NOTE: This replacement routine for findObjectByTypeAndName (above)
574    // uses the unique name prefixes to determine what type of item to find.
575    // Currently this routine (like the one above that it replaces) is only
576    // called by the setObjects routine in TrackSegment.java however in the
577    // move toward encapsulation this routine should see a lot more usage;
578    // specifically, instead of a TON of "if (type == XXX) { findXXXByName(...)...}"
579    // code you would just call this method instead.
580    public LayoutTrack findObjectByName(String name) {
581        LayoutTrack result = null;   // assume failure (pessimist!)
582        if ((name != null) && !name.isEmpty()) {
583            if (name.startsWith("TO")) {
584                result = findLayoutTurnoutByName(name);
585            } else if (name.startsWith("A") || name.startsWith("EB") || name.startsWith("EC") || name.matches("F\\d+-A-\\d+")) {
586                result = findPositionablePointByName(name);
587            } else if (name.startsWith("X")) {
588                result = findLevelXingByName(name);
589            } else if (name.startsWith("SL")) {
590                result = findLayoutSlipByName(name);
591            } else if (name.startsWith("TUR")) {
592                result = findLayoutTurntableByName(name);
593            } else if (name.startsWith("T") || name.matches("F\\d+-S-\\d+")) {  // (this prefix has to go after "TO" & "TUR" prefixes above)
594                result = findTrackSegmentByName(name);
595            } else if (name.endsWith("-EB")) {  //BUGFIX: a 3rd party JMRI exporter gets this one wrong.
596                result = findPositionablePointByName(name);
597            } else {
598                log.warn("findObjectByName({}): unknown type name prefix", name);
599            }
600            if (result == null) {
601                log.debug("findObjectByName({}) returned null", name);
602            }
603        }
604        return result;
605    }
606
607    /**
608     * Determine the first unused object name...
609     *
610     * @param inPrefix     ...with this prefix...
611     * @param inStartIndex ...and this starting index...
612     * @return the first unused object name
613     */
614    public String uniqueName(String inPrefix, int inStartIndex) {
615        String result;
616        for (int idx = inStartIndex; true; idx++) {
617            result = String.format("%s%d", inPrefix, idx);
618            if ((inPrefix.equals("S") // LayoutShape?
619                    && (findLayoutShapeByName(result) == null))
620                    || (findObjectByName(result) == null)) {
621                break;
622            }
623        }
624        return result;
625    }
626
627    /**
628     * Determine the first unused object name...
629     *
630     * @param inPrefix ...with this prefix...
631     * @return the first unused object name
632     */
633    public String uniqueName(String inPrefix) {
634        return uniqueName(inPrefix, 1);
635    }
636
637    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditorFindItems.class);
638}