14242d095e3f1650103f84fcd002b7d68f23c3a3
[yangtools.git] / data / yang-data-tree-ri / src / main / java / org / opendaylight / yangtools / yang / data / tree / impl / UniqueValidator.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.data.tree.impl;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.base.MoreObjects;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.Maps;
19 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.opendaylight.yangtools.concepts.Immutable;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
30 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
32 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Descendant;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * A validator for a single {@code unique} constraint. This class is further specialized for single- and
38  * multiple-constraint implementations.
39  *
40  * <p>
41  * The basic idea is that for each list entry there is a corresponding value vector of one or more values, each
42  * corresponding to one component of the {@code unique} constraint.
43  */
44 abstract class UniqueValidator<T> implements Immutable {
45     private static final class One extends UniqueValidator<Object> {
46         One(final List<NodeIdentifier> path) {
47             super(encodePath(path));
48         }
49
50         @Override
51         Object extractValues(final Map<List<NodeIdentifier>, Object> valueCache, final DataContainerNode data) {
52             return extractValue(valueCache, data, decodePath(descendants));
53         }
54
55         @Override
56         Map<Descendant, @Nullable Object> indexValues(final Object values) {
57             return Collections.singletonMap(decodeDescendant(descendants), values);
58         }
59     }
60
61     private static final class Many extends UniqueValidator<Set<Object>> {
62         Many(final List<List<NodeIdentifier>> descendantPaths) {
63             super(descendantPaths.stream().map(UniqueValidator::encodePath).collect(ImmutableSet.toImmutableSet()));
64         }
65
66         @Override
67         UniqueValues extractValues(final Map<List<NodeIdentifier>, Object> valueCache, final DataContainerNode data) {
68             return descendants.stream()
69                 .map(obj -> extractValue(valueCache, data, decodePath(obj)))
70                 .collect(UniqueValues.COLLECTOR);
71         }
72
73         @Override
74         Map<Descendant, @Nullable Object> indexValues(final Object values) {
75             final Map<Descendant, @Nullable Object> index = Maps.newHashMapWithExpectedSize(descendants.size());
76             final Iterator<?> it = ((UniqueValues) values).iterator();
77             for (Object obj : descendants) {
78                 verify(index.put(decodeDescendant(obj), it.next()) == null);
79             }
80             return index;
81         }
82     }
83
84     private static final Logger LOG = LoggerFactory.getLogger(UniqueValidator.class);
85
86     final @NonNull T descendants;
87
88     UniqueValidator(final T descendants) {
89         this.descendants = requireNonNull(descendants);
90     }
91
92     static UniqueValidator<?> of(final List<List<NodeIdentifier>> descendants) {
93         return descendants.size() == 1 ? new One(descendants.get(0)) : new Many(descendants);
94     }
95
96     /**
97      * Extract a value vector from a particular child.
98      *
99      * @param valueCache Cache of descendants already looked up
100      * @param data Root data node
101      * @return Value vector
102      */
103     abstract @Nullable Object extractValues(Map<List<NodeIdentifier>, Object> valueCache, DataContainerNode data);
104
105     /**
106      * Index a value vector by associating each value with its corresponding {@link Descendant}.
107      *
108      * @param values Value vector
109      * @return Map of Descandant/value relations
110      */
111     abstract Map<Descendant, @Nullable Object> indexValues(Object values);
112
113     @Override
114     public final String toString() {
115         return MoreObjects.toStringHelper(this).add("paths", descendants).toString();
116     }
117
118     /**
119      * Encode a path for storage. Single-element paths are squashed to their only element. The inverse operation is
120      * {@link #decodePath(Object)}.
121      *
122      * @param path Path to encode
123      * @return Encoded path.
124      */
125     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
126         justification = "https://github.com/spotbugs/spotbugs/issues/811")
127     private static Object encodePath(final List<NodeIdentifier> path) {
128         return path.size() == 1 ? path.get(0) : ImmutableList.copyOf(path);
129     }
130
131     /**
132      * Decode a path from storage. This is the inverse operation to {@link #encodePath(List)}.
133      *
134      * @param obj Encoded path
135      * @return Decoded path
136      */
137     private static @NonNull ImmutableList<NodeIdentifier> decodePath(final Object obj) {
138         return obj instanceof NodeIdentifier ? ImmutableList.of((NodeIdentifier) obj)
139             : (ImmutableList<NodeIdentifier>) obj;
140     }
141
142     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
143         justification = "https://github.com/spotbugs/spotbugs/issues/811")
144     private static @NonNull Descendant decodeDescendant(final Object obj) {
145         return Descendant.of(Collections2.transform(decodePath(obj), NodeIdentifier::getNodeType));
146     }
147
148     /**
149      * Extract the value for a single descendant.
150      *
151      * @param valueCache Cache of descendants already looked up
152      * @param data Root data node
153      * @param path Descendant path
154      * @return Value for the descendant
155      */
156     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
157         justification = "https://github.com/spotbugs/spotbugs/issues/811")
158     private static @Nullable Object extractValue(final Map<List<NodeIdentifier>, Object> valueCache,
159             final DataContainerNode data, final List<NodeIdentifier> path) {
160         return valueCache.computeIfAbsent(path, key -> extractValue(data, key));
161     }
162
163     /**
164      * Extract the value for a single descendant.
165      *
166      * @param data Root data node
167      * @param path Descendant path
168      * @return Value for the descendant
169      */
170     private static @Nullable Object extractValue(final DataContainerNode data, final List<NodeIdentifier> path) {
171         DataContainerNode current = data;
172         final Iterator<NodeIdentifier> it = path.iterator();
173         while (true) {
174             final NodeIdentifier step = it.next();
175             final DataContainerChild next = current.childByArg(step);
176             if (next == null) {
177                 return null;
178             }
179
180             if (!it.hasNext()) {
181                 checkState(next instanceof LeafNode, "Unexpected node %s at %s", next, path);
182                 final Object value = next.body();
183                 LOG.trace("Resolved {} to value {}", path, value);
184                 return value;
185             }
186
187             checkState(next instanceof DataContainerNode, "Unexpected node %s in %s", next, path);
188             current = (DataContainerNode) next;
189         }
190     }
191 }