Optimize AbstractCompositeRuntimeType storage
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / impl / rt / AbstractCompositeRuntimeType.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.generator.impl.rt;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Functions;
14 import com.google.common.collect.ImmutableMap;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.util.Arrays;
17 import java.util.Collections;
18 import java.util.List;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
21 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
22 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
23 import org.opendaylight.mdsal.binding.runtime.api.GeneratedRuntimeType;
24 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
27 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
28
29 abstract class AbstractCompositeRuntimeType<S extends EffectiveStatement<?, ?>>
30         extends AbstractRuntimeType<S, GeneratedType> implements CompositeRuntimeType {
31     private static final RuntimeType[] EMPTY = new RuntimeType[0];
32
33     private final ImmutableMap<JavaTypeName, GeneratedRuntimeType> byClass;
34     private final Object bySchemaTree;
35
36     @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
37         justification = "https://github.com/spotbugs/spotbugs/issues/1985")
38     AbstractCompositeRuntimeType(final GeneratedType bindingType, final S statement, final List<RuntimeType> children) {
39         super(bindingType, statement);
40
41         byClass = children.stream()
42             .filter(GeneratedRuntimeType.class::isInstance)
43             .map(GeneratedRuntimeType.class::cast)
44             .collect(ImmutableMap.toImmutableMap(GeneratedRuntimeType::getIdentifier, Functions.identity()));
45
46         final var tmp = children.stream()
47             .filter(child -> child.statement() instanceof SchemaTreeEffectiveStatement)
48             .toArray(RuntimeType[]::new);
49         switch (tmp.length) {
50             case 0:
51                 bySchemaTree = EMPTY;
52                 break;
53             case 1:
54                 bySchemaTree = tmp[0];
55                 break;
56             default:
57                 Arrays.sort(tmp, (o1, o2) -> {
58                     final int cmp = extractQName(o1).compareTo(extractQName(o2));
59                     verify(cmp != 0, "Type %s conflicts with %s on schema tree", o1, o2);
60                     return cmp;
61                 });
62                 bySchemaTree = tmp;
63         }
64     }
65
66     @Override
67     public final RuntimeType schemaTreeChild(final QName qname) {
68         if (bySchemaTree instanceof RuntimeType) {
69             final var tmp = (RuntimeType) bySchemaTree;
70             return qname.equals(tmp.statement().argument()) ? tmp : null;
71         }
72
73         final var tmp = (RuntimeType[]) bySchemaTree;
74         @SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
75             justification = "https://github.com/spotbugs/spotbugs/issues/1985")
76         final int offset = Arrays.binarySearch(tmp, null, (o1, o2) -> {
77             // We make assumptions about how Arrays.binarySearch() is implemented: o2 is expected to be the provided
78             // key -- which is null. This helps CHA by not introducing a fake RuntimeType class and the
79             // corresponding instanceof checks.
80             verify(o2 == null, "Unexpected key %s", o2);
81             return extractQName(o1).compareTo(qname);
82         });
83         return offset < 0 ? null : tmp[offset];
84     }
85
86     @Override
87     public final GeneratedRuntimeType bindingChild(final JavaTypeName typeName) {
88         return byClass.get(requireNonNull(typeName));
89     }
90
91     // Makes an assertion of all types being of specified type
92     @SuppressWarnings("unchecked")
93     final <T extends RuntimeType> @NonNull List<T> schemaTree(final Class<T> expectedType) {
94         if (expectedType.isInstance(bySchemaTree)) {
95             return List.of(expectedType.cast(bySchemaTree));
96         }
97
98         final var tmp = (RuntimeType[]) bySchemaTree;
99         for (var item : tmp) {
100             verify(expectedType.isInstance(item), "Unexpected schema tree child %s", item);
101         }
102         return (List<T>) Collections.unmodifiableList(Arrays.asList(tmp));
103     }
104
105     private static @NonNull QName extractQName(final RuntimeType type) {
106         final var stmt = type.statement();
107         verify(stmt instanceof SchemaTreeEffectiveStatement, "Unexpected statement %s in %s", stmt, type);
108         return ((SchemaTreeEffectiveStatement<?>) stmt).argument();
109     }
110 }