002b9bc50cd32cb4e4391646a003b80f6086fe5e
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / SubstatementContext.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.stmt.reactor;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Verify;
12 import com.google.common.collect.ImmutableSet;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Optional;
16 import java.util.Set;
17 import javax.annotation.Nonnull;
18 import org.opendaylight.yangtools.util.OptionalBoolean;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21 import org.opendaylight.yangtools.yang.common.YangVersion;
22 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
23 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
24 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
25 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
26 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
27 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
28 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
29 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceStatement;
30 import org.opendaylight.yangtools.yang.model.api.stmt.ConfigStatement;
31 import org.opendaylight.yangtools.yang.model.api.stmt.DeviationStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
34 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
35 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
43 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
44 import org.opendaylight.yangtools.yang.parser.spi.source.AugmentToChoiceNamespace;
45 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
46 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
47 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 final class SubstatementContext<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>> extends
52         StatementContextBase<A, D, E> {
53     private static final Logger LOG = LoggerFactory.getLogger(SubstatementContext.class);
54
55     private final StatementContextBase<?, ?, ?> parent;
56     private final A argument;
57
58     /**
59      * config statements are not all that common which means we are performing a recursive search towards the root
60      * every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search
61      * for the (usually non-existent) config statement.
62      *
63      * This field maintains a resolution cache, so once we have returned a result, we will keep on returning the same
64      * result without performing any lookups.
65      */
66     // BooleanField value
67     private byte configuration;
68
69     /**
70      * This field maintains a resolution cache for ignore config, so once we have returned a result, we will
71      * keep on returning the same result without performing any lookups.
72      */
73     // BooleanField value
74     private byte ignoreConfig;
75
76     /**
77      * This field maintains a resolution cache for ignore if-feature, so once we have returned a result, we will
78      * keep on returning the same result without performing any lookups.
79      */
80     // BooleanField value
81     private byte ignoreIfFeature;
82
83     private volatile SchemaPath schemaPath;
84
85     SubstatementContext(final StatementContextBase<?, ?, ?> parent, final StatementDefinitionContext<A, D, E> def,
86             final StatementSourceReference ref, final String rawArgument) {
87         super(def, ref, rawArgument);
88         this.parent = Preconditions.checkNotNull(parent, "Parent must not be null");
89         this.argument = def.parseArgumentValue(this, rawStatementArgument());
90     }
91
92     SubstatementContext(final StatementContextBase<A, D, E> original, final StatementContextBase<?, ?, ?> parent,
93             final A argument, final CopyType copyType) {
94         super(original, copyType);
95         this.parent = Preconditions.checkNotNull(parent);
96         this.argument = argument;
97     }
98
99     @Override
100     public StatementContextBase<?, ?, ?> getParentContext() {
101         return parent;
102     }
103
104     @Override
105     public StorageNodeType getStorageNodeType() {
106         return StorageNodeType.STATEMENT_LOCAL;
107     }
108
109     @Override
110     public NamespaceStorageNode getParentNamespaceStorage() {
111         return parent;
112     }
113
114     @Override
115     public Registry getBehaviourRegistry() {
116         return parent.getBehaviourRegistry();
117     }
118
119     @Nonnull
120     @Override
121     public RootStatementContext<?, ?, ?> getRoot() {
122         return parent.getRoot();
123     }
124
125     @Override
126     public A getStatementArgument() {
127         return argument;
128     }
129
130     StatementContextBase<A, D, E> createCopy(final QNameModule newQNameModule,
131             final StatementContextBase<?, ?, ?> newParent, final CopyType typeOfCopy) {
132         Preconditions.checkState(getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL,
133                 "Attempted to copy statement %s which has completed phase %s", this, getCompletedPhase());
134
135         final A argumentCopy = newQNameModule == null ? argument
136                 : definition().adaptArgumentValue(this, newQNameModule);
137         final SubstatementContext<A, D, E> copy = new SubstatementContext<>(this, newParent, argumentCopy, typeOfCopy);
138
139         definition().onStatementAdded(copy);
140
141         copy.copyStatements(this, newQNameModule, typeOfCopy);
142         return copy;
143     }
144
145     private void copyStatements(final SubstatementContext<A, D, E> original, final QNameModule newQNameModule,
146             final CopyType typeOfCopy) {
147         final Collection<? extends Mutable<?, ?, ?>> declared = original.mutableDeclaredSubstatements();
148         final Collection<? extends Mutable<?, ?, ?>> effective = original.mutableEffectiveSubstatements();
149         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
150
151         for (final Mutable<?, ?, ?> stmtContext : declared) {
152             if (stmtContext.isSupportedByFeatures()) {
153                 copySubstatement(stmtContext, newQNameModule, typeOfCopy, buffer);
154             }
155         }
156
157         for (final Mutable<?, ?, ?> stmtContext : effective) {
158             copySubstatement(stmtContext, newQNameModule, typeOfCopy, buffer);
159         }
160
161         addEffectiveSubstatements(buffer);
162     }
163
164     private void copySubstatement(final Mutable<?, ?, ?> stmtContext, final QNameModule newQNameModule,
165             final CopyType typeOfCopy, final Collection<Mutable<?, ?, ?>> buffer) {
166         if (needToCopyByUses(stmtContext)) {
167             final Mutable<?, ?, ?> copy = childCopyOf(stmtContext, typeOfCopy, newQNameModule);
168             LOG.debug("Copying substatement {} for {} as", stmtContext, this, copy);
169             buffer.add(copy);
170         } else if (isReusedByUses(stmtContext)) {
171             LOG.debug("Reusing substatement {} for {}", stmtContext, this);
172             buffer.add(stmtContext);
173         } else {
174             LOG.debug("Skipping statement {}", stmtContext);
175         }
176     }
177
178     // FIXME: revise this, as it seems to be wrong
179     private static final Set<YangStmtMapping> NOCOPY_FROM_GROUPING_SET = ImmutableSet.of(
180         YangStmtMapping.DESCRIPTION,
181         YangStmtMapping.REFERENCE,
182         YangStmtMapping.STATUS);
183     private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(
184         YangStmtMapping.TYPE,
185         YangStmtMapping.TYPEDEF,
186         YangStmtMapping.USES);
187
188     private static boolean needToCopyByUses(final StmtContext<?, ?, ?> stmtContext) {
189         final StatementDefinition def = stmtContext.getPublicDefinition();
190         if (REUSED_DEF_SET.contains(def)) {
191             LOG.debug("Will reuse {} statement {}", def, stmtContext);
192             return false;
193         }
194         if (NOCOPY_FROM_GROUPING_SET.contains(def)) {
195             return !YangStmtMapping.GROUPING.equals(stmtContext.getParentContext().getPublicDefinition());
196         }
197
198         LOG.debug("Will copy {} statement {}", def, stmtContext);
199         return true;
200     }
201
202     private static boolean isReusedByUses(final StmtContext<?, ?, ?> stmtContext) {
203         return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition());
204     }
205
206     private boolean isSupportedAsShorthandCase() {
207         final Collection<?> supportedCaseShorthands = getFromNamespace(ValidationBundlesNamespace.class,
208                 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
209         return supportedCaseShorthands == null || supportedCaseShorthands.contains(getPublicDefinition());
210     }
211
212     private SchemaPath createSchemaPath() {
213         final Optional<SchemaPath> maybeParentPath = parent.getSchemaPath();
214         Verify.verify(maybeParentPath.isPresent(), "Parent %s does not have a SchemaPath", parent);
215         final SchemaPath parentPath = maybeParentPath.get();
216
217         if (StmtContextUtils.isUnknownStatement(this)) {
218             return parentPath.createChild(getPublicDefinition().getStatementName());
219         }
220         if (argument instanceof QName) {
221             final QName qname = (QName) argument;
222             if (StmtContextUtils.producesDeclared(this, UsesStatement.class)) {
223                 return maybeParentPath.orElse(null);
224             }
225
226             final SchemaPath path;
227             if ((StmtContextUtils.producesDeclared(getParentContext(), ChoiceStatement.class)
228                     || Boolean.TRUE.equals(parent.getFromNamespace(AugmentToChoiceNamespace.class, parent)))
229                     && isSupportedAsShorthandCase()) {
230                 path = parentPath.createChild(qname);
231             } else {
232                 path = parentPath;
233             }
234             return path.createChild(qname);
235         }
236         if (argument instanceof String) {
237             // FIXME: This may yield illegal argument exceptions
238             final Optional<StmtContext<?, ?, ?>> originalCtx = getOriginalCtx();
239             final QName qname = StmtContextUtils.qnameFromArgument(originalCtx.orElse(this), (String) argument);
240             return parentPath.createChild(qname);
241         }
242         if (argument instanceof SchemaNodeIdentifier
243                 && (StmtContextUtils.producesDeclared(this, AugmentStatement.class)
244                         || StmtContextUtils.producesDeclared(this, RefineStatement.class)
245                         || StmtContextUtils.producesDeclared(this, DeviationStatement.class))) {
246
247             return parentPath.createChild(((SchemaNodeIdentifier) argument).getPathFromRoot());
248         }
249
250         // FIXME: this does not look right
251         return maybeParentPath.orElse(null);
252     }
253
254     @Nonnull
255     @Override
256     public Optional<SchemaPath> getSchemaPath() {
257         SchemaPath local = schemaPath;
258         if (local == null) {
259             synchronized (this) {
260                 local = schemaPath;
261                 if (local == null) {
262                     local = createSchemaPath();
263                     schemaPath = local;
264                 }
265             }
266
267         }
268
269         return Optional.ofNullable(local);
270     }
271
272     @Override
273     public boolean isConfiguration() {
274         if (isIgnoringConfig()) {
275             return true;
276         }
277
278         if (OptionalBoolean.isPresent(configuration)) {
279             return OptionalBoolean.get(configuration);
280         }
281
282         final StmtContext<Boolean, ?, ?> configStatement = StmtContextUtils.findFirstSubstatement(this,
283             ConfigStatement.class);
284         final boolean parentIsConfig = parent.isConfiguration();
285
286         final boolean isConfig;
287         if (configStatement != null) {
288             isConfig = configStatement.getStatementArgument();
289
290             // Validity check: if parent is config=false this cannot be a config=true
291             InferenceException.throwIf(isConfig && !parentIsConfig, getStatementSourceReference(),
292                     "Parent node has config=false, this node must not be specifed as config=true");
293         } else {
294             // If "config" statement is not specified, the default is the same as the parent's "config" value.
295             isConfig = parentIsConfig;
296         }
297
298         // Resolved, make sure we cache this return
299         configuration = OptionalBoolean.of(isConfig);
300         return isConfig;
301     }
302
303     @Override
304     public boolean isEnabledSemanticVersioning() {
305         return parent.isEnabledSemanticVersioning();
306     }
307
308     @Override
309     public YangVersion getRootVersion() {
310         return getRoot().getRootVersion();
311     }
312
313     @Override
314     public void setRootVersion(final YangVersion version) {
315         getRoot().setRootVersion(version);
316     }
317
318     @Override
319     public void addMutableStmtToSeal(final MutableStatement mutableStatement) {
320         getRoot().addMutableStmtToSeal(mutableStatement);
321     }
322
323     @Override
324     public void addRequiredModule(final ModuleIdentifier dependency) {
325         getRoot().addRequiredModule(dependency);
326     }
327
328     @Override
329     public void setRootIdentifier(final ModuleIdentifier identifier) {
330         getRoot().setRootIdentifier(identifier);
331     }
332
333     @Override
334     protected boolean isIgnoringIfFeatures() {
335         if (OptionalBoolean.isPresent(ignoreIfFeature)) {
336             return OptionalBoolean.get(ignoreIfFeature);
337         }
338
339         final boolean ret = definition().isIgnoringIfFeatures() || parent.isIgnoringIfFeatures();
340         ignoreIfFeature = OptionalBoolean.of(ret);
341
342         return ret;
343     }
344
345     @Override
346     protected boolean isIgnoringConfig() {
347         if (OptionalBoolean.isPresent(ignoreConfig)) {
348             return OptionalBoolean.get(ignoreConfig);
349         }
350
351         final boolean ret = definition().isIgnoringConfig() || parent.isIgnoringConfig();
352         ignoreConfig = OptionalBoolean.of(ret);
353
354         return ret;
355     }
356
357     @Override
358     protected boolean isParentSupportedByFeatures() {
359         return parent.isSupportedByFeatures();
360     }
361 }