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