28a5dfc7880dfaa8e8182b43533bec1290ab5598
[yangtools.git] / yang / yang-parser-spi / src / main / java / org / opendaylight / yangtools / yang / parser / spi / SchemaTreeNamespace.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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.spi;
9
10 import com.google.common.annotations.Beta;
11 import java.util.Collection;
12 import java.util.Iterator;
13 import java.util.Map;
14 import java.util.Optional;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
18 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
19 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
20 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
21 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
22 import org.opendaylight.yangtools.yang.model.api.stmt.UnknownStatement;
23 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
24 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
25 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
26 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
27 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
28
29 /**
30  * Statement local namespace, which holds direct schema node descendants. This corresponds to the contents of the schema
31  * tree as exposed through {@link SchemaTreeAwareEffectiveStatement}.
32  */
33 // FIXME: 7.0.0: this contract seems to fall on the reactor side of things rather than parser-spi. Consider moving this
34 //               into yang-(parser-)reactor-api.
35 @Beta
36 public final class SchemaTreeNamespace<D extends DeclaredStatement<QName>,
37             E extends SchemaTreeEffectiveStatement<D>>
38         extends NamespaceBehaviour<QName, StmtContext<?, D, E>, SchemaTreeNamespace<D, E>>
39         implements StatementNamespace<QName, D, E> {
40     private static final @NonNull SchemaTreeNamespace<?, ?> INSTANCE = new SchemaTreeNamespace<>();
41
42     @SuppressWarnings({ "rawtypes", "unchecked" })
43     private SchemaTreeNamespace() {
44         super((Class) SchemaTreeNamespace.class);
45     }
46
47     @SuppressWarnings("unchecked")
48     public static <D extends DeclaredStatement<QName>, E extends SchemaTreeEffectiveStatement<D>>
49             @NonNull SchemaTreeNamespace<D, E> getInstance() {
50         return (SchemaTreeNamespace<D, E>) INSTANCE;
51     }
52
53     @Override
54     public StmtContext<?, D, E> get(final QName key) {
55         // FIXME: 7.0.0: this method needs to be well-defined
56         return null;
57     }
58
59     /**
60      * {@inheritDoc}
61      *
62      * <p>
63      * This method is analogous to {@link SchemaTreeAwareEffectiveStatement#findSchemaTreeNode(QName)}.
64      */
65     @Override
66     public StmtContext<?, D, E> getFrom(final NamespaceStorageNode storage, final QName key) {
67         // Get the backing storage node for the requested storage
68         final NamespaceStorageNode storageNode = globalOrStatementSpecific(storage);
69         // Check try to look up existing node
70         final StmtContext<?, D, E> existing = storageNode.getFromLocalStorage(getIdentifier(), key);
71
72         // An existing node takes precedence, if it does not exist try to request it
73         return existing != null ? existing : requestFrom(storageNode, key);
74     }
75
76     private static <D extends DeclaredStatement<QName>, E extends EffectiveStatement<QName, D>>
77             StmtContext<?, D, E> requestFrom(final NamespaceStorageNode storageNode, final QName key) {
78         return storageNode instanceof OnDemandSchemaTreeStorageNode
79             ? ((OnDemandSchemaTreeStorageNode) storageNode).requestSchemaTreeChild(key) : null;
80     }
81
82     @Override
83     public Map<QName, StmtContext<?, D, E>> getAllFrom(final NamespaceStorageNode storage) {
84         // FIXME: 7.0.0: this method needs to be well-defined
85         return null;
86     }
87
88     @Override
89     @SuppressWarnings("unchecked")
90     public void addTo(final NamespaceStorageNode storage, final QName key, final StmtContext<?, D, E> value) {
91         final StmtContext<?, D, E> prev = globalOrStatementSpecific(storage).putToLocalStorageIfAbsent(
92             SchemaTreeNamespace.class, key, value);
93
94         if (prev != null) {
95             throw new SourceException(value.sourceReference(),
96                 "Error in module '%s': cannot add '%s'. Node name collision: '%s' already declared at %s",
97                 value.getRoot().rawArgument(), key, prev.argument(), prev.sourceReference());
98         }
99     }
100
101     /**
102      * Find statement context identified by interpreting specified {@link SchemaNodeIdentifier} starting at specified
103      * {@link StmtContext}.
104      *
105      * @param root Search root context
106      * @param identifier {@link SchemaNodeIdentifier} relative to search root
107      * @return Matching statement context, if present.
108      * @throws NullPointerException if any of the arguments is null
109      */
110     public static Optional<StmtContext<?, ?, ?>> findNode(final StmtContext<?, ?, ?> root,
111             final SchemaNodeIdentifier identifier) {
112         final Iterator<QName> iterator = identifier.getNodeIdentifiers().iterator();
113         if (!iterator.hasNext()) {
114             return Optional.of(root);
115         }
116
117         QName nextPath = iterator.next();
118         @SuppressWarnings("unchecked")
119         StmtContext<?, ?, ?> current = (StmtContext<?, ?, ?>) root.getFromNamespace(SchemaTreeNamespace.class,
120             nextPath);
121         if (current == null) {
122             return Optional.ofNullable(tryToFindUnknownStatement(nextPath.getLocalName(), root));
123         }
124         while (current != null && iterator.hasNext()) {
125             nextPath = iterator.next();
126             @SuppressWarnings("unchecked")
127             final StmtContext<?, ?, ?> nextNodeCtx = (StmtContext<?, ?, ?>) current.getFromNamespace(
128                 SchemaTreeNamespace.class, nextPath);
129             if (nextNodeCtx == null) {
130                 return Optional.ofNullable(tryToFindUnknownStatement(nextPath.getLocalName(), current));
131             }
132             current = nextNodeCtx;
133         }
134         return Optional.ofNullable(current);
135     }
136
137     @SuppressWarnings("unchecked")
138     private static StmtContext<?, ?, ?> tryToFindUnknownStatement(final String localName,
139             final StmtContext<?, ?, ?> current) {
140         final Collection<? extends StmtContext<?, ?, ?>> unknownSubstatements = StmtContextUtils.findAllSubstatements(
141             current, UnknownStatement.class);
142         for (final StmtContext<?, ?, ?> unknownSubstatement : unknownSubstatements) {
143             if (localName.equals(unknownSubstatement.rawArgument())) {
144                 return unknownSubstatement;
145             }
146         }
147         return null;
148     }
149
150     private static NamespaceStorageNode globalOrStatementSpecific(final NamespaceStorageNode storage) {
151         NamespaceStorageNode current = storage;
152         while (!isLocalOrGlobal(current.getStorageNodeType())) {
153             current = current.getParentNamespaceStorage();
154         }
155         return current;
156     }
157
158     private static boolean isLocalOrGlobal(final StorageNodeType type) {
159         return type == StorageNodeType.STATEMENT_LOCAL || type == StorageNodeType.GLOBAL;
160     }
161 }