/
Manager.java
1027 lines (956 loc) · 39.4 KB
/
Manager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package jmri;
import edu.umd.cs.findbugs.annotations.OverrideMustInvoke;
import java.beans.PropertyChangeListener;
import java.beans.VetoableChangeListener;
import java.util.*;
import javax.annotation.CheckForNull;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import jmri.NamedBean.BadSystemNameException;
import jmri.NamedBean.DuplicateSystemNameException;
import jmri.beans.PropertyChangeProvider;
import jmri.beans.VetoableChangeProvider;
import jmri.jmrix.SystemConnectionMemo;
/**
* Basic interface for access to named, managed objects.
* <p>
* {@link NamedBean} objects represent various real elements, and have a "system
* name" and perhaps "user name". A specific Manager object provides access to
* them by name, and serves as a factory for new objects.
* <p>
* Right now, this interface just contains the members needed by
* {@link InstanceManager} to handle managers for more than one system.
* <p>
* Although they are not defined here because their return type differs, any
* specific Manager subclass provides "get" methods to locate specific objects,
* and a "new" method to create a new one via the Factory pattern. The "get"
* methods will return an existing object or null, and will never create a new
* object. The "new" method will log a warning if an object already exists with
* that system name.
* <p>
* add/remove PropertyChangeListener methods are provided. At a minimum,
* subclasses must notify of changes to the list of available NamedBeans; they
* may have other properties that will also notify.
* <p>
* Probably should have been called NamedBeanManager
* <hr>
* This file is part of JMRI.
* <p>
* JMRI is free software; you can redistribute it and/or modify it under the
* terms of version 2 of the GNU General Public License as published by the Free
* Software Foundation. See the "COPYING" file for a copy of this license.
* <p>
* JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* @param <E> the type of NamedBean supported by this manager
* @author Bob Jacobsen Copyright (C) 2003
*/
public interface Manager<E extends NamedBean> extends PropertyChangeProvider, VetoableChangeProvider {
/**
* Get the system connection for this manager.
*
* @return the system connection for this manager
*/
@CheckReturnValue
@Nonnull
public SystemConnectionMemo getMemo();
/**
* Provides access to the system prefix string. This was previously called
* the "System letter"
*
* @return the system prefix
*/
@CheckReturnValue
@Nonnull
public String getSystemPrefix();
/**
* @return The type letter for a specific implementation
*/
@CheckReturnValue
public char typeLetter();
/**
* Get the prefix and type for the system name of the NamedBeans handled by
* this manager.
*
* @return the prefix generated by concatenating the result of
* {@link #getSystemPrefix() } and {@link #typeLetter() }
*/
public default String getSystemNamePrefix() {
return getSystemPrefix() + typeLetter();
}
/**
* Create a SystemName by prepending the system name prefix to the name if
* not already present.
* <p>
* <strong>Note:</strong> implementations <em>must</em> call
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to
* ensure the returned name is valid.
*
* @param name the item to make the system name for
* @return A system name from a user input, typically a number.
* @throws BadSystemNameException if a valid name can't be created
*/
@Nonnull
public default String makeSystemName(@Nonnull String name) {
return makeSystemName(name, true);
}
/**
* Create a SystemName by prepending the system name prefix to the name if
* not already present.
* <p>
* The {@code logErrors} parameter is present to allow user interface input
* validation to use this method without logging system name validation
* errors as the user types.
* <p>
* <strong>Note:</strong> implementations <em>must</em> call
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to ensure
* the returned name is valid.
*
* @param name the item to make the system name for
* @param logErrors true to log errors; false to not log errors
* @return a valid system name
* @throws BadSystemNameException if a valid name can't be created
*/
@Nonnull
public default String makeSystemName(@Nonnull String name, boolean logErrors) {
return makeSystemName(name, logErrors, Locale.getDefault());
}
/**
* Create a SystemName by prepending the system name prefix to the name if
* not already present.
* <p>
* The {@code logErrors} parameter is present to allow user interface input
* validation to use this method without logging system name validation
* errors as the user types.
* <p>
* <strong>Note:</strong> implementations <em>must</em> call
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} to ensure
* the returned name is valid.
*
* @param name the item to make the system name for
* @param logErrors true to log errors; false to not log errors
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return a valid system name
* @throws BadSystemNameException if a valid name can't be created
*/
@Nonnull
public default String makeSystemName(@Nonnull String name, boolean logErrors, Locale locale) {
String prefix = getSystemNamePrefix();
// the one special case that is not caught by validation here
if (name.trim().isEmpty()) { // In Java 9+ use name.isBlank() instead
throw new NamedBean.BadSystemNameException(Locale.getDefault(), "InvalidSystemNameInvalidPrefix", prefix);
}
return validateSystemNameFormat(name.startsWith(prefix) ? name : prefix + name, locale);
}
/**
* Validate the format of a system name, returning it unchanged if valid.
* <p>
* Although further restrictions may be added by system-specific
* implementations, at a minimum, the implementation must consider a name
* that does not start with the System Name prefix for this manager to be
* invalid, and must consider a name that is the same as the System Name
* prefix to be invalid.
* <p>
* This method should not be overridden;
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}
* should be overridden instead.
*
* @param name the system name to validate
* @return the system name unchanged from its input so that this method can
* be chained or used as an parameter to another method
* @throws BadSystemNameException if the name is not valid with error
* messages in the default locale
*/
@Nonnull
public default String validateSystemNameFormat(@Nonnull String name) throws BadSystemNameException {
return Manager.this.validateSystemNameFormat(name, Locale.getDefault());
}
/**
* Validate the format of name, returning it unchanged if valid.
* <p>
* Although further restrictions may be added by system-specific
* implementations, at a minimum, the implementation must consider a name
* that does not start with the System Name prefix for this manager to be
* invalid, and must consider a name that is the same as the System Name
* prefix to be invalid.
* <p>
* Overriding implementations may rely on
* {@link #validSystemNameFormat(java.lang.String)}, however they must
* provide an actionable message in the thrown exception if that method does
* not return {@link NameValidity#VALID}. Implementations of
* this method <em>must not</em> throw an exception, log an error, or
* otherwise disrupt the user.
*
* @param name the system name to validate
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException {
return validateSystemNamePrefix(name, locale);
}
/**
* Basic validation that the system name prefix is correct. Used within the
* default implementation of
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)} and
* abstracted out of that method so this can be used by validation
* implementations in {@link jmri.jmrix.SystemConnectionMemo}s to avoid
* duplicating code in all managers relying on a single subclass of
* SystemConnectionMemo.
*
* @param name the system name to validate
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateSystemNamePrefix(@Nonnull String name, @Nonnull Locale locale) throws BadSystemNameException {
String prefix = getSystemNamePrefix();
if (name.equals(prefix)) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameMatchesPrefix", name);
}
if (!name.startsWith(prefix)) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameInvalidPrefix", prefix);
}
return name;
}
/**
* Convenience implementation of
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}
* that verifies name has no trailing white space and no white space between
* the prefix and suffix.
* <p>
* <strong>Note</strong> this <em>must</em> only be used if the connection
* type is externally documented to require these restrictions.
*
* @param name the system name to validate
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateTrimmedSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
name = validateSystemNamePrefix(name, locale);
String prefix = getSystemNamePrefix();
String suffix = name.substring(prefix.length());
if (!suffix.equals(suffix.trim())) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameTrailingWhitespace", name, prefix);
}
return name;
}
/**
* Convenience implementation of
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}
* that verifies name is upper case and has no trailing white space and not
* white space between the prefix and suffix.
* <p>
* <strong>Note</strong> this <em>must</em> only be used if the connection
* type is externally documented to require these restrictions.
*
* @param name the system name to validate
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateUppercaseTrimmedSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
name = validateTrimmedSystemNameFormat(name, locale);
String prefix = getSystemNamePrefix();
String suffix = name.substring(prefix.length());
if (!suffix.equals(suffix.toUpperCase())) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameNotUpperCase", name, prefix);
}
return name;
}
/**
* Convenience implementation of
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}
* that verifies name is an integer after the prefix.
* <p>
* <strong>Note</strong> this <em>must</em> only be used if the connection
* type is externally documented to require these restrictions.
*
* @param name the system name to validate
* @param min the minimum valid integer value
* @param max the maximum valid integer value
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateIntegerSystemNameFormat(@Nonnull String name, int min, int max, @Nonnull Locale locale) {
name = validateTrimmedSystemNameFormat(name, locale);
String prefix = getSystemNamePrefix();
String suffix = name.substring(prefix.length());
try {
int number = Integer.parseInt(suffix);
if (number < min) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameIntegerLessThan", name, min);
} else if (number > max) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameIntegerGreaterThan", name, max);
}
} catch (NumberFormatException ex) {
throw new NamedBean.BadSystemNameException(locale, "InvalidSystemNameNotInteger", name, prefix);
}
return name;
}
/**
* Convenience implementation of
* {@link #validateSystemNameFormat(java.lang.String, java.util.Locale)}
* that verifies name is a valid NMRA Accessory address after the prefix. A
* name is considered a valid NMRA accessory address if it is an integer
* between {@value NmraPacket#accIdLowLimit} and
* {@value NmraPacket#accIdHighLimit}, inclusive.
* <p>
* <strong>Note</strong> this <em>must</em> only be used if the connection
* type is externally documented to require these restrictions.
*
* @param name the system name to validate
* @param locale the locale for a localized exception; this is needed for
* the JMRI web server, which supports multiple locales
* @return the unchanged value of the name parameter
* @throws BadSystemNameException if provided name is an invalid format
*/
@Nonnull
public default String validateNmraAccessorySystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
return this.validateIntegerSystemNameFormat(name, NmraPacket.accIdLowLimit, NmraPacket.accIdHighLimit, locale);
}
/**
* Code the validity (including just as a prefix) of a proposed name string.
*
* @since 4.9.5
*/
enum NameValidity {
/**
* Indicates the name is valid as is, and can also be a valid prefix for
* longer names
*/
VALID,
/**
* Indicates name is not valid as-is, nor can it be made valid by adding
* more characters; just a bad name.
*/
INVALID,
/**
* Indicates that adding additional characters might (or might not) turn
* this into a valid name; it is not a valid name now.
*/
VALID_AS_PREFIX_ONLY
}
/**
* Test if parameter is a properly formatted system name. Implementations of
* this method <em>must not</em> throw an exception, log an error, or
* otherwise disrupt the user.
*
* @since 4.9.5, although similar methods existed previously in lower-level
* classes
* @param systemName the system name
* @return enum indicating current validity, which might be just as a prefix
*/
@CheckReturnValue
@OverrideMustInvoke
public default NameValidity validSystemNameFormat(@Nonnull String systemName) {
String prefix = getSystemNamePrefix();
if (prefix.equals(systemName)) {
return NameValidity.VALID_AS_PREFIX_ONLY;
}
return systemName.startsWith(prefix)
? NameValidity.VALID
: NameValidity.INVALID;
}
/**
* Test if a given name is in a valid format for this Manager.
*
* @param systemName the name to check
* @return {@code true} if {@link #validSystemNameFormat(java.lang.String)}
* equals {@link NameValidity#VALID}; {@code false} otherwise
*/
public default boolean isValidSystemNameFormat(@Nonnull String systemName) {
return validSystemNameFormat(systemName) == NameValidity.VALID;
}
/**
* Free resources when no longer used. Specifically, remove all references
* to and from this object, so it can be garbage-collected.
*/
public void dispose();
/**
* Get the count of managed objects.
*
* @return the number of managed objects
*/
@CheckReturnValue
public int getObjectCount();
/**
* This provides an array of system names.
* <p>
* Note: this is ordered by the underlying NamedBeans, not on the Strings
* themselves.
* <p>
* Note: this is not a live array; the contents don't stay up to date
*
* @return (slow) copy of system names in array form
* @deprecated 4.11.5 - use direct access via {@link #getNamedBeanSet()}
*/
@Deprecated // 4.11.5
@CheckReturnValue
@Nonnull
public String[] getSystemNameArray();
/**
* This provides an
* {@linkplain java.util.Collections#unmodifiableList unmodifiable} List of
* system names.
* <p>
* Note: this is ordered by the underlying NamedBeans, not on the Strings
* themselves.
* <p>
* Note: Access via {@link #getNamedBeanSet()} is faster.
* <p>
* Note: This is not a live list; the contents don't stay up to date
*
* @return Unmodifiable access to a list of system names
* @deprecated 4.11.5 - use direct access via {@link #getNamedBeanSet()}
*/
@Deprecated // 4.11.5
@CheckReturnValue
@Nonnull
public List<String> getSystemNameList();
/**
* This provides an
* {@linkplain java.util.Collections#unmodifiableList unmodifiable} List of
* NamedBeans in system-name order.
* <p>
* Note: Access via {@link #getNamedBeanSet()} is faster.
* <p>
* Note: This is not a live list; the contents don't stay up to date
*
* @return Unmodifiable access to a List of NamedBeans
* @deprecated 4.11.5 - use direct access via {@link #getNamedBeanSet()}
*/
@Deprecated // 4.11.5
@CheckReturnValue
@Nonnull
public List<E> getNamedBeanList();
/**
* This provides an
* {@linkplain java.util.Collections#unmodifiableSet unmodifiable} SortedSet
* of NamedBeans in system-name order.
* <p>
* Note: This is the fastest of the accessors, and is the only long-term
* form.
* <p>
* Note: This is a live set; the contents are kept up to date
*
* @return Unmodifiable access to a SortedSet of NamedBeans
*/
@CheckReturnValue
@Nonnull
public SortedSet<E> getNamedBeanSet();
/**
* Locate an existing instance based on a system name. Returns null if no
* instance already exists.
*
* @param systemName System Name of the required NamedBean
* @return requested NamedBean object or null if none exists
* @throws IllegalArgumentException if provided name is invalid
*/
@CheckReturnValue
@CheckForNull
public E getBeanBySystemName(@Nonnull String systemName);
/**
* Locate an existing instance based on a user name. Returns null if no
* instance already exists.
*
* @param userName System Name of the required NamedBean
* @return requested NamedBean object or null if none exists
*/
@CheckReturnValue
@CheckForNull
public E getBeanByUserName(@Nonnull String userName);
/**
* Locate an existing instance based on a name. Returns null if no instance
* already exists.
*
* @param name User Name or System Name of the required NamedBean
* @return requested NamedBean object or null if none exists
*/
@CheckReturnValue
@CheckForNull
public E getNamedBean(@Nonnull String name);
/**
* Return the descriptors for the system-specific properties of the
* NamedBeans that are kept in this manager.
*
* @return list of known properties, or empty list if there are none.
*/
@Nonnull
default public List<NamedBeanPropertyDescriptor<?>> getKnownBeanProperties() {
return new LinkedList<>();
}
/**
* At a minimum, subclasses must notify of changes to the list of available
* NamedBeans; they may have other properties that will also notify.
*
* @param l the listener
*/
@Override
public void addPropertyChangeListener(@CheckForNull PropertyChangeListener l);
/**
* At a minimum, subclasses must notify of changes to the list of available
* NamedBeans; they may have other properties that will also notify.
*
* @param l the listener
*/
@Override
public void removePropertyChangeListener(@CheckForNull PropertyChangeListener l);
/**
* Add a VetoableChangeListener to the listener list.
*
* @param l the listener
*/
@Override
public void addVetoableChangeListener(@CheckForNull VetoableChangeListener l);
/**
* Remove a VetoableChangeListener to the listener list.
*
* @param l the listener
*/
@Override
public void removeVetoableChangeListener(@CheckForNull VetoableChangeListener l);
/**
* Method for a UI to delete a bean.
* <p>
* The UI should first request a "CanDelete", this will return a list of
* locations (and descriptions) where the bean is in use via throwing a
* VetoException, then if that comes back clear, or the user agrees with the
* actions, then a "DoDelete" can be called which inform the listeners to
* delete the bean, then it will be deregistered and disposed of.
* <p>
* If a property name of "DoNotDelete" is thrown back in the VetoException
* then the delete process should be aborted.
*
* @param n The NamedBean to be deleted
* @param property The programmatic name of the request. "CanDelete" will
* enquire with all listeners if the item can be deleted.
* "DoDelete" tells the listener to delete the item.
* @throws java.beans.PropertyVetoException - If the recipients wishes the
* delete to be aborted (see
* above).
*/
public void deleteBean(@Nonnull E n, @Nonnull String property) throws java.beans.PropertyVetoException;
/**
* Remember a NamedBean Object created outside the manager.
* <p>
* The non-system-specific SignalHeadManagers use this method extensively.
*
* @param n the bean
* @throws DuplicateSystemNameException if a different bean with the same system
* name is already registered in the
* manager
*/
public void register(@Nonnull E n);
/**
* Forget a NamedBean Object created outside the manager.
* <p>
* The non-system-specific RouteManager uses this method.
*
* @param n the bean
*/
public void deregister(@Nonnull E n);
/**
* The order in which things get saved to the xml file.
*/
public static final int SENSORS = 10;
public static final int TURNOUTS = SENSORS + 10;
public static final int LIGHTS = TURNOUTS + 10;
public static final int REPORTERS = LIGHTS + 10;
public static final int MEMORIES = REPORTERS + 10;
public static final int SENSORGROUPS = MEMORIES + 10;
public static final int SIGNALHEADS = SENSORGROUPS + 10;
public static final int SIGNALMASTS = SIGNALHEADS + 10;
public static final int SIGNALGROUPS = SIGNALMASTS + 10;
public static final int BLOCKS = SIGNALGROUPS + 10;
public static final int OBLOCKS = BLOCKS + 10;
public static final int LAYOUTBLOCKS = OBLOCKS + 10;
public static final int SECTIONS = LAYOUTBLOCKS + 10;
public static final int TRANSITS = SECTIONS + 10;
public static final int BLOCKBOSS = TRANSITS + 10;
public static final int ROUTES = BLOCKBOSS + 10;
public static final int WARRANTS = ROUTES + 10;
public static final int SIGNALMASTLOGICS = WARRANTS + 10;
public static final int IDTAGS = SIGNALMASTLOGICS + 10;
public static final int LOGIXS = IDTAGS + 10;
public static final int CONDITIONALS = LOGIXS + 10;
public static final int AUDIO = LOGIXS + 10;
public static final int TIMEBASE = AUDIO + 10;
public static final int PANELFILES = TIMEBASE + 10;
public static final int ENTRYEXIT = PANELFILES + 10;
/**
* Determine the order that types should be written when storing panel
* files. Uses one of the constants defined in this class.
* <p>
* Yes, that's an overly-centralized methodology, but it works for now.
*
* @return write order for this Manager; larger is later.
*/
@CheckReturnValue
public int getXMLOrder();
/**
* Returns the user-readable name of the type of NamedBean handled by this
* manager.
* <p>
* For instance, in the code where we are dealing with just a bean and a
* message that needs to be passed to the user or in a log.
*
* @return a string of the bean type that the manager handles, eg Turnout,
* Sensor etc
*/
@CheckReturnValue
@Nonnull
public default String getBeanTypeHandled() {
return getBeanTypeHandled(false);
}
/**
* Returns the user-readable name of the type of NamedBean handled by this
* manager.
* <p>
* For instance, in the code where we are dealing with just a bean and a
* message that needs to be passed to the user or in a log.
*
* @param plural true to return plural form of the type; false to return
* singular form
*
* @return a string of the bean type that the manager handles, eg Turnout,
* Sensor etc
*/
@CheckReturnValue
@Nonnull
public String getBeanTypeHandled(boolean plural);
/**
* Provides length of the system prefix of the given system name.
* <p>
* This is a common operation across JMRI, as the system prefix can be
* parsed out without knowledge of the type of NamedBean involved.
*
* @param inputName System Name to provide the prefix
* @throws NamedBean.BadSystemNameException If the inputName can't be
* converted to normalized form
* @return The length of the system-prefix part of the system name in
* standard normalized form
*/
@CheckReturnValue
static public int getSystemPrefixLength(@Nonnull String inputName) throws NamedBean.BadSystemNameException {
if (inputName.isEmpty()) {
throw new NamedBean.BadSystemNameException();
}
if (!Character.isLetter(inputName.charAt(0))) {
throw new NamedBean.BadSystemNameException();
}
// As a very special case, check for legacy prefixes - to be removed
// This is also quite a bit slower than the tuned implementation below
int p = startsWithLegacySystemPrefix(inputName);
if (p > 0) {
if (legacyNameSet.isEmpty()) {
// register our own shutdown
InstanceManager.getDefault(ShutDownManager.class)
.register(legacyReportTask);
}
legacyNameSet.add(inputName);
return p;
}
// implementation for well-formed names
int i;
for (i = 1; i < inputName.length(); i++) {
if (!Character.isDigit(inputName.charAt(i))) {
break;
}
}
return i;
}
@Deprecated // as part of name migration, Issue #4670
static Set<String> legacyNameSet = Collections.synchronizedSet(new HashSet<String>(200)); // want fast search and insert
@Deprecated // as part of name migration, Issue #4670
static ShutDownTask legacyReportTask = new jmri.implementation.AbstractShutDownTask("Legacy Name List") {
@Override
public boolean execute() {
if (legacyNameSet.isEmpty()) {
return true;
}
// as an extremely ugly hack, handle the special case of
// Reporters-with-an-M-system letter, e.g. from MERG
//
// We couldn't do this earlier because the name might be checked
// before the bean is created
ReporterManager rm = InstanceManager.getDefault(ReporterManager.class);
java.util.SortedSet<String> tempSet = new java.util.TreeSet<>();
legacyNameSet.stream()
// The legacy MR name for MRC can't do Reporters
.filter((name) -> !(name.startsWith("MR") && rm.getReporter(name) != null))
.forEachOrdered((name) -> {
tempSet.add(name);
});
if (tempSet.isEmpty()) {
return true;
}
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Manager.class);
log.warn("The following legacy names need to be migrated:");
tempSet.forEach((name) -> {
log.warn(" {}", name);
});
// now create the legacy.csv file
try (java.io.PrintWriter writer = new java.io.PrintWriter(jmri.util.FileUtil.getUserFilesPath() + java.io.File.separator + "legacy_bean_names.csv");) {
tempSet.forEach((name) -> {
writer.println(name);
});
} catch (java.io.IOException e) {
log.error("Failed to write legacy name file", e);
}
// clean up in case invoked twice
legacyNameSet.clear();
return true;
}
};
/**
* Provides the system prefix of the given system name.
* <p>
* This is a common operation across JMRI, as the system prefix can be
* parsed out without knowledge of the type of NamedBean involved.
*
* @param inputName System name to provide the prefix
* @throws NamedBean.BadSystemNameException If the inputName can't be
* converted to normalized form
* @return The system-prefix part of the system name in standard normalized
* form
*/
@CheckReturnValue
static public @Nonnull
String getSystemPrefix(@Nonnull String inputName) throws NamedBean.BadSystemNameException {
return inputName.substring(0, getSystemPrefixLength(inputName));
}
/**
* Indicate whether a system-prefix is one of the legacy non-parsable ones
* that are being removed during the JMRI 4.11 cycle.
*
* @param prefix the system prefix
* @deprecated 4.11.2 to make sure we remember to remove this post-migration
* @since 4.11.2
* @return true if a legacy prefix, hence non-parsable
*/
@Deprecated // 4.11.2 to make sure we remember to remove this post-migration
@CheckReturnValue
public static boolean isLegacySystemPrefix(@Nonnull String prefix) {
return LEGACY_PREFIXES.contains(prefix);
}
@Deprecated // 4.11.2 to make sure we remember to remove this post-migration
static final TreeSet<String> LEGACY_PREFIXES = new TreeSet<>(Arrays.asList(
new String[]{
"DX", "DCCPP", "DP", "MR", "MC", "PI", "TM"
}));
/**
* If the argument starts with one of the legacy prefixes, detect that and
* indicate its length.
* <p>
* This is a slightly-expensive operation, and should be used sparingly
*
* @param prefix the system prefix
* @deprecated // 4.11.2 to make sure we remember to remove this
* post-migration
* @since 4.11.2
* @return length of a legacy prefix, if present, otherwise -1
*/
@Deprecated // 4.11.2 to make sure we remember to remove this post-migration
@CheckReturnValue
public static int startsWithLegacySystemPrefix(@Nonnull String prefix) {
// implementation replies on legacy suffix length properties to gain a bit of speed...
if (prefix.length() < 2) {
return -1;
}
if (LEGACY_PREFIXES.contains(prefix.substring(0, 2))) {
return 2;
} else if (prefix.startsWith("DCCPP")) {
return 5;
} else {
return -1;
}
}
/**
* Get a manager-specific tool tip for adding an entry to the manager.
*
* @return the tool tip or null to disable the tool tip
*/
public default String getEntryToolTip() {
return null;
}
/**
* Register a {@link ManagerDataListener} to hear about adding or removing
* items from the list of NamedBeans.
*
* @param e the data listener to add
*/
public void addDataListener(ManagerDataListener<E> e);
/**
* Unregister a previously-added {@link ManagerDataListener}.
*
* @param e the data listener to remove
*/
public void removeDataListener(ManagerDataListener<E> e);
/**
* Temporarily suppress DataListener notifications.
* <p>
* This avoids O(N^2) behavior when doing bulk updates, i.e. when loading
* lots of Beans. Note that this is (1) optional, in the sense that the
* manager is not required to mute and (2) if present, its' temporary, in
* the sense that the manager must do a cumulative notification when done.
*
* @param muted true if notifications should be suppressed; false otherwise
*/
public default void setDataListenerMute(boolean muted) {
}
/**
* Intended to be equivalent to {@link javax.swing.event.ListDataListener}
* without introducing a Swing dependency into core JMRI.
*
* @param <E> the type to support listening for
* @since JMRI 4.11.4
*/
interface ManagerDataListener<E extends NamedBean> {
/**
* Sent when the contents of the list has changed in a way that's too
* complex to characterize with the previous methods.
*
* @param e encapsulates event information
*/
void contentsChanged(ManagerDataEvent<E> e);
/**
* Sent after the indices in the index0,index1 interval have been
* inserted in the data model.
*
* @param e encapsulates the event information
*/
void intervalAdded(ManagerDataEvent<E> e);
/**
* Sent after the indices in the index0,index1 interval have been
* removed from the data model.
*
* @param e encapsulates the event information
*/
void intervalRemoved(ManagerDataEvent<E> e);
}
/**
* Defines an event that encapsulates changes to a list.
* <p>
* Intended to be equivalent to {@link javax.swing.event.ListDataEvent}
* without introducing a Swing dependency into core JMRI.
*
* @param <E> the type to support in the event
* @since JMRI 4.11.4
*/
@javax.annotation.concurrent.Immutable
public final class ManagerDataEvent<E extends NamedBean> extends java.util.EventObject {
/**
* Equal to {@link javax.swing.event.ListDataEvent#CONTENTS_CHANGED}
*/
final static public int CONTENTS_CHANGED = 0;
/**
* Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_ADDED}
*/
final static public int INTERVAL_ADDED = 1;
/**
* Equal to {@link javax.swing.event.ListDataEvent#INTERVAL_REMOVED}
*/
final static public int INTERVAL_REMOVED = 2;
final private int type;
final private int index0;
final private int index1;
final private E changedBean; // used when just one bean is added or removed as an efficiency measure
final private Manager<E> source;
/**
* Creates a <code>ListDataEvent</code> object.
*
* @param source the source of the event (<code>null</code> not
* permitted).
* @param type the type of the event (should be one of
* {@link #CONTENTS_CHANGED}, {@link #INTERVAL_ADDED}
* or {@link #INTERVAL_REMOVED}, although this is not
* enforced).
* @param index0 the index for one end of the modified range of
* list elements.
* @param index1 the index for the other end of the modified range
* of list elements.
* @param changedBean used when just one bean is added or removed,
* otherwise null
*/
public ManagerDataEvent(@Nonnull Manager<E> source, int type, int index0, int index1, E changedBean) {
super(source);
this.source = source;
this.type = type;
this.index0 = Math.min(index0, index1); // from javax.swing.event.ListDataEvent implementation
this.index1 = Math.max(index0, index1); // from javax.swing.event.ListDataEvent implementation
this.changedBean = changedBean;
}
/**
* Returns the source of the event in a type-safe manner.
*
* @return the event source
*/
@Override
public Manager<E> getSource() {
return source;
}
/**
* Returns the index of the first item in the range of modified list
* items.
*
* @return The index of the first item in the range of modified list
* items.
*/
public int getIndex0() {
return index0;
}
/**
* Returns the index of the last item in the range of modified list
* items.
*
* @return The index of the last item in the range of modified list
* items.
*/
public int getIndex1() {
return index1;
}
/**
* Returns the changed bean or null
*
* @return null if more than one bean was changed
*/