RobotCore 1.0
WPILib port to raspberry Pi
Loading...
Searching...
No Matches
TunableRegistry.java
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5package org.wpilib.tunable;
6
7import java.lang.ref.ReferenceQueue;
8import java.lang.ref.WeakReference;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.IdentityHashMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Objects;
17import java.util.Set;
18import java.util.concurrent.ConcurrentHashMap;
19import java.util.concurrent.ConcurrentMap;
20import java.util.function.Consumer;
21import org.wpilib.tunable.util.PathUtil;
22import org.wpilib.util.collections.PrefixMap;
23import org.wpilib.util.collections.prefixmap.StringPrefixMap;
24
26public final class TunableRegistry {
32 @FunctionalInterface
33 public interface TypeHandler<T> {
41 Tunable<T> createTunable(T initialValue, TunableConfig config);
42 }
43
50 private record TypeHandlerData<T>(Class<T> cls, TypeHandler<T> handler) {}
51
52 private record ComplexMigrationRecorder(
53 Set<String> publishedPaths, ComplexMigrationRecorder previous) {}
54
55 private record RevisionParentLink(Object child, ComplexTunable parent) {}
56
57 private record RevisionParentLinkState(String path, RevisionParentLink link) {}
58
59 private record ComplexPathState(
60 String path,
61 ComplexTunable tunable,
62 boolean addedPath,
63 List<RevisionParentLinkState> descendantLinks) {}
64
65 private record ComplexChildPathState(String path, TunableBase previousTunable) {}
66
67 private static final class WeakIdentityKey extends WeakReference<Object> {
68 private final int m_hash;
69
70 WeakIdentityKey(Object value, ReferenceQueue<Object> queue) {
71 super(Objects.requireNonNull(value), queue);
72 m_hash = System.identityHashCode(value);
73 }
74
75 WeakIdentityKey(Object value) {
76 super(Objects.requireNonNull(value));
77 m_hash = System.identityHashCode(value);
78 }
79
80 @Override
81 public int hashCode() {
82 return m_hash;
83 }
84
85 @Override
86 public boolean equals(Object obj) {
87 return this == obj
88 || obj instanceof WeakIdentityKey other && get() != null && get() == other.get();
89 }
90 }
91
92 private static final class WeakIdentityMap<K, V> {
93 private final ReferenceQueue<Object> m_queue = new ReferenceQueue<>();
94 private final Map<WeakIdentityKey, V> m_values = new HashMap<>();
95
96 private void cleanStaleEntries() {
97 WeakIdentityKey key;
98 while ((key = (WeakIdentityKey) m_queue.poll()) != null) {
99 m_values.remove(key);
100 }
101 }
102
103 V get(K key) {
104 cleanStaleEntries();
105 return m_values.get(new WeakIdentityKey(key));
106 }
107
108 V getOrDefault(K key, V defaultValue) {
109 V value = get(key);
110 return value != null ? value : defaultValue;
111 }
112
113 void put(K key, V value) {
114 cleanStaleEntries();
115 m_values.put(new WeakIdentityKey(key, m_queue), value);
116 }
117
118 void remove(K key) {
119 cleanStaleEntries();
120 m_values.remove(new WeakIdentityKey(key));
121 }
122
123 void clear() {
124 m_values.clear();
125 cleanStaleEntries();
126 }
127
128 boolean isEmpty() {
129 cleanStaleEntries();
130 return m_values.isEmpty();
131 }
132
133 @SuppressWarnings("unchecked")
134 List<K> keySnapshot() {
135 cleanStaleEntries();
136 List<K> keys = new ArrayList<>();
137 var iterator = m_values.keySet().iterator();
138 while (iterator.hasNext()) {
139 Object key = iterator.next().get();
140 if (key == null) {
141 iterator.remove();
142 } else {
143 keys.add((K) key);
144 }
145 }
146 return keys;
147 }
148 }
149
150 private static final List<TypeHandlerData<?>> s_typeHandlers = new ArrayList<>();
151 private static final PrefixMap<TunableBackend> s_backends = new StringPrefixMap<>();
152 private static volatile TunableBackend[] s_backendSnapshot = new TunableBackend[0];
153 private static final ThreadLocal<ComplexMigrationRecorder> s_complexMigrationRecorder =
154 new ThreadLocal<>();
155 private static final IdentityHashMap<TunableBase, Boolean> s_pendingChangedResets =
156 new IdentityHashMap<>();
157 private static final IdentityHashMap<ComplexTunable, Boolean> s_updatedComplexTunables =
158 new IdentityHashMap<>();
159 private static final List<Runnable> s_pendingUpdateCallbacks = new ArrayList<>();
160 private static int s_updateDepth;
161 private static final ConcurrentMap<String, TunableTable> s_tables = new ConcurrentHashMap<>();
162 private static final Object s_complexPathsMutex = new Object();
163 private static final IdentityHashMap<ComplexTunable, List<String>> s_complexPaths =
164 new IdentityHashMap<>();
165 private static final Map<String, ComplexTunable> s_complexByPath = new HashMap<>();
166 private static final Map<String, TunableBase> s_complexChildrenByPath = new HashMap<>();
167 private static final WeakIdentityMap<Object, Long> s_tuneRevisions = new WeakIdentityMap<>();
168 private static final WeakIdentityMap<Object, WeakIdentityMap<ComplexTunable, Integer>>
169 s_revisionParents = new WeakIdentityMap<>();
170 private static final Map<String, RevisionParentLink> s_revisionParentLinksByPath =
171 new HashMap<>();
172 private static final TunableBackend s_missingBackend = new NoopTunableBackend();
173 private static Consumer<String> s_reportWarning = TunableRegistry::defaultReportWarning;
174
175 private static final class NoopTunableBackend implements TunableBackend {
176 @Override
177 public boolean publish(String path, TunableBase tunable) {
178 return false;
179 }
180
181 @Override
182 public boolean publishComplex(String path, ComplexTunable tunable) {
183 return false;
184 }
185
186 @Override
187 public void remove(String path) {}
188
189 @Override
190 public List<PublishedTunable> removePrefix(String prefix) {
191 return List.of();
192 }
193
194 @Override
195 public void update() {}
196
197 @Override
198 public void close() {}
199 }
200
201 private TunableRegistry() {
202 throw new UnsupportedOperationException("This is a utility class!");
203 }
204
205 private static void defaultReportWarning(String msg) {
206 // TODO: do something smarter here
207 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
208 StringBuilder traceString = new StringBuilder();
209 for (int i = 1; i < stackTrace.length; i++) {
210 String loc = stackTrace[i].toString();
211 traceString.append("\tat ").append(loc).append('\n');
212 }
213
214 System.err.println("Tunable warning: " + msg + "\n" + traceString.toString());
215 }
216
224 public static void setReportWarning(Consumer<String> func) {
225 synchronized (TunableRegistry.class) {
226 if (func == null) {
227 s_reportWarning = TunableRegistry::defaultReportWarning;
228 } else {
229 s_reportWarning = func;
230 }
231 }
232 }
233
239 public static Consumer<String> getReportWarning() {
240 synchronized (TunableRegistry.class) {
241 return s_reportWarning;
242 }
243 }
244
250 public static void reportWarning(String msg) {
251 synchronized (TunableRegistry.class) {
252 s_reportWarning.accept(msg);
253 }
254 }
255
264 public static <T> void registerTypeHandler(Class<T> cls, TypeHandler<T> handler) {
265 synchronized (s_typeHandlers) {
266 // we want this ordered such that the more specific types come before the less specific ones
267 // this is O(N^2) but N should be small
268 boolean replace = false;
269 int i = 0;
270 for (var entry : s_typeHandlers) {
271 if (entry.cls.equals(cls)) {
272 // replace existing
273 replace = true;
274 break;
275 }
276 if (entry.cls.isAssignableFrom(cls)) {
277 // superclass; insert before
278 break;
279 }
280 i++;
281 }
282
283 if (replace) {
284 s_typeHandlers.set(i, new TypeHandlerData<>(cls, handler));
285 } else {
286 s_typeHandlers.add(i, new TypeHandlerData<>(cls, handler));
287 }
288 }
289 }
290
298 @SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.AvoidCatchingGenericException"})
299 public static void registerBackend(String prefix, TunableBackend backend) {
300 String normalizedPrefix = PathUtil.normalizeBackendPrefix(prefix);
301 List<TunableBackend> closeBackends = new ArrayList<>();
302 synchronized (s_backends) {
303 final List<TunableBackend> oldBackends = new ArrayList<>(s_backends.values());
304 s_backends.remove(normalizedPrefix);
305 s_backends.put(normalizedPrefix, backend);
306 updateBackendSnapshot();
307
308 List<TunableBackend.PublishedTunable> migrations = new ArrayList<>();
309 IdentityHashMap<TunableBackend, Boolean> displacedBackends = new IdentityHashMap<>();
310 for (TunableBackend oldBackend : oldBackends) {
311 if (oldBackend != backend) {
312 migrations.addAll(oldBackend.removePrefix(normalizedPrefix));
313 displacedBackends.put(oldBackend, Boolean.TRUE);
314 }
315 }
316
317 // Java complex backends publish the parent and then publish their child entries as a side
318 // effect of publishComplex(). Republish complex entries first and record the paths they
319 // actually recreate so only those returned entries are suppressed.
320 Set<String> publishedComplexChildren = new HashSet<>();
321 List<TunableBackend.PublishedTunable> complexMigrations = new ArrayList<>();
322 for (var published : migrations) {
323 if (published.isComplex()) {
324 complexMigrations.add(published);
325 }
326 }
327 complexMigrations.sort((a, b) -> Integer.compare(a.path().length(), b.path().length()));
328
329 for (var published : complexMigrations) {
330 if (publishedComplexChildren.contains(published.path())) {
331 continue;
332 }
333 TunableBackend targetBackend = getBackendForNormalizedPath(published.path());
334 publishMigratedComplex(targetBackend, published, publishedComplexChildren);
335 }
336
337 for (var published : migrations) {
338 if (published.isComplex() || publishedComplexChildren.contains(published.path())) {
339 continue;
340 }
341 TunableBackend targetBackend = getBackendForNormalizedPath(published.path());
342 targetBackend.publish(published.path(), published.tunable());
343 }
344
345 IdentityHashMap<TunableBackend, Boolean> currentBackends = new IdentityHashMap<>();
346 for (TunableBackend currentBackend : s_backendSnapshot) {
347 currentBackends.put(currentBackend, Boolean.TRUE);
348 }
349 for (TunableBackend displacedBackend : displacedBackends.keySet()) {
350 if (!currentBackends.containsKey(displacedBackend)) {
351 closeBackends.add(displacedBackend);
352 }
353 }
354 }
355
356 for (TunableBackend closeBackend : closeBackends) {
357 try {
358 closeBackend.close();
359 } catch (Exception e) {
360 System.out.println("Unexpected exception when closing backend: " + e);
361 }
362 }
363 }
364
365 private static void publishMigratedComplex(
366 TunableBackend targetBackend,
368 Set<String> publishedComplexChildren) {
369 ComplexMigrationRecorder previous = s_complexMigrationRecorder.get();
370 s_complexMigrationRecorder.set(
371 new ComplexMigrationRecorder(publishedComplexChildren, previous));
372 try {
373 targetBackend.publishComplex(published.path(), published.complex());
374 } finally {
375 if (previous == null) {
376 s_complexMigrationRecorder.remove();
377 } else {
378 s_complexMigrationRecorder.set(previous);
379 }
380 }
381 }
382
383 private static void recordComplexMigrationPublish(String path) {
384 ComplexMigrationRecorder recorder = s_complexMigrationRecorder.get();
385 while (recorder != null) {
386 recorder.publishedPaths().add(path);
387 recorder = recorder.previous();
388 }
389 }
390
391 private static void updateBackendSnapshot() {
392 IdentityHashMap<TunableBackend, Boolean> uniqueBackends = new IdentityHashMap<>();
393 List<TunableBackend> snapshot = new ArrayList<>();
394 for (TunableBackend backend : s_backends.values()) {
395 if (uniqueBackends.put(backend, Boolean.TRUE) == null) {
396 snapshot.add(backend);
397 }
398 }
399 s_backendSnapshot = snapshot.toArray(new TunableBackend[0]);
400 }
401
409 public static <T> TypeHandler<T> getTypeHandler(T value) {
410 synchronized (s_typeHandlers) {
411 for (var entry : s_typeHandlers) {
412 if (entry.cls.isInstance(value)) {
413 @SuppressWarnings("unchecked")
414 TypeHandler<T> handler = (TypeHandler<T>) entry.handler;
415 return handler;
416 }
417 }
418 return null;
419 }
420 }
421
429 public static TunableBackend getBackend(String path) {
430 String normalized = PathUtil.normalizeName(path);
431 TunableBackend backend;
432 synchronized (s_backends) {
433 backend = getBackendForNormalizedPath(normalized);
434 }
435 if (isMissingBackend(backend)) {
436 reportWarning("no backend for path '" + normalized + "'");
437 }
438 return backend;
439 }
440
441 private static TunableBackend getBackendForNormalizedPath(String path) {
442 String candidate = path;
443 while (true) {
444 TunableBackend backend = s_backends.get(candidate);
445 if (backend != null) {
446 return backend;
447 }
448
449 int slash = candidate.lastIndexOf('/');
450 if (slash <= 0) {
451 break;
452 }
453 candidate = candidate.substring(0, slash);
454 }
455
456 TunableBackend rootBackend = s_backends.get("/");
457 if (rootBackend != null) {
458 return rootBackend;
459 }
460 TunableBackend defaultBackend = s_backends.get("");
461 if (defaultBackend != null) {
462 return defaultBackend;
463 }
464 return s_missingBackend;
465 }
466
467 @SuppressWarnings("PMD.CompareObjectsWithEquals")
468 private static boolean isMissingBackend(TunableBackend backend) {
469 return backend == s_missingBackend;
470 }
471
472 static void notifyChanged(TunableBase tunable) {
473 for (TunableBackend backend : s_backendSnapshot) {
474 backend.markDirty(tunable);
475 }
476 }
477
494 public static long getTuneRevision(TunableBase tunable) {
495 synchronized (s_complexPathsMutex) {
496 return s_tuneRevisions.getOrDefault(getRevisionOwner(tunable), 0L);
497 }
498 }
499
509 public static long getTuneRevision(ComplexTunable tunable) {
510 synchronized (s_complexPathsMutex) {
511 return s_tuneRevisions.getOrDefault(tunable, 0L);
512 }
513 }
514
525 public static void recordTuneApplied(TunableBase tunable) {
526 synchronized (s_complexPathsMutex) {
527 incrementTuneRevision(getRevisionOwner(tunable), new IdentityHashMap<>());
528 }
529 }
530
539 public static void resetChangedAfterUpdate(TunableBase tunable) {
540 synchronized (s_backends) {
541 if (s_updateDepth > 0) {
542 s_pendingChangedResets.put(tunable, Boolean.TRUE);
543 } else {
544 tunable.resetChanged();
545 }
546 }
547 }
548
557 public static void runAfterUpdate(Runnable callback) {
558 synchronized (s_backends) {
559 if (s_updateDepth > 0) {
560 s_pendingUpdateCallbacks.add(callback);
561 } else {
562 callback.run();
563 }
564 }
565 }
566
575 public static void updateComplexIfNeeded(ComplexTunable tunable) {
576 synchronized (s_backends) {
577 if (s_updateDepth > 0 && s_updatedComplexTunables.put(tunable, Boolean.TRUE) != null) {
578 return;
579 }
580 tunable.updateTunable();
581 }
582 }
583
584 private static void finishUpdate() {
585 s_updateDepth--;
586 if (s_updateDepth != 0) {
587 return;
588 }
589
590 s_updatedComplexTunables.clear();
591
592 if (!s_pendingChangedResets.isEmpty()) {
593 for (TunableBase tunable : s_pendingChangedResets.keySet()) {
594 tunable.resetChanged();
595 }
596 s_pendingChangedResets.clear();
597 }
598
599 if (s_pendingUpdateCallbacks.isEmpty()) {
600 return;
601 }
602
603 List<Runnable> callbacks = new ArrayList<>(s_pendingUpdateCallbacks);
604 s_pendingUpdateCallbacks.clear();
605 for (Runnable callback : callbacks) {
606 callback.run();
607 }
608 }
609
616 public static TunableTable getTable(String path) {
617 return s_tables.computeIfAbsent(PathUtil.normalizeTableName(path), TunableTable::new);
618 }
619
629 public static void publishComplexChildren(String path, ComplexTunable tunable) {
630 tunable.publishTunable(getTable(path));
631 }
632
640 public static boolean publish(String path, TunableBase tunable) {
641 if (tunable instanceof Tunable.CustomTunable t) {
642 return publish(path, t.getInnerTunable());
643 }
644 String normalized = PathUtil.normalizeName(path);
645 boolean missingBackend = false;
646 synchronized (s_backends) {
647 TunableBackend backend = getBackendForNormalizedPath(normalized);
648 if (isMissingBackend(backend)) {
649 missingBackend = true;
650 } else {
651 ComplexChildPathState childPathState = addComplexChildPath(normalized, tunable);
652 if (backend.publish(path, tunable)) {
653 recordComplexMigrationPublish(normalized);
654 return true;
655 }
656 restoreComplexChildPath(childPathState);
657 }
658 }
659 if (missingBackend) {
660 reportWarning("no backend for path '" + normalized + "'");
661 }
662 return false;
663 }
664
672 public static boolean publish(String path, ComplexTunable tunable) {
673 String normalized = PathUtil.normalizeName(path);
674 boolean missingBackend = false;
675 synchronized (s_backends) {
676 TunableBackend backend = getBackendForNormalizedPath(normalized);
677 if (isMissingBackend(backend)) {
678 missingBackend = true;
679 } else {
680 ComplexPathState pathState = addComplexPath(normalized, tunable);
681 if (backend.publishComplex(path, tunable)) {
682 recordComplexMigrationPublish(normalized);
683 return true;
684 } else if (pathState.addedPath()) {
685 restoreComplexPath(pathState);
686 }
687 }
688 }
689 if (missingBackend) {
690 reportWarning("no backend for path '" + normalized + "'");
691 }
692 return false;
693 }
694
702 static void publishChild(ComplexTunable parent, String name, TunableBase tunable) {
703 for (String path : getComplexPaths(parent)) {
704 publish(PathUtil.normalizeName(path + "/" + name), tunable);
705 }
706 }
707
715 static void publishChild(ComplexTunable parent, String name, ComplexTunable tunable) {
716 for (String path : getComplexPaths(parent)) {
717 publish(PathUtil.normalizeName(path + "/" + name), tunable);
718 }
719 }
720
727 static void removeChild(ComplexTunable parent, String name) {
728 for (String path : getComplexPaths(parent)) {
729 remove(PathUtil.normalizeName(path + "/" + name));
730 }
731 }
732
739 static void setChildChanged(ComplexTunable parent, String name) {
740 List<TunableBase> children = new ArrayList<>();
741 synchronized (s_complexPathsMutex) {
742 List<String> paths = s_complexPaths.get(parent);
743 if (paths == null) {
744 return;
745 }
746 for (String path : paths) {
747 TunableBase child = s_complexChildrenByPath.get(PathUtil.normalizeName(path + "/" + name));
748 if (child != null && !children.contains(child)) {
749 children.add(child);
750 }
751 }
752 }
753 for (TunableBase child : children) {
754 child.markChanged();
755 }
756 }
757
763 public static void remove(String path) {
764 // Backends may have changed since publishing, so remove from all backends
765 synchronized (s_backends) {
766 String childPrefix = PathUtil.childTablePath(path);
767 for (var entry : s_backends.values()) {
768 entry.remove(path);
769 entry.removePrefix(childPrefix);
770 }
771 }
772 removeComplexChildPaths(path);
773 removeComplexPaths(path);
774 }
775
781 public static void remove(ComplexTunable tunable) {
782 for (String path : getComplexPaths(tunable)) {
783 remove(path);
784 }
785 }
786
788 public static void update() {
789 synchronized (s_backends) {
790 s_updateDepth++;
791 for (TunableBackend entry : s_backendSnapshot) {
792 entry.update();
793 }
794 finishUpdate();
795 }
796 }
797
810 public static void withUpdateMutex(Runnable func) {
811 synchronized (s_backends) {
812 func.run();
813 }
814 }
815
820 @SuppressWarnings("PMD.AvoidCatchingGenericException")
821 public static void reset() {
822 List<TunableBackend> backends = new ArrayList<>();
823 synchronized (s_typeHandlers) {
824 s_typeHandlers.clear();
825 }
826 synchronized (s_backends) {
827 backends.addAll(Arrays.asList(s_backendSnapshot));
828 s_backends.clear();
829 updateBackendSnapshot();
830 s_pendingChangedResets.clear();
831 s_updatedComplexTunables.clear();
832 s_pendingUpdateCallbacks.clear();
833 s_updateDepth = 0;
834 }
835 for (TunableBackend backend : backends) {
836 backend.removePrefix("");
837 try {
838 backend.close();
839 } catch (Exception e) {
840 System.out.println("Unexpected exception when closing backend: " + e);
841 }
842 }
843 synchronized (s_complexPathsMutex) {
844 s_complexPaths.clear();
845 s_complexByPath.clear();
846 s_complexChildrenByPath.clear();
847 s_tuneRevisions.clear();
848 s_revisionParents.clear();
849 s_revisionParentLinksByPath.clear();
850 }
851 }
852
853 @SuppressWarnings("PMD.CompareObjectsWithEquals")
854 private static TunableBase getRevisionOwner(TunableBase tunable) {
855 if (tunable instanceof Tunable.CustomTunable custom) {
856 TunableBase inner = custom.getInnerTunable();
857 if (inner != null && inner != tunable) {
858 return getRevisionOwner(inner);
859 }
860 }
861 return tunable;
862 }
863
864 private static void incrementTuneRevision(
865 Object tunable, IdentityHashMap<Object, Boolean> visited) {
866 if (visited.put(tunable, Boolean.TRUE) != null) {
867 return;
868 }
869 s_tuneRevisions.put(tunable, s_tuneRevisions.getOrDefault(tunable, 0L) + 1L);
870 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(tunable);
871 if (parents == null) {
872 return;
873 }
874 for (ComplexTunable parent : parents.keySnapshot()) {
875 incrementTuneRevision(parent, visited);
876 }
877 }
878
879 @SuppressWarnings("PMD.CompareObjectsWithEquals")
880 private static ComplexTunable findNearestComplexParent(String path, Object child) {
881 String bestPrefix = null;
882 ComplexTunable bestParent = null;
883 for (var entry : s_complexByPath.entrySet()) {
884 ComplexTunable parent = entry.getValue();
885 if (parent == child) {
886 continue;
887 }
888 String childPrefix = PathUtil.childTablePath(entry.getKey());
889 if (path.startsWith(childPrefix)
890 && (bestPrefix == null || childPrefix.length() > bestPrefix.length())) {
891 bestPrefix = childPrefix;
892 bestParent = parent;
893 }
894 }
895 return bestParent;
896 }
897
898 private static void addRevisionParent(Object child, ComplexTunable parent) {
899 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(child);
900 if (parents == null) {
901 parents = new WeakIdentityMap<>();
902 s_revisionParents.put(child, parents);
903 }
904 Integer count = parents.get(parent);
905 parents.put(parent, count == null ? 1 : count + 1);
906 }
907
908 private static void removeRevisionParent(Object child, ComplexTunable parent) {
909 WeakIdentityMap<ComplexTunable, Integer> parents = s_revisionParents.get(child);
910 if (parents == null) {
911 return;
912 }
913 Integer count = parents.get(parent);
914 if (count == null) {
915 return;
916 } else if (count > 1) {
917 parents.put(parent, count - 1);
918 } else {
919 parents.remove(parent);
920 if (parents.isEmpty()) {
921 s_revisionParents.remove(child);
922 }
923 }
924 }
925
926 private static void addRevisionParentLink(String path, Object child, ComplexTunable parent) {
927 RevisionParentLink oldLink =
928 s_revisionParentLinksByPath.put(path, new RevisionParentLink(child, parent));
929 if (oldLink != null) {
930 removeRevisionParent(oldLink.child(), oldLink.parent());
931 }
932 if (parent == null) {
933 s_revisionParentLinksByPath.remove(path);
934 return;
935 }
936 addRevisionParent(child, parent);
937 }
938
939 private static void linkRevisionParentForPath(String path, Object child) {
940 addRevisionParentLink(path, child, findNearestComplexParent(path, child));
941 }
942
943 private static void removeRevisionParentLink(String path) {
944 RevisionParentLink link = s_revisionParentLinksByPath.remove(path);
945 if (link != null) {
946 removeRevisionParent(link.child(), link.parent());
947 }
948 }
949
950 private static void restoreRevisionParentLinks(List<RevisionParentLinkState> states) {
951 for (int i = states.size() - 1; i >= 0; i--) {
952 RevisionParentLinkState state = states.get(i);
953 removeRevisionParentLink(state.path());
954 if (state.link() != null) {
955 s_revisionParentLinksByPath.put(state.path(), state.link());
956 addRevisionParent(state.link().child(), state.link().parent());
957 }
958 }
959 }
960
961 private static List<RevisionParentLinkState> linkExistingComplexDescendants(String path) {
962 List<RevisionParentLinkState> previousLinks = new ArrayList<>();
963 String childPrefix = PathUtil.childTablePath(path);
964 for (var entry : s_complexChildrenByPath.entrySet()) {
965 if (entry.getKey().startsWith(childPrefix)) {
966 previousLinks.add(
967 new RevisionParentLinkState(
968 entry.getKey(), s_revisionParentLinksByPath.get(entry.getKey())));
969 linkRevisionParentForPath(entry.getKey(), getRevisionOwner(entry.getValue()));
970 }
971 }
972 for (var entry : s_complexByPath.entrySet()) {
973 if (!entry.getKey().equals(path) && entry.getKey().startsWith(childPrefix)) {
974 previousLinks.add(
975 new RevisionParentLinkState(
976 entry.getKey(), s_revisionParentLinksByPath.get(entry.getKey())));
977 linkRevisionParentForPath(entry.getKey(), entry.getValue());
978 }
979 }
980 return previousLinks;
981 }
982
983 private static ComplexChildPathState addComplexChildPath(String path, TunableBase tunable) {
984 synchronized (s_complexPathsMutex) {
985 String normalized = PathUtil.normalizeName(path);
986 TunableBase previousTunable = s_complexChildrenByPath.put(normalized, tunable);
987 if (previousTunable != null) {
988 removeRevisionParentLink(normalized);
989 }
990 linkRevisionParentForPath(normalized, getRevisionOwner(tunable));
991 return new ComplexChildPathState(normalized, previousTunable);
992 }
993 }
994
995 private static void restoreComplexChildPath(ComplexChildPathState state) {
996 synchronized (s_complexPathsMutex) {
997 removeRevisionParentLink(state.path());
998 if (state.previousTunable() != null) {
999 s_complexChildrenByPath.put(state.path(), state.previousTunable());
1000 linkRevisionParentForPath(state.path(), getRevisionOwner(state.previousTunable()));
1001 } else {
1002 s_complexChildrenByPath.remove(state.path());
1003 }
1004 }
1005 }
1006
1007 private static ComplexPathState addComplexPath(String path, ComplexTunable tunable) {
1008 synchronized (s_complexPathsMutex) {
1009 String normalized = PathUtil.normalizeName(path);
1010 if (s_complexByPath.containsKey(normalized)) {
1011 return new ComplexPathState(normalized, tunable, false, List.of());
1012 }
1013 linkRevisionParentForPath(normalized, tunable);
1014 s_complexByPath.put(normalized, tunable);
1015 s_complexPaths.computeIfAbsent(tunable, k -> new ArrayList<>()).add(normalized);
1016 List<RevisionParentLinkState> descendantLinks = linkExistingComplexDescendants(normalized);
1017 return new ComplexPathState(normalized, tunable, true, descendantLinks);
1018 }
1019 }
1020
1021 private static void restoreComplexPath(ComplexPathState state) {
1022 synchronized (s_complexPathsMutex) {
1023 removeComplexPath(state.path(), state.tunable());
1024 restoreRevisionParentLinks(state.descendantLinks());
1025 }
1026 }
1027
1028 private static void removeComplexPaths(String path) {
1029 synchronized (s_complexPathsMutex) {
1030 List<String> paths = new ArrayList<>();
1031 for (String complexPath : s_complexByPath.keySet()) {
1032 if (PathUtil.isPathOrDescendant(complexPath, path)) {
1033 paths.add(complexPath);
1034 }
1035 }
1036 for (String complexPath : paths) {
1037 ComplexTunable tunable = s_complexByPath.remove(complexPath);
1038 removeRevisionParentLink(complexPath);
1039 removeComplexPath(complexPath, tunable);
1040 }
1041 }
1042 }
1043
1044 private static void removeComplexChildPaths(String path) {
1045 synchronized (s_complexPathsMutex) {
1046 List<String> paths = new ArrayList<>();
1047 for (String childPath : s_complexChildrenByPath.keySet()) {
1048 if (PathUtil.isPathOrDescendant(childPath, path)) {
1049 paths.add(childPath);
1050 }
1051 }
1052 for (String childPath : paths) {
1053 s_complexChildrenByPath.remove(childPath);
1054 removeRevisionParentLink(childPath);
1055 }
1056 }
1057 }
1058
1059 @SuppressWarnings("PMD.CompareObjectsWithEquals")
1060 private static void removeComplexPath(String path, ComplexTunable tunable) {
1061 if (tunable == null) {
1062 return;
1063 }
1064 synchronized (s_complexPathsMutex) {
1065 if (s_complexByPath.get(path) == tunable) {
1066 s_complexByPath.remove(path);
1067 }
1068 removeRevisionParentLink(path);
1069 List<String> paths = s_complexPaths.get(tunable);
1070 if (paths == null) {
1071 return;
1072 }
1073 paths.remove(path);
1074 if (paths.isEmpty()) {
1075 s_complexPaths.remove(tunable);
1076 }
1077 }
1078 }
1079
1080 private static List<String> getComplexPaths(ComplexTunable tunable) {
1081 synchronized (s_complexPathsMutex) {
1082 List<String> paths = s_complexPaths.get(tunable);
1083 if (paths == null) {
1084 return List.of();
1085 }
1086 return List.copyOf(paths);
1087 }
1088 }
1089}
static void withUpdateMutex(Runnable func)
static long getTuneRevision(ComplexTunable tunable)
static boolean publish(String path, TunableBase tunable)
static void registerBackend(String prefix, TunableBackend backend)
static void updateComplexIfNeeded(ComplexTunable tunable)
static void runAfterUpdate(Runnable callback)
static void reportWarning(String msg)
static< T > void registerTypeHandler(Class< T > cls, TypeHandler< T > handler)
static< T > TypeHandler< T > getTypeHandler(T value)
static Consumer< String > getReportWarning()
static void resetChangedAfterUpdate(TunableBase tunable)
static void publishComplexChildren(String path, ComplexTunable tunable)
static TunableBackend getBackend(String path)
static TunableTable getTable(String path)
static void recordTuneApplied(TunableBase tunable)
static boolean publish(String path, ComplexTunable tunable)
static void setReportWarning(Consumer< String > func)
static long getTuneRevision(TunableBase tunable)
static String childTablePath(String path)
Definition PathUtil.java:66
static String normalizeBackendPrefix(String prefix)
Definition PathUtil.java:36
static String normalizeName(String path)
Definition PathUtil.java:93
static String normalizeTableName(String path)
Definition PathUtil.java:20
boolean publish(String path, TunableBase tunable)
record PublishedTunable(String path, TunableBase tunable, ComplexTunable complex)
boolean publishComplex(String path, ComplexTunable tunable)
default void markDirty(TunableBase tunable)
Tunable< T > createTunable(T initialValue, TunableConfig config)