Optimize AbstractCompositeRuntimeType storage
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / rt / AbstractCompositeRuntimeType.java
index 85ecec6148b4279d5a04ee97cda338b8ae2ca89b..2887813cf96bea200ad2fea89dc137ea1dd4d8dc 100644 (file)
@@ -7,11 +7,14 @@
  */
 package org.opendaylight.mdsal.binding.generator.impl.rt;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Functions;
-import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableMap;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
@@ -25,9 +28,13 @@ import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStateme
 
 abstract class AbstractCompositeRuntimeType<S extends EffectiveStatement<?, ?>>
         extends AbstractRuntimeType<S, GeneratedType> implements CompositeRuntimeType {
+    private static final RuntimeType[] EMPTY = new RuntimeType[0];
+
     private final ImmutableMap<JavaTypeName, GeneratedRuntimeType> byClass;
-    private final ImmutableMap<QName, RuntimeType> bySchemaTree;
+    private final Object bySchemaTree;
 
+    @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
+        justification = "https://github.com/spotbugs/spotbugs/issues/1985")
     AbstractCompositeRuntimeType(final GeneratedType bindingType, final S statement, final List<RuntimeType> children) {
         super(bindingType, statement);
 
@@ -36,20 +43,44 @@ abstract class AbstractCompositeRuntimeType<S extends EffectiveStatement<?, ?>>
             .map(GeneratedRuntimeType.class::cast)
             .collect(ImmutableMap.toImmutableMap(GeneratedRuntimeType::getIdentifier, Functions.identity()));
 
-        // Note: this may be over-sized, but we typically deal with schema tree statements, hence it is kind of accurate
-        final var builder = ImmutableMap.<QName, RuntimeType>builderWithExpectedSize(children.size());
-        for (var child : children) {
-            final var stmt = child.statement();
-            if (stmt instanceof SchemaTreeEffectiveStatement) {
-                builder.put(((SchemaTreeEffectiveStatement<?>)stmt).argument(), child);
-            }
+        final var tmp = children.stream()
+            .filter(child -> child.statement() instanceof SchemaTreeEffectiveStatement)
+            .toArray(RuntimeType[]::new);
+        switch (tmp.length) {
+            case 0:
+                bySchemaTree = EMPTY;
+                break;
+            case 1:
+                bySchemaTree = tmp[0];
+                break;
+            default:
+                Arrays.sort(tmp, (o1, o2) -> {
+                    final int cmp = extractQName(o1).compareTo(extractQName(o2));
+                    verify(cmp != 0, "Type %s conflicts with %s on schema tree", o1, o2);
+                    return cmp;
+                });
+                bySchemaTree = tmp;
         }
-        bySchemaTree = builder.build();
     }
 
     @Override
     public final RuntimeType schemaTreeChild(final QName qname) {
-        return bySchemaTree.get(requireNonNull(qname));
+        if (bySchemaTree instanceof RuntimeType) {
+            final var tmp = (RuntimeType) bySchemaTree;
+            return qname.equals(tmp.statement().argument()) ? tmp : null;
+        }
+
+        final var tmp = (RuntimeType[]) bySchemaTree;
+        @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
+            justification = "https://github.com/spotbugs/spotbugs/issues/1985")
+        final int offset = Arrays.binarySearch(tmp, null, (o1, o2) -> {
+            // We make assumptions about how Arrays.binarySearch() is implemented: o2 is expected to be the provided
+            // key -- which is null. This helps CHA by not introducing a fake RuntimeType class and the
+            // corresponding instanceof checks.
+            verify(o2 == null, "Unexpected key %s", o2);
+            return extractQName(o1).compareTo(qname);
+        });
+        return offset < 0 ? null : tmp[offset];
     }
 
     @Override
@@ -57,7 +88,23 @@ abstract class AbstractCompositeRuntimeType<S extends EffectiveStatement<?, ?>>
         return byClass.get(requireNonNull(typeName));
     }
 
-    final @NonNull ImmutableCollection<RuntimeType> schemaTreeChildren() {
-        return bySchemaTree.values();
+    // Makes an assertion of all types being of specified type
+    @SuppressWarnings("unchecked")
+    final <T extends RuntimeType> @NonNull List<T> schemaTree(final Class<T> expectedType) {
+        if (expectedType.isInstance(bySchemaTree)) {
+            return List.of(expectedType.cast(bySchemaTree));
+        }
+
+        final var tmp = (RuntimeType[]) bySchemaTree;
+        for (var item : tmp) {
+            verify(expectedType.isInstance(item), "Unexpected schema tree child %s", item);
+        }
+        return (List<T>) Collections.unmodifiableList(Arrays.asList(tmp));
+    }
+
+    private static @NonNull QName extractQName(final RuntimeType type) {
+        final var stmt = type.statement();
+        verify(stmt instanceof SchemaTreeEffectiveStatement, "Unexpected statement %s in %s", stmt, type);
+        return ((SchemaTreeEffectiveStatement<?>) stmt).argument();
     }
-}
+}
\ No newline at end of file