edbf8ac31aabd4f7768db537dca0fb65e4877cfb
[yangtools.git] / model / yang-model-spi / src / main / java / org / opendaylight / yangtools / yang / model / spi / DefaultSchemaTreeInference.java
1 /*
2  * Copyright (c) 2021 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;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.Lists;
16 import java.util.Iterator;
17 import java.util.List;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
21 import org.opendaylight.yangtools.yang.model.api.SchemaTreeInference;
22 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
23 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
24 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
25 import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference.WithPath;
26 import org.slf4j.LoggerFactory;
27
28 /**
29  * Default implementation of a {@link SchemaTreeInference}. Guaranteed to be consistent with its
30  * {@link #modelContext()}.
31  */
32 @Beta
33 @NonNullByDefault
34 public final class DefaultSchemaTreeInference extends WithPath<SchemaTreeEffectiveStatement<?>>
35         implements SchemaTreeInference {
36     private static final String VERIFY_UNSAFE_PROP =
37         "org.opendaylight.yangtools.yang.model.spi.DefaultSchemaTreeInference.verifyUnsafeOf";
38     private static final boolean VERIFY_UNSAFE = Boolean.getBoolean(VERIFY_UNSAFE_PROP);
39
40     static {
41         if (VERIFY_UNSAFE) {
42             LoggerFactory.getLogger(DefaultSchemaTreeInference.class)
43                 .info("DefaultSchemaTreeInference.unsafeOf() arguments are being verified");
44         }
45     }
46
47     private DefaultSchemaTreeInference(final EffectiveModelContext modelContext,
48             final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
49         super(modelContext, path);
50     }
51
52     /**
53      * Create a new instance based on an {@link EffectiveModelContext} and an {@link Absolute} schema node identifier.
54      *
55      * @param modelContext Associated {@link EffectiveModelContext}
56      * @param path An absolute schema node identifier
57      * @return A new instance
58      * @throws NullPointerException if any argument is null
59      * @throws IllegalArgumentException if the provided {@code path} cannot be resolved in {@code modelContext}
60      */
61     public static DefaultSchemaTreeInference of(final EffectiveModelContext modelContext, final Absolute path) {
62         return new DefaultSchemaTreeInference(modelContext, resolveSteps(modelContext, path.getNodeIdentifiers()));
63     }
64
65     /**
66      * Create a new instance based on an {@link EffectiveModelContext} and a resolved sequence of statements. Provided
67      * statements are expected to have been produced in a validated manner and are normally trusted to be accurate.
68      *
69      * <p>
70      * Run-time verification of {@code path} can be enabled by setting the {@value #VERIFY_UNSAFE_PROP} system property
71      * to {@code true}.
72      *
73      * @param modelContext Associated {@link EffectiveModelContext}
74      * @param path Resolved statement path
75      * @return A new instance
76      * @throws NullPointerException if any argument is null
77      * @throws IllegalArgumentException if {@code path} is empty or when verification is enabled and the {@code path}
78      *                                  does not match the {@code modelContext}'s schema tree
79      */
80     public static DefaultSchemaTreeInference unsafeOf(final EffectiveModelContext modelContext,
81             final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
82         checkArgument(!path.isEmpty(), "Path must not be empty");
83         return VERIFY_UNSAFE ? verifiedOf(modelContext, path) : new DefaultSchemaTreeInference(modelContext, path);
84     }
85
86     @VisibleForTesting
87     static DefaultSchemaTreeInference verifiedOf(final EffectiveModelContext modelContext,
88             final ImmutableList<? extends SchemaTreeEffectiveStatement<?>> path) {
89         final var resolved = resolveSteps(modelContext, Lists.transform(path, SchemaTreeEffectiveStatement::argument));
90         checkArgument(path.equals(resolved), "Provided path %s is not consistent with resolved path %s", path,
91             resolved);
92         return new DefaultSchemaTreeInference(modelContext, path);
93     }
94
95     private static ImmutableList<SchemaTreeEffectiveStatement<?>> resolveSteps(final EffectiveModelContext modelContext,
96             final List<QName> steps) {
97         final var first = steps.get(0);
98         final var module = modelContext.findModuleStatement(first.getModule()).orElseThrow(
99             () -> new IllegalArgumentException("No module for " + first));
100
101         final var builder = ImmutableList.<SchemaTreeEffectiveStatement<?>>builderWithExpectedSize(steps.size());
102         SchemaTreeAwareEffectiveStatement<?, ?> parent = module;
103         final Iterator<QName> it = steps.iterator();
104         while (true) {
105             final var qname = it.next();
106             final var found = parent.findSchemaTreeNode(qname).orElseThrow(
107                 () -> new IllegalArgumentException("Cannot resolve step " + qname + " in " + builder.build()));
108             builder.add(found);
109
110             if (!it.hasNext()) {
111                 break;
112             }
113
114             checkArgument(found instanceof SchemaTreeAwareEffectiveStatement, "Cannot resolve steps %s past %s", steps,
115                 found);
116             parent = (SchemaTreeAwareEffectiveStatement<?, ?>) found;
117         }
118
119         return builder.build();
120     }
121 }