1   /* Copyright 2002-2025 Bryan Cazabonne
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    * Bryan Cazabonne 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 org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  
22  import java.util.ArrayList;
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 FieldArrayDictionary
39   * @author Bryan Cazabonne
40   * @since 13.0
41   */
42  public class FieldDataDictionary<T extends CalculusFieldElement<T>> {
43  
44      /** Default capacity. */
45      private static final int DEFAULT_INITIAL_CAPACITY = 4;
46  
47      /** Field to which elements belong. */
48      private final Field<T> field;
49  
50      /** Data container. */
51      private final List<Entry> data;
52  
53      /** Constructor with {@link #DEFAULT_INITIAL_CAPACITY default initial capacity}.
54       * @param field field to which elements belong
55       */
56      public FieldDataDictionary(final Field<T> field) {
57          this(field, DEFAULT_INITIAL_CAPACITY);
58      }
59  
60      /** Constructor from a map.
61       * @param field field to which the elements belong
62       * @param map map to use for initializing entries
63       */
64      public FieldDataDictionary(final Field<T> field, final Map<String, Object> map) {
65          this(field, map.size());
66          for (final Map.Entry<String, Object> entry : map.entrySet()) {
67              // we don't call put(key, value) to avoid the overhead of the unneeded call to remove(key)
68              data.add(new Entry(entry.getKey(), entry.getValue()));
69          }
70      }
71  
72      /** Constructor from another dictionary.
73       * @param dictionary dictionary to use for initializing entries
74       */
75      public FieldDataDictionary(final FieldDataDictionary<T> dictionary) {
76          // take care to call dictionary.getData() and not use dictionary.data
77          this(dictionary.getField(), DEFAULT_INITIAL_CAPACITY + dictionary.getData().size());
78          for (final Entry entry : dictionary.getData()) {
79              // we don't call put(key, value) to avoid the overhead of the unneeded call to remove(key)
80              data.add(new Entry(entry.getKey(), entry.getValue()));
81          }
82      }
83  
84      /** Constructor with specified capacity.
85       * @param field field to which elements belong
86       * @param initialCapacity initial capacity
87       */
88      public FieldDataDictionary(final Field<T> field, final int initialCapacity) {
89          this.data = new ArrayList<>(initialCapacity);
90          this.field = field;
91      }
92  
93      /** Creates a "field" values dictionary.
94       * <p>
95       * Creates a DoubleArrayDictionary with all double[] values
96       * contained in the instance.
97       * </p>
98       * @return a double values dictionary
99       */
100     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
101     public FieldArrayDictionary<T> toFieldArrayDictionary() {
102         final FieldArrayDictionary<T> dictionary = new FieldArrayDictionary<>(field);
103         for (final Entry entry : data) {
104             if (entry.getValue() instanceof CalculusFieldElement[]) {
105                 dictionary.put(entry.getKey(), (T[]) entry.getValue());
106             }
107         }
108         return dictionary;
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     /** Get the number of dictionary entries.
132      * @return number of dictionary entries
133      */
134     public int size() {
135         return data.size();
136     }
137 
138     /** Add an entry.
139      * <p>
140      * If an entry with the same key already exists, it will be removed first.
141      * </p>
142      * <p>
143      * The new entry is always put at the end.
144      * </p>
145      * @param key entry key
146      * @param value entry value
147      */
148     public void put(final String key, final Object value) {
149         remove(key);
150         data.add(new Entry(key, value));
151     }
152 
153     /** Put all the T[] entries from the map in the dictionary.
154      * @param map map to copy into the instance
155      */
156     public void putAllFields(final Map<String, T[]> map) {
157         for (final Map.Entry<String,  T[]> entry : map.entrySet()) {
158             put(entry.getKey(), entry.getValue());
159         }
160     }
161 
162 
163     /** Put all the entries from the map in the dictionary.
164      * @param map map to copy into the instance
165      */
166     public void putAll(final Map<String, Object> map) {
167         for (final Map.Entry<String, Object> entry : map.entrySet()) {
168             put(entry.getKey(), entry.getValue());
169         }
170     }
171 
172     /** Put all the entries from another dictionary.
173      * @param dictionary dictionary to copy into the instance
174      */
175     public void putAll(final FieldDataDictionary<T> dictionary) {
176         for (final FieldDataDictionary<T>.Entry entry : dictionary.data) {
177             put(entry.getKey(), entry.getValue());
178         }
179     }
180 
181     /** Get the value corresponding to a key.
182      * @param key entry key
183      * @return copy of the value corresponding to the key or null if key not present
184      */
185     public Object get(final String key) {
186         final FieldDataDictionary<T>.Entry entry = getEntry(key);
187         return entry == null ? null : entry.getValue();
188     }
189 
190     /** Get a complete entry.
191      * @param key entry key
192      * @return entry with key if it exists, null otherwise
193      */
194     public Entry getEntry(final String key) {
195         for (final Entry entry : data) {
196             if (entry.getKey().equals(key)) {
197                 return entry;
198             }
199         }
200         return null;
201     }
202 
203     /** Remove an entry.
204      * @param key key of the entry to remove
205      * @return true if an entry has been removed, false if the key was not present
206      */
207     public boolean remove(final String key) {
208         final Iterator<Entry> iterator = data.iterator();
209         while (iterator.hasNext()) {
210             if (iterator.next().getKey().equals(key)) {
211                 iterator.remove();
212                 return true;
213             }
214         }
215         return false;
216     }
217 
218     /**
219      * Get an unmodifiable view of the dictionary entries.
220      *
221      * @return unmodifiable view of the dictionary entries
222      */
223     public List<Entry> getData() {
224         return Collections.unmodifiableList(data);
225     }
226 
227     /**
228      * Get the field to which elements belong.
229      * @return the field to which elements belong
230      */
231     public Field<T> getField() {
232         return field;
233     }
234 
235     /** Get a string representation of the dictionary.
236      * <p>
237      * This string representation is intended for improving displays in debuggers only.
238      * </p>
239      * @return string representation of the dictionary
240      */
241     @Override
242     public String toString() {
243         return DataDictionary.toString(toMap());
244     }
245 
246     /** Entry in a dictionary. */
247     public class Entry {
248 
249         /** Key. */
250         private final String key;
251 
252         /** Value. */
253         private final Object value;
254 
255         /** Simple constructor.
256          * @param key key
257          * @param value value
258          */
259         Entry(final String key, final Object value) {
260             this.key   = key;
261             this.value = value;
262         }
263 
264         /** Get the entry key.
265          * @return entry key
266          */
267         public String getKey() {
268             return key;
269         }
270 
271         /** Get the value.
272          * @return a copy of the value (independent from internal array if it is a double array)
273          */
274         @SuppressWarnings("unchecked")
275         public Object getValue() {
276             return value instanceof CalculusFieldElement[] ? ((T[]) value).clone() : value;
277         }
278 
279         /** Increment the value with another scaled entry.
280          * <p>
281          * Each component {@code value[i]} will be replaced by {@code value[i] + factor * raw.value[i]}.
282          * </p>
283          * <p>
284          * For the sake of performance, no checks are done on arguments.
285          * </p>
286          * @param factor multiplicative factor for increment
287          * @param raw raw increment to be multiplied by {@code factor} and then added
288          */
289         @SuppressWarnings("unchecked")
290         public void scaledIncrement(final double factor, final FieldArrayDictionary<T>.Entry raw) {
291             if (value instanceof CalculusFieldElement[]) {
292                 for (int i = 0; i < raw.getValue().length; ++i) {
293                     ((T[]) value)[i] = ((T[]) value)[i].add(raw.getValue()[i].multiply(factor));
294                 }
295             }
296         }
297 
298         /** Increment the value with another scaled entry.
299          * <p>
300          * Each component {@code value[i]} will be replaced by {@code value[i] + factor * raw.value[i]}.
301          * </p>
302          * <p>
303          * For the sake of performance, no checks are done on arguments.
304          * </p>
305          * @param factor multiplicative factor for increment
306          * @param raw raw increment to be multiplied by {@code factor} and then added
307          */
308         @SuppressWarnings("unchecked")
309         public void scaledIncrement(final T factor, final FieldArrayDictionary<T>.Entry raw) {
310             if (value instanceof CalculusFieldElement[]) {
311                 for (int i = 0; i < raw.getValue().length; ++i) {
312                     ((T[]) value)[i] = ((T[]) value)[i].add(raw.getValue()[i].multiply(factor));
313                 }
314             }
315         }
316 
317     }
318 
319 }