Fix data/schema tree reuse
[yangtools.git] / yang / yang-model-spi / src / main / java / org / opendaylight / yangtools / yang / model / spi / meta / AbstractEffectiveStatement.java
1 /*
2  * Copyright (c) 2020 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.yangtools.yang.model.spi.meta;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import java.util.Collection;
15 import java.util.LinkedHashMap;
16 import java.util.Map;
17 import java.util.Optional;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
21 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
22 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
23 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
24 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
25 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
26 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
27
28 /**
29  * Baseline stateless implementation of an EffectiveStatement. This class adds a few default implementations and
30  * namespace dispatch, but does not actually force any state on its subclasses. This approach adds requirements for an
31  * implementation, but it leaves it up to the final class to provide object layout.
32  *
33  * <p>
34  * This finds immense value in catering the common case, for example effective statements which can, but typically
35  * do not, contain substatements.
36  *
37  * @param <A> Argument type ({@link Void} if statement does not have argument.)
38  * @param <D> Class representing declared version of this statement.
39  */
40 abstract class AbstractEffectiveStatement<A, D extends DeclaredStatement<A>>
41         extends AbstractModelStatement<A> implements EffectiveStatement<A, D> {
42     @Override
43     public final <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends V> get(final Class<N> namespace,
44             final K identifier) {
45         return Optional.ofNullable(getAll(namespace).get(requireNonNull(identifier)));
46     }
47
48     @Override
49     public final <K, V, N extends IdentifierNamespace<K, V>> Map<K, V> getAll(final Class<N> namespace) {
50         final Optional<? extends Map<K, V>> ret = getNamespaceContents(requireNonNull(namespace));
51         return ret.isPresent() ? ret.get() : ImmutableMap.of();
52     }
53
54     @Override
55     public Collection<? extends EffectiveStatement<?, ?>> effectiveSubstatements() {
56         return ImmutableList.of();
57     }
58
59     /**
60      * Return the statement-specific contents of specified namespace, if available.
61      *
62      * @param namespace Requested namespace
63      * @return Namespace contents, if available.
64      */
65     protected <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
66             final @NonNull Class<N> namespace) {
67         return Optional.empty();
68     }
69
70     /**
71      * Utility method for recovering singleton lists squashed by {@link #maskList(ImmutableList)}.
72      *
73      * @param masked list to unmask
74      * @return Unmasked list
75      * @throws NullPointerException if masked is null
76      * @throws ClassCastException if masked object does not match EffectiveStatement
77      */
78     @SuppressWarnings({ "rawtypes", "unchecked" })
79     protected static final @NonNull ImmutableList<? extends @NonNull EffectiveStatement<?, ?>> unmaskList(
80             final @NonNull Object masked) {
81         return (ImmutableList) unmaskList(masked, EffectiveStatement.class);
82     }
83
84     // TODO: below methods need to find a better place, this is just a temporary hideout as their public class is on
85     //       its way out
86     protected static @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> createSchemaTreeNamespace(
87             final Collection<? extends EffectiveStatement<?, ?>> substatements) {
88         final Map<QName, SchemaTreeEffectiveStatement<?>> schemaChildren = new LinkedHashMap<>();
89         substatements.stream().filter(SchemaTreeEffectiveStatement.class::isInstance)
90             .forEach(child -> putChild(schemaChildren, (SchemaTreeEffectiveStatement<?>) child, "schema"));
91         return schemaChildren;
92     }
93
94     protected static @NonNull ImmutableMap<QName, DataTreeEffectiveStatement<?>> createDataTreeNamespace(
95             final Collection<SchemaTreeEffectiveStatement<?>> schemaTreeStatements,
96             // Note: this dance is needed to not retain ImmutableMap$Values
97             final ImmutableMap<QName, SchemaTreeEffectiveStatement<?>> schemaTreeNamespace) {
98         final Map<QName, DataTreeEffectiveStatement<?>> dataChildren = new LinkedHashMap<>();
99         boolean sameAsSchema = true;
100
101         for (SchemaTreeEffectiveStatement<?> child : schemaTreeStatements) {
102             if (!indexDataTree(dataChildren, child)) {
103                 sameAsSchema = false;
104             }
105         }
106
107         // This is a mighty hack to lower memory usage: if we consumed all schema tree children as data nodes,
108         // the two maps are equal and hence we can share the instance.
109         return sameAsSchema ? (ImmutableMap) schemaTreeNamespace : ImmutableMap.copyOf(dataChildren);
110     }
111
112     private static boolean indexDataTree(final Map<QName, DataTreeEffectiveStatement<?>> map,
113             final EffectiveStatement<?, ?> stmt) {
114         if (stmt instanceof DataTreeEffectiveStatement) {
115             putChild(map, (DataTreeEffectiveStatement<?>) stmt, "data");
116             return true;
117         } else if (stmt instanceof ChoiceEffectiveStatement) {
118             // For choice statements go through all their cases and fetch their data children
119             for (EffectiveStatement<?, ?> choiceChild : stmt.effectiveSubstatements()) {
120                 if (choiceChild instanceof CaseEffectiveStatement) {
121                     for (EffectiveStatement<?, ?> caseChild : choiceChild.effectiveSubstatements()) {
122                         indexDataTree(map, caseChild);
123                     }
124                 }
125             }
126             return false;
127         } else if (stmt instanceof CaseEffectiveStatement) {
128             // For case statements go through all their statements
129             for (EffectiveStatement<?, ?> child : stmt.effectiveSubstatements()) {
130                 indexDataTree(map, child);
131             }
132             return false;
133         } else {
134             return true;
135         }
136     }
137
138     private static <T extends SchemaTreeEffectiveStatement<?>> void putChild(final Map<QName, T> map, final T child,
139             final String tree) {
140         final QName id = child.getIdentifier();
141         final T prev = map.putIfAbsent(id, child);
142         if (prev != null) {
143             throw new SubstatementIndexingException(
144                 "Cannot add " + tree + " tree child with name " + id + ", a conflicting child already exists");
145         }
146     }
147 }