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