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