Improve OffsetMapTemplate error reporting
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / ImmutableOffsetMapTemplate.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, 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.util;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.MoreObjects;
13 import com.google.common.collect.ImmutableMap;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.function.BiFunction;
20 import org.eclipse.jdt.annotation.NonNull;
21
22 /**
23  * Template for instantiating {@link ImmutableOffsetMap} instances with a fixed set of keys. The template can then be
24  * used as a factory for instances via using {@link #instantiateTransformed(Map, BiFunction)} or, more efficiently,
25  * using {@link #instantiateWithValues(Object[])} where the argument array has values ordered corresponding to the key
26  * order defined by {@link #keySet()}.
27  *
28  * @param <K> the type of keys maintained by this template
29  */
30 public abstract sealed class ImmutableOffsetMapTemplate<K> extends ImmutableMapTemplate<K> {
31     private static final class Ordered<K> extends ImmutableOffsetMapTemplate<K> {
32         Ordered(final Collection<K> keys) {
33             super(OffsetMapCache.orderedOffsets(keys));
34         }
35
36         @Override
37         <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
38             return new ImmutableOffsetMap.Ordered<>(offsets, objects);
39         }
40     }
41
42     private static final class Unordered<K> extends ImmutableOffsetMapTemplate<K> {
43         Unordered(final Collection<K> keys) {
44             super(OffsetMapCache.unorderedOffsets(keys));
45         }
46
47         @Override
48         <V> @NonNull ImmutableOffsetMap<K, V> createMap(final ImmutableMap<K, Integer> offsets, final V[] objects) {
49             return new ImmutableOffsetMap.Unordered<>(offsets, objects);
50         }
51     }
52
53     private final @NonNull ImmutableMap<K, Integer> offsets;
54
55     private ImmutableOffsetMapTemplate(final ImmutableMap<K, Integer> offsets) {
56         this.offsets = requireNonNull(offsets);
57     }
58
59     /**
60      * Create a template which produces Maps with specified keys, with iteration order matching the iteration order
61      * of {@code keys}. {@link #keySet()} will return these keys in exactly the same order. The resulting map will
62      * retain insertion order through {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
63      *
64      * @param keys Keys in requested iteration order.
65      * @param <K> the type of keys maintained by resulting template
66      * @return A template object.
67      * @throws NullPointerException if {@code keys} or any of its elements is null
68      * @throws IllegalArgumentException if {@code keys} is does not have at least two keys
69      */
70     public static <K> @NonNull ImmutableOffsetMapTemplate<K> ordered(final Collection<K> keys) {
71         checkTwoKeys(keys);
72         return new Ordered<>(keys);
73     }
74
75     /**
76      * Create a template which produces Maps with specified keys, with unconstrained iteration order. Produced maps
77      * will have the iteration order matching the order returned by {@link #keySet()}.  The resulting map will
78      * NOT retain ordering through {@link UnmodifiableMapPhase#toModifiableMap()} transformations.
79      *
80      * @param keys Keys in any iteration order.
81      * @param <K> the type of keys maintained by resulting template
82      * @return A template object.
83      * @throws NullPointerException if {@code keys} or any of its elements is null
84      * @throws IllegalArgumentException if {@code keys} is does not have at least two keys
85      */
86     public static <K> @NonNull ImmutableOffsetMapTemplate<K> unordered(final Collection<K> keys) {
87         checkTwoKeys(keys);
88         return new Unordered<>(keys);
89     }
90
91     @Override
92     public final Set<K> keySet() {
93         return offsets.keySet();
94     }
95
96     @Override
97     public final <T, V> @NonNull ImmutableOffsetMap<K, V> instantiateTransformed(final Map<K, T> fromMap,
98             final BiFunction<K, T, V> valueTransformer) {
99         final int size = offsets.size();
100         checkSize(size, fromMap.size());
101
102         @SuppressWarnings("unchecked")
103         final var objects = (V[]) new Object[size];
104         for (var entry : fromMap.entrySet()) {
105             final var key = requireNonNull(entry.getKey());
106             objects[offsetOf(key)] = transformValue(key, entry.getValue(), valueTransformer);
107         }
108
109         return createMap(offsets, objects);
110     }
111
112     private int offsetOf(final K key) {
113         final var offset = offsets.get(key);
114         if (offset == null) {
115             throw new IllegalArgumentException("Key " + key + " present in input, but not in offsets " + offsets);
116         }
117         return offset;
118     }
119
120     @Override
121     @SafeVarargs
122     public final <V> ImmutableOffsetMap<K, V> instantiateWithValues(final V... values) {
123         checkSize(offsets.size(), values.length);
124         final var copy = values.clone();
125         Arrays.stream(copy).forEach(Objects::requireNonNull);
126         return createMap(offsets, values);
127     }
128
129     @Override
130     public final String toString() {
131         return MoreObjects.toStringHelper(this).add("offsets", offsets).toString();
132     }
133
134     abstract <V> @NonNull ImmutableOffsetMap<K, V> createMap(ImmutableMap<K, Integer> offsets, V[] objects);
135
136     private static void checkTwoKeys(final Collection<?> keys) {
137         final var size = keys.size();
138         if (size < 2) {
139             throw new IllegalArgumentException("Expected at least 2 keys, " + size + " supplied");
140         }
141     }
142 }