dc93fd2b73450fc55f9aead3d888eb0807e17f2c
[yangtools.git] / model / yang-model-spi / src / main / java / org / opendaylight / yangtools / yang / model / spi / meta / EffectiveStatementMixins.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, 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.model.spi.meta;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.MoreObjects;
12 import com.google.common.base.Strings;
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableSet;
15 import java.util.Collection;
16 import java.util.Optional;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.yangtools.concepts.Mutable;
21 import org.opendaylight.yangtools.yang.common.Empty;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
24 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
25 import org.opendaylight.yangtools.yang.model.api.AddedByUsesAware;
26 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
28 import org.opendaylight.yangtools.yang.model.api.ConstraintMetaDefinition;
29 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
30 import org.opendaylight.yangtools.yang.model.api.CopyableNode;
31 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
35 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
36 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
38 import org.opendaylight.yangtools.yang.model.api.MustConstraintAware;
39 import org.opendaylight.yangtools.yang.model.api.MustDefinition;
40 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
41 import org.opendaylight.yangtools.yang.model.api.NotificationNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
43 import org.opendaylight.yangtools.yang.model.api.OutputSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.Status;
46 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.UsesNode;
49 import org.opendaylight.yangtools.yang.model.api.WhenConditionAware;
50 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
51 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.DescriptionEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.ErrorAppTagEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.ErrorMessageEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.InputEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.OutputEffectiveStatement;
57 import org.opendaylight.yangtools.yang.model.api.stmt.ReferenceEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.stmt.StatusEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
60 import org.opendaylight.yangtools.yang.model.api.stmt.WhenEffectiveStatement;
61 import org.opendaylight.yangtools.yang.model.spi.meta.EffectiveStatementMixins.EffectiveStatementWithFlags.FlagsBuilder;
62 import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression.QualifiedBound;
63
64 /**
65  * Mix-in interfaces providing services required by SchemaNode et al. These interfaces provide implementations, or
66  * implementation helpers based on default methods, so the correct behavior can be logically centralized.
67  */
68 @Beta
69 public final class EffectiveStatementMixins {
70     // Marker interface requiring all mixins to be derived from EffectiveStatement.
71     private interface Mixin<A, D extends DeclaredStatement<A>> extends EffectiveStatement<A, D> {
72         @SuppressWarnings("unchecked")
73         default <T> @NonNull Collection<? extends @NonNull T> filterEffectiveStatements(final Class<T> type) {
74             // Yeah, this is not nice, but saves one transformation
75             return (Collection<? extends T>) Collections2.filter(effectiveSubstatements(), type::isInstance);
76         }
77     }
78
79     /**
80      * Bridge between {@link EffectiveStatement} and {@link AugmentationTarget}.
81      *
82      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
83      * @param <D> Class representing declared version of this statement.
84      */
85     public interface AugmentationTargetMixin<A, D extends DeclaredStatement<A>>
86             extends Mixin<A, D>, AugmentationTarget {
87         @Override
88         default Collection<? extends AugmentationSchemaNode> getAvailableAugmentations() {
89             return filterEffectiveStatements(AugmentationSchemaNode.class);
90         }
91     }
92
93     /**
94      * Bridge between {@link EffectiveStatementWithFlags} and {@link AddedByUsesAware}.
95      *
96      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
97      * @param <D> Class representing declared version of this statement.
98      */
99     public interface AddedByUsesMixin<A, D extends DeclaredStatement<A>>
100             extends EffectiveStatementWithFlags<A, D>, AddedByUsesAware {
101         @Override
102         default boolean isAddedByUses() {
103             return (flags() & FlagsBuilder.ADDED_BY_USES) != 0;
104         }
105     }
106
107     /**
108      * Bridge between {@link EffectiveStatementWithFlags} and {@link ActionNodeContainer}.
109      *
110      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
111      * @param <D> Class representing declared version of this statement.
112      */
113     public interface ActionNodeContainerMixin<A, D extends DeclaredStatement<A>>
114             extends Mixin<A, D>, ActionNodeContainer {
115         @Override
116         default Collection<? extends ActionDefinition> getActions() {
117             return filterEffectiveStatements(ActionDefinition.class);
118         }
119     }
120
121     /**
122      * Bridge between {@link EffectiveStatementWithFlags} and {@link NotificationNodeContainer}.
123      *
124      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
125      * @param <D> Class representing declared version of this statement.
126      */
127     public interface NotificationNodeContainerMixin<A, D extends DeclaredStatement<A>>
128             extends Mixin<A, D>, NotificationNodeContainer {
129         @Override
130         default Collection<? extends NotificationDefinition> getNotifications() {
131             return filterEffectiveStatements(NotificationDefinition.class);
132         }
133     }
134
135     /**
136      * Bridge between {@link EffectiveStatementWithFlags} and {@link MustConstraintAware}.
137      *
138      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
139      * @param <D> Class representing declared version of this statement.
140      */
141     public interface MustConstraintMixin<A, D extends DeclaredStatement<A>> extends Mixin<A, D>, MustConstraintAware {
142         @Override
143         default Collection<? extends @NonNull MustDefinition> getMustConstraints() {
144             return filterEffectiveStatements(MustDefinition.class);
145         }
146     }
147
148     /**
149      * Bridge between {@link EffectiveStatementWithFlags} and {@link CopyableNode}.
150      *
151      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
152      * @param <D> Class representing declared version of this statement.
153      */
154     public interface CopyableMixin<A, D extends DeclaredStatement<A>> extends AddedByUsesMixin<A, D>, CopyableNode {
155         @Override
156         default boolean isAugmenting() {
157             return (flags() & FlagsBuilder.AUGMENTING) != 0;
158         }
159     }
160
161     /**
162      * Bridge between {@link EffectiveStatementWithFlags} and {@link DataNodeContainer}.
163      *
164      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
165      * @param <D> Class representing declared version of this statement.
166      */
167     public interface DataNodeContainerMixin<A, D extends DeclaredStatement<A>> extends DataNodeContainer, Mixin<A, D> {
168         @Override
169         default Collection<? extends TypeDefinition<?>> getTypeDefinitions() {
170             return filterTypeDefinitions(this);
171         }
172
173         @Override
174         default Collection<? extends DataSchemaNode> getChildNodes() {
175             return filterEffectiveStatements(DataSchemaNode.class);
176         }
177
178         @Override
179         default Collection<? extends GroupingDefinition> getGroupings() {
180             return filterEffectiveStatements(GroupingDefinition.class);
181         }
182
183         @Override
184         default Collection<? extends UsesNode> getUses() {
185             return filterEffectiveStatements(UsesNode.class);
186         }
187     }
188
189     /**
190      * Bridge between {@link EffectiveStatementWithFlags} and {@link DataSchemaNode}.
191      *
192      * @param <D> Class representing declared version of this statement.
193      */
194     public interface DataSchemaNodeMixin<D extends DeclaredStatement<QName>>
195             extends DataSchemaNode, CopyableMixin<QName, D>, SchemaNodeMixin<D>, WhenConditionMixin<QName, D> {
196         @Override
197         default Optional<Boolean> effectiveConfig() {
198             final int fl = flags() & FlagsBuilder.MASK_CONFIG;
199             switch (fl) {
200                 case FlagsBuilder.CONFIG_FALSE:
201                     return Optional.of(Boolean.FALSE);
202                 case FlagsBuilder.CONFIG_TRUE:
203                     return Optional.of(Boolean.TRUE);
204                 case FlagsBuilder.CONFIG_UNDEF:
205                     return Optional.empty();
206                 default:
207                     throw new IllegalStateException("Unhandled effective config flags " + fl);
208             }
209         }
210     }
211
212     /**
213      * Bridge between {@link EffectiveStatementWithFlags} and {@link DocumentedNode}.
214      *
215      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
216      * @param <D> Class representing declared version of this statement.
217      */
218     public interface DocumentedNodeMixin<A, D extends DeclaredStatement<A>> extends Mixin<A, D>, DocumentedNode {
219         /**
220          * Bridge between {@link EffectiveStatementWithFlags} and
221          * {@link org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus}.
222          *
223          * @param <A> Argument type ({@link Empty} if statement does not have argument.)
224          * @param <D> Class representing declared version of this statement.
225          */
226         interface WithStatus<A, D extends DeclaredStatement<A>>
227                 extends EffectiveStatementWithFlags<A, D>, DocumentedNodeMixin<A, D>, DocumentedNode.WithStatus {
228             @Override
229             default Status getStatus() {
230                 final int status = flags() & FlagsBuilder.MASK_STATUS;
231                 switch (status) {
232                     case FlagsBuilder.STATUS_CURRENT:
233                         return Status.CURRENT;
234                     case FlagsBuilder.STATUS_DEPRECATED:
235                         return Status.DEPRECATED;
236                     case FlagsBuilder.STATUS_OBSOLETE:
237                         return Status.OBSOLETE;
238                     default:
239                         throw new IllegalStateException("Illegal status " + status);
240                 }
241             }
242         }
243
244         @Override
245         default Optional<String> getDescription() {
246             return findFirstEffectiveSubstatementArgument(DescriptionEffectiveStatement.class);
247         }
248
249         @Override
250         default Optional<String> getReference() {
251             return findFirstEffectiveSubstatementArgument(ReferenceEffectiveStatement.class);
252         }
253
254         @Override
255         default Collection<? extends UnknownSchemaNode> getUnknownSchemaNodes() {
256             return filterEffectiveStatements(UnknownSchemaNode.class);
257         }
258     }
259
260     /**
261      * Bridge between {@link EffectiveStatementWithFlags} and {@link ConstraintMetaDefinition}.
262      *
263      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
264      * @param <D> Class representing declared version of this statement.
265      */
266     public interface ConstraintMetaDefinitionMixin<A, D extends DeclaredStatement<A>> extends DocumentedNodeMixin<A, D>,
267             ConstraintMetaDefinition {
268         @Override
269         default Optional<String> getErrorAppTag() {
270             return findFirstEffectiveSubstatementArgument(ErrorAppTagEffectiveStatement.class);
271         }
272
273         @Override
274         default Optional<String> getErrorMessage() {
275             return findFirstEffectiveSubstatementArgument(ErrorMessageEffectiveStatement.class);
276         }
277     }
278
279     /**
280      * Bridge between {@link EffectiveStatementWithFlags} and {@link MandatoryAware}.
281      *
282      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
283      * @param <D> Class representing declared version of this statement.
284      */
285     public interface MandatoryMixin<A, D extends DeclaredStatement<A>>
286             extends EffectiveStatementWithFlags<A, D>, MandatoryAware {
287         @Override
288         default boolean isMandatory() {
289             return (flags() & FlagsBuilder.MANDATORY) != 0;
290         }
291     }
292
293     /**
294      * Bridge between {@link EffectiveStatementWithFlags} and {@code presence} statement.
295      *
296      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
297      * @param <D> Class representing declared version of this statement.
298      */
299     public interface PresenceMixin<A, D extends DeclaredStatement<A>> extends EffectiveStatementWithFlags<A, D> {
300         default boolean presence() {
301             return (flags() & FlagsBuilder.PRESENCE) != 0;
302         }
303     }
304
305     /**
306      * Bridge between {@link EffectiveStatementWithFlags} and {@link SchemaNode}.
307      *
308      * @param <D> Class representing declared version of this statement.
309      */
310     public interface SchemaNodeMixin<D extends DeclaredStatement<QName>>
311             extends DocumentedNodeMixin.WithStatus<QName, D>, SchemaNode {
312         @Override
313         default QName getQName() {
314             return argument();
315         }
316     }
317
318     /**
319      * Bridge between {@link EffectiveStatementWithFlags} and {@link UnknownSchemaNode}.
320      *
321      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
322      * @param <D> Class representing declared version of this statement.
323      */
324     public interface UnknownSchemaNodeMixin<A, D extends DeclaredStatement<A>>
325             extends DocumentedNodeMixin.WithStatus<A, D>, CopyableMixin<A, D>, UnknownSchemaNode {
326         @Override
327         default String getNodeParameter() {
328             return Strings.nullToEmpty(getDeclared().rawArgument());
329         }
330     }
331
332     /**
333      * Bridge between {@link EffectiveStatementWithFlags} and {@code ordered-by} statement.
334      *
335      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
336      * @param <D> Class representing declared version of this statement.
337      */
338     public interface UserOrderedMixin<A, D extends DeclaredStatement<A>> extends EffectiveStatementWithFlags<A, D> {
339         default boolean userOrdered() {
340             return (flags() & FlagsBuilder.USER_ORDERED) != 0;
341         }
342     }
343
344     /**
345      * Helper used to locate the effective {@code when} statement and exposing its argument as per
346      * {@link WhenConditionAware}.
347      *
348      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
349      * @param <D> Class representing declared version of this statement.
350      */
351     public interface WhenConditionMixin<A, D extends DeclaredStatement<A>> extends Mixin<A, D>, WhenConditionAware {
352         @Override
353         default Optional<QualifiedBound> getWhenCondition() {
354             return findFirstEffectiveSubstatementArgument(WhenEffectiveStatement.class);
355         }
356     }
357
358     /**
359      * Helper bridge for operation containers ({@code input} and {@code output}).
360      *
361      * @param <D> Class representing declared version of this statement.
362      */
363     public interface OperationContainerMixin<D extends DeclaredStatement<QName>>
364             extends ContainerLike, DocumentedNodeMixin.WithStatus<QName, D>, DataNodeContainerMixin<QName, D>,
365                     MustConstraintMixin<QName, D>, WhenConditionMixin<QName, D>, AugmentationTargetMixin<QName, D>,
366                     SchemaNodeMixin<D>, CopyableMixin<QName, D> {
367         @Override
368         default Optional<ActionDefinition> findAction(final QName qname) {
369             return Optional.empty();
370         }
371
372         @Override
373         default Optional<NotificationDefinition> findNotification(final QName qname) {
374             return Optional.empty();
375         }
376
377         @Override
378         default Collection<? extends ActionDefinition> getActions() {
379             return ImmutableSet.of();
380         }
381
382         @Override
383         default Collection<? extends NotificationDefinition> getNotifications() {
384             return ImmutableSet.of();
385         }
386
387         @Override
388         default Optional<Boolean> effectiveConfig() {
389             return Optional.empty();
390         }
391
392         default String defaultToString() {
393             return MoreObjects.toStringHelper(this).add("qname", getQName()).toString();
394         }
395     }
396
397     /**
398      * Helper bridge for {@code anydata} and {@code anyxml} opaque data.
399      *
400      * @param <D> Class representing declared version of this statement.
401      */
402     public interface OpaqueDataSchemaNodeMixin<D extends DeclaredStatement<QName>, S extends DerivableSchemaNode<S>>
403             extends DerivableSchemaNode<S>, DataSchemaNodeMixin<D>, DocumentedNodeMixin.WithStatus<QName, D>,
404                     MandatoryMixin<QName, D>, MustConstraintMixin<QName, D>, WhenConditionMixin<QName, D> {
405         @Override
406         default QName getQName() {
407             return argument();
408         }
409     }
410
411     /**
412      * Helper bridge for {@code rpc} and {@code action} operations.
413      *
414      * @param <D> Class representing declared version of this statement.
415      */
416     public interface OperationDefinitionMixin<D extends DeclaredStatement<QName>>
417             extends SchemaNodeMixin<D>, OperationDefinition {
418         @Override
419         default Collection<? extends @NonNull TypeDefinition<?>> getTypeDefinitions() {
420             return filterTypeDefinitions(this);
421         }
422
423         @Override
424         default Collection<? extends @NonNull GroupingDefinition> getGroupings() {
425             return filterEffectiveStatements(GroupingDefinition.class);
426         }
427
428         @Override
429         default InputSchemaNode getInput() {
430             return findAsContainer(this, InputEffectiveStatement.class, InputSchemaNode.class);
431         }
432
433         @Override
434         default OutputSchemaNode getOutput() {
435             return findAsContainer(this, OutputEffectiveStatement.class, OutputSchemaNode.class);
436         }
437     }
438
439     /**
440      * Support interface for various mixins. Implementations are required to store 32bits worth of flags, which are
441      * globally assigned to sub-interfaces -- thus providing storage for many low-cardinality properties.
442      *
443      * @param <A> Argument type ({@link Empty} if statement does not have argument.)
444      * @param <D> Class representing declared version of this statement.
445      */
446     public interface EffectiveStatementWithFlags<A, D extends DeclaredStatement<A>> extends Mixin<A, D> {
447         /**
448          * Return flags associated with this statements. Flags can be built using {@link FlagsBuilder}.
449          *
450          * @return Flag field value (32 bits).
451          */
452         int flags();
453
454         @NonNullByDefault
455         final class FlagsBuilder implements Mutable {
456             // We still have 23 flags remaining
457             static final int STATUS_CURRENT       = 0x0001;
458             static final int STATUS_DEPRECATED    = 0x0002;
459             static final int STATUS_OBSOLETE      = 0x0003;
460             static final int MASK_STATUS          = 0x0003;
461
462             static final int MANDATORY            = 0x0004;
463
464             static final int AUGMENTING           = 0x0010;
465             static final int ADDED_BY_USES        = 0x0020;
466             private static final int MASK_HISTORY = 0x0030;
467
468             static final int USER_ORDERED         = 0x0040;
469             static final int PRESENCE             = 0x0080;
470
471             static final int CONFIG_UNDEF         = 0x0100;
472             static final int CONFIG_FALSE         = 0x0200;
473             static final int CONFIG_TRUE          = 0x0300;
474             static final int MASK_CONFIG          = CONFIG_TRUE;
475
476             private int flags;
477
478             public FlagsBuilder setConfiguration(final @Nullable Boolean config) {
479                 final int fl;
480                 if (config != null) {
481                     fl = config ? CONFIG_TRUE : CONFIG_FALSE;
482                 } else {
483                     fl = CONFIG_UNDEF;
484                 }
485                 flags = flags & ~MASK_CONFIG | fl;
486                 return this;
487             }
488
489             public FlagsBuilder setHistory(final CopyableNode history) {
490                 flags = flags & ~MASK_HISTORY
491                     | (history.isAugmenting() ? AUGMENTING : 0) | (history.isAddedByUses() ? ADDED_BY_USES : 0);
492                 return this;
493             }
494
495             public FlagsBuilder setMandatory(final boolean mandatory) {
496                 if (mandatory) {
497                     flags |= MANDATORY;
498                 } else {
499                     flags &= ~MANDATORY;
500                 }
501                 return this;
502             }
503
504             public FlagsBuilder setPresence(final boolean presence) {
505                 if (presence) {
506                     flags |= PRESENCE;
507                 } else {
508                     flags &= ~PRESENCE;
509                 }
510                 return this;
511             }
512
513             public FlagsBuilder setStatus(final Status status) {
514                 final int bits;
515                 switch (status) {
516                     case CURRENT:
517                         bits = STATUS_CURRENT;
518                         break;
519                     case DEPRECATED:
520                         bits = STATUS_DEPRECATED;
521                         break;
522                     case OBSOLETE:
523                         bits = STATUS_OBSOLETE;
524                         break;
525                     default:
526                         throw new IllegalStateException("Unhandled status " + status);
527                 }
528
529                 flags = flags & ~MASK_STATUS | bits;
530                 return this;
531             }
532
533             public FlagsBuilder setUserOrdered(final boolean userOrdered) {
534                 if (userOrdered) {
535                     flags |= USER_ORDERED;
536                 } else {
537                     flags &= ~USER_ORDERED;
538                 }
539                 return this;
540             }
541
542             public int toFlags() {
543                 return flags;
544             }
545         }
546     }
547
548     private EffectiveStatementMixins() {
549     }
550
551     static <T extends ContainerLike> T findAsContainer(final EffectiveStatement<?, ?> stmt,
552             final Class<? extends EffectiveStatement<QName, ?>> type, final Class<T> target) {
553         return target.cast(stmt.findFirstEffectiveSubstatement(type).get());
554     }
555
556     static Collection<? extends @NonNull TypeDefinition<?>> filterTypeDefinitions(final Mixin<?, ?> stmt) {
557         return Collections2.transform(stmt.filterEffectiveStatements(TypedefEffectiveStatement.class),
558             TypedefEffectiveStatement::getTypeDefinition);
559     }
560
561     public static int historyAndStatusFlags(final CopyableNode history,
562             final Collection<? extends EffectiveStatement<?, ?>> substatements) {
563         return new FlagsBuilder()
564                 .setHistory(history)
565                 .setStatus(substatements.stream()
566                     .filter(StatusEffectiveStatement.class::isInstance)
567                     .findAny()
568                     .map(stmt -> ((StatusEffectiveStatement) stmt).argument())
569                     .orElse(Status.CURRENT))
570                 .toFlags();
571     }
572 }