1 /* Copyright 2002-2026 Airbus Defence and Space
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * Airbus Defence and Space licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.utils;
18
19 import java.io.Serial;
20 import java.io.Serializable;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28
29 /** String → Object mapping, for small number of keys.
30 * <p>
31 * This class is a low overhead for a very small number of keys.
32 * It is based on simple array and string comparison. It plays
33 * the same role a {@code Map<String, Object>} but with reduced
34 * features and not intended for large number of keys. For such
35 * needs the regular {@code Map<String, Object>} should be preferred.
36 * </p>
37 *
38 * @see DoubleArrayDictionary
39 * @author Anne-Laure Lugan
40 * @since 13.0
41 */
42 public class DataDictionary implements Serializable {
43
44 /** Serializable UID. */
45 @Serial
46 private static final long serialVersionUID = 20250208L;
47
48 /** Default capacity. */
49 private static final int DEFAULT_INITIAL_CAPACITY = 4;
50
51 /** Data container. */
52 private final List<Entry> data;
53
54 /** Constructor with {@link #DEFAULT_INITIAL_CAPACITY default initial capacity}.
55 */
56 public DataDictionary() {
57 this(DEFAULT_INITIAL_CAPACITY);
58 }
59
60 /** Constructor with specified capacity.
61 * @param initialCapacity initial capacity
62 */
63 public DataDictionary(final int initialCapacity) {
64 data = new ArrayList<>(initialCapacity);
65 }
66
67 /** Constructor from another dictionary.
68 * @param dictionary dictionary to use for initializing entries
69 */
70 public DataDictionary(final DataDictionary dictionary) {
71 // take care to call dictionary.getData() and not use dictionary.data,
72 // otherwise we get an empty dictionary when using a DoubleArrayDictionary.view
73 this(DEFAULT_INITIAL_CAPACITY + dictionary.getData().size());
74 for (final Entry entry : dictionary.getData()) {
75 // we don't call put(key, value) to avoid the overhead of the unneeded call to remove(key)
76 data.add(new Entry(entry.getKey(), entry.getValue()));
77 }
78 }
79
80 /** Creates a double values dictionary.
81 * <p>
82 * Creates a DoubleArrayDictionary with all double[] values
83 * contained in the instance.
84 * </p>
85 * @return a double values dictionary
86 */
87 public DoubleArrayDictionary toDoubleDictionary() {
88 final DoubleArrayDictionary dictionary = new DoubleArrayDictionary();
89 for (final Entry entry : data) {
90 if (entry.getValue() instanceof double[]) {
91 dictionary.put(entry.getKey(), (double[]) entry.getValue());
92 }
93 }
94 return dictionary;
95 }
96
97 /**
98 * Get an unmodifiable view of the dictionary entries.
99 *
100 * @return unmodifiable view of the dictionary entries
101 */
102 public List<Entry> getData() {
103 return Collections.unmodifiableList(data);
104 }
105
106 /** Get the number of dictionary entries.
107 * @return number of dictionary entries
108 */
109 public int size() {
110 return data.size();
111 }
112
113 /** Create a map from the instance.
114 * <p>
115 * The map contains a copy of the instance data
116 * </p>
117 * @return copy of the dictionary, as an independent map
118 */
119 public Map<String, Object> toMap() {
120 final Map<String, Object> map = new HashMap<>(data.size());
121 for (final Entry entry : data) {
122 map.put(entry.getKey(), entry.getValue());
123 }
124 return map;
125 }
126
127 /** Remove all entries.
128 */
129 public void clear() {
130 data.clear();
131 }
132
133 /** Add an entry.
134 * <p>
135 * If an entry with the same key already exists, it will be removed first.
136 * </p>
137 * <p>
138 * The new entry is always put at the end.
139 * </p>
140 * @param key entry key
141 * @param value entry value
142 */
143 public void put(final String key, final Object value) {
144 remove(key);
145 data.add(new Entry(key, value));
146 }
147
148 /** Put all the double[] entries from the map in the dictionary.
149 * @param map map to copy into the instance
150 */
151 public void putAllDoubles(final Map<String, double[]> map) {
152 for (final Map.Entry<String, double[]> entry : map.entrySet()) {
153 put(entry.getKey(), entry.getValue());
154 }
155 }
156
157 /** Put all the entries from another dictionary.
158 * @param dictionary dictionary to copy into the instance
159 */
160 public void putAll(final DataDictionary dictionary) {
161 for (final Entry entry : dictionary.data) {
162 put(entry.getKey(), entry.getValue());
163 }
164 }
165
166 /** Get the value corresponding to a key.
167 * @param key entry key
168 * @return copy of the value corresponding to the key or null if key not present
169 */
170 public Object get(final String key) {
171 final Entry entry = getEntry(key);
172 return entry == null ? null : entry.getValue();
173 }
174
175
176 /** Get a complete entry.
177 * @param key entry key
178 * @return entry with key if it exists, null otherwise
179 */
180 public Entry getEntry(final String key) {
181 for (final Entry entry : data) {
182 if (entry.getKey().equals(key)) {
183 return entry;
184 }
185 }
186 return null;
187 }
188
189 /** Remove an entry.
190 * @param key key of the entry to remove
191 * @return true if an entry has been removed, false if the key was not present
192 */
193 public boolean remove(final String key) {
194 final Iterator<Entry> iterator = data.iterator();
195 while (iterator.hasNext()) {
196 if (iterator.next().getKey().equals(key)) {
197 iterator.remove();
198 return true;
199 }
200 }
201 return false;
202 }
203
204 /** Get a string representation of the dictionary.
205 * <p>
206 * This string representation is intended for improving displays in debuggers only.
207 * </p>
208 * @param values dictionary
209 * @return string representation of the dictionary
210 */
211 public static String toString(final Map<String, Object> values) {
212 final StringBuilder builder = new StringBuilder();
213 builder.append('{');
214 int i = 0;
215 for (Map.Entry<String, Object> entry : values.entrySet()) {
216 if (i > 0) {
217 builder.append(", ");
218 }
219 builder.append(entry.getKey());
220 builder.append('[');
221 if (entry.getValue() instanceof double[]) {
222 builder.append(((double[]) entry.getValue()).length);
223 } else {
224 builder.append(entry.getValue());
225 }
226 builder.append(']');
227 i++;
228 }
229 builder.append('}');
230 return builder.toString();
231 }
232
233 /** Get a string representation of the dictionary.
234 * <p>
235 * This string representation is intended for improving displays in debuggers only.
236 * </p>
237 * @return string representation of the dictionary
238 */
239 @Override
240 public String toString() {
241 return toString(toMap());
242 }
243
244 /** Entry in a dictionary. */
245 public static class Entry implements Serializable {
246
247 /** Serializable UID. */
248 @Serial
249 private static final long serialVersionUID = 20250208L;
250
251 /** Key. */
252 private final String key;
253
254 /** Value. */
255 private final Object value;
256
257 /** Simple constructor.
258 * @param key key
259 * @param value value
260 */
261 Entry(final String key, final Object value) {
262 this.key = key;
263 this.value = value;
264 }
265
266 /** Get the entry key.
267 * @return entry key
268 */
269 public String getKey() {
270 return key;
271 }
272
273 /** Get the value.
274 * @return a copy of the value (independent from internal array if it is a double array)
275 */
276 public Object getValue() {
277 return value instanceof double[] ds ? ds.clone() : value;
278 }
279
280 /** Increment the value if it is a double array.
281 * <p>
282 * For the sake of performance, no checks are done on argument.
283 * </p>
284 * @param increment increment to apply to the entry value
285 */
286 public void increment(final double[] increment) {
287 if (value instanceof double[] doubles) {
288 for (int i = 0; i < increment.length; ++i) {
289 doubles[i] += increment[i];
290 }
291 }
292 }
293
294 /** Increment the value with another scaled entry if it is a double array.
295 * <p>
296 * Each component {@code value[i]} will be replaced by {@code value[i] + factor * raw.value[i]}.
297 * </p>
298 * <p>
299 * For the sake of performance, no checks are done on arguments.
300 * </p>
301 * @param factor multiplicative factor for increment
302 * @param raw raw increment to be multiplied by {@code factor} and then added
303 */
304 public void scaledIncrement(final double factor, final DoubleArrayDictionary.Entry raw) {
305 if (value instanceof double[] doubles) {
306 for (int i = 0; i < raw.getValue().length; ++i) {
307 doubles[i] += factor * raw.getValue()[i];
308 }
309 }
310 }
311
312 /** Reset the value to zero if it is a double array.
313 */
314 public void zero() {
315 if (value instanceof double[] doubles) {
316 Arrays.fill(doubles, 0.0);
317 }
318 }
319 }
320 }