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