Do not use singleton ImmutableMap for datatree/schematree
[yangtools.git] / yang / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / AbstractSchemaEffectiveDocumentedNode.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, 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.parser.rfc7950.stmt;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableSet;
16 import java.lang.invoke.VarHandle;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.LinkedHashMap;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Optional;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
26 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
28 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
29 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
30 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeAwareEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
34 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
35 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
37 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
38 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
39
40 /**
41  * An {@link AbstractEffectiveDocumentedNode} which can optionally support {@link SchemaTreeAwareEffectiveStatement}.
42  *
43  * @param <A> Argument type ({@link Void} if statement does not have argument.)
44  * @param <D> Class representing declared version of this statement.
45  * @author Robert Varga
46  */
47 @Beta
48 public abstract class AbstractSchemaEffectiveDocumentedNode<A, D extends DeclaredStatement<A>>
49         extends AbstractEffectiveDocumentedNode<A, D> {
50     private final Map<QName, DataTreeEffectiveStatement<?>> dataTreeNamespace;
51     private final Map<QName, SchemaTreeEffectiveStatement<?>> schemaTreeNamespace;
52
53     protected AbstractSchemaEffectiveDocumentedNode(final StmtContext<A, D, ?> ctx) {
54         super(ctx);
55
56         // This check is rather weird, but comes from our desire to lower memory footprint while providing both
57         // EffectiveStatements and SchemaNode interfaces -- which do not overlap completely where child lookups are
58         // concerned. This ensures that we have SchemaTree index available for use with child lookups.
59         if (this instanceof SchemaTreeAwareEffectiveStatement || this instanceof DataNodeContainer) {
60             schemaTreeNamespace = createSchemaTreeNamespace(ctx.getStatementSourceReference(),
61                 effectiveSubstatements());
62         } else {
63             schemaTreeNamespace = ImmutableMap.of();
64         }
65         if (this instanceof DataTreeAwareEffectiveStatement && !schemaTreeNamespace.isEmpty()) {
66             dataTreeNamespace = createDataTreeNamespace(ctx.getStatementSourceReference(), schemaTreeNamespace);
67         } else {
68             dataTreeNamespace = ImmutableMap.of();
69         }
70     }
71
72     @Override
73     @SuppressWarnings("unchecked")
74     protected <K, V, N extends IdentifierNamespace<K, V>> Optional<? extends Map<K, V>> getNamespaceContents(
75             final Class<N> namespace) {
76         if (this instanceof SchemaTreeAwareEffectiveStatement
77                 && SchemaTreeAwareEffectiveStatement.Namespace.class.equals(namespace)) {
78             return Optional.of((Map<K, V>) schemaTreeNamespace);
79         }
80         if (this instanceof DataTreeAwareEffectiveStatement
81                 && DataTreeAwareEffectiveStatement.Namespace.class.equals(namespace)) {
82             return Optional.of((Map<K, V>) dataTreeNamespace);
83         }
84         return super.getNamespaceContents(namespace);
85     }
86
87     protected final <T> @NonNull ImmutableSet<T> derivedSet(final VarHandle vh, final @NonNull Class<T> clazz) {
88         final ImmutableSet<T> existing = (ImmutableSet<T>) vh.getAcquire(this);
89         return existing != null ? existing : loadSet(vh, clazz);
90     }
91
92     /**
93      * Indexing support for {@link DataNodeContainer#findDataChildByName(QName)}.
94      */
95     protected final Optional<DataSchemaNode> findDataSchemaNode(final QName name) {
96         // Only DataNodeContainer subclasses should be calling this method
97         verify(this instanceof DataNodeContainer);
98         final SchemaTreeEffectiveStatement<?> child = schemaTreeNamespace.get(requireNonNull(name));
99         return child instanceof DataSchemaNode ? Optional.of((DataSchemaNode) child) : Optional.empty();
100     }
101
102     static @NonNull Map<QName, SchemaTreeEffectiveStatement<?>> createSchemaTreeNamespace(
103             final StatementSourceReference ref, final Collection<? extends EffectiveStatement<?, ?>> substatements) {
104         final Map<QName, SchemaTreeEffectiveStatement<?>> schemaChildren = new LinkedHashMap<>();
105         substatements.stream().filter(SchemaTreeEffectiveStatement.class::isInstance)
106             .forEach(child -> putChild(schemaChildren, (SchemaTreeEffectiveStatement) child, ref, "schema"));
107         return toImmutable(schemaChildren);
108     }
109
110     static @NonNull Map<QName, DataTreeEffectiveStatement<?>> createDataTreeNamespace(
111             final StatementSourceReference ref,
112             final Map<QName, SchemaTreeEffectiveStatement<?>> schemaTreeNamespace) {
113         final Map<QName, DataTreeEffectiveStatement<?>> dataChildren = new LinkedHashMap<>();
114         boolean sameAsSchema = true;
115
116         for (SchemaTreeEffectiveStatement<?> child : schemaTreeNamespace.values()) {
117             if (child instanceof DataTreeEffectiveStatement) {
118                 putChild(dataChildren, (DataTreeEffectiveStatement<?>) child, ref, "data");
119             } else {
120                 sameAsSchema = false;
121                 putChoiceDataChildren(dataChildren, ref, child);
122             }
123         }
124
125         // This is a mighty hack to lower memory usage: if we consumed all schema tree children as data nodes,
126         // the two maps are equal and hence we can share the instance.
127         return sameAsSchema ? (Map) schemaTreeNamespace : toImmutable(dataChildren);
128     }
129
130     private static <K, V> Map<K, V> toImmutable(final Map<K, V> map) {
131         switch (map.size()) {
132             case 0:
133                 return ImmutableMap.of();
134             case 1:
135                 // Special case: singleton ImmutableMap is actually SingletonImmutableBiMap, which allocates its inverse
136                 //               view and its keySet() when asked for values() -- which costs us 64 bytes (40+24).
137                 //               java.util.Collections can do the same job for less memory.
138                 final Entry<K, V> entry = map.entrySet().iterator().next();
139                 return Collections.singletonMap(entry.getKey(), entry.getValue());
140             default:
141                 return ImmutableMap.copyOf(map);
142         }
143     }
144
145     @SuppressWarnings("unchecked")
146     private <T> @NonNull ImmutableSet<T> loadSet(final VarHandle vh, final @NonNull Class<T> clazz) {
147         final ImmutableSet<T> computed = ImmutableSet.copyOf(allSubstatementsOfType(clazz));
148         final Object witness = vh.compareAndExchangeRelease(this, null, computed);
149         return witness == null ? computed : (ImmutableSet<T>) witness;
150     }
151
152     private static <T extends SchemaTreeEffectiveStatement<?>> void putChild(final Map<QName, T> map,
153             final T child, final StatementSourceReference ref, final String tree) {
154         final QName id = child.getIdentifier();
155         final T prev = map.putIfAbsent(id, child);
156         SourceException.throwIf(prev != null, ref,
157                 "Cannot add %s tree child with name %s, a conflicting child already exists", tree, id);
158     }
159
160     private static void putChoiceDataChildren(final Map<QName, DataTreeEffectiveStatement<?>> map,
161             final StatementSourceReference ref, final SchemaTreeEffectiveStatement<?> child) {
162         // For choice statements go through all their cases and fetch their data children
163         if (child instanceof ChoiceEffectiveStatement) {
164             child.streamEffectiveSubstatements(CaseEffectiveStatement.class).forEach(
165                 caseStmt -> caseStmt.streamEffectiveSubstatements(SchemaTreeEffectiveStatement.class).forEach(stmt -> {
166                     if (stmt instanceof DataTreeEffectiveStatement) {
167                         putChild(map, (DataTreeEffectiveStatement<?>) stmt, ref, "data");
168                     } else {
169                         putChoiceDataChildren(map, ref, stmt);
170                     }
171                 }));
172         }
173     }
174 }