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