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 }