001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.util;
030
031import static java.util.Objects.requireNonNull;
032
033import org.python.core.Py;
034import org.python.core.PyDictProxy;
035import org.python.core.PyDictionary;
036import org.python.core.PyDictionaryDerived;
037import org.python.core.PyObject;
038import org.python.core.PyString;
039import org.python.core.PyTuple;
040import ucar.unidata.idv.IdvObjectStore;
041
042import edu.wisc.ssec.mcidasv.McIDASV;
043
044import java.util.ArrayList;
045import java.util.HashMap;
046import java.util.LinkedHashMap;
047import java.util.List;
048import java.util.Map;
049import java.util.Set;
050import java.util.TreeSet;
051
052/**
053 * Wraps the application's {@link ucar.unidata.idv.IdvObjectStore} object and
054 * provides methods that are safe to use from Jython scripts.
055 *
056 * <p>A secondary aim of this class is to be largely API-compatible with
057 * {@code java.util.prefs.Preferences}.</p>
058 */
059public class JythonObjectStore {
060
061    /** {@code IdvObjectStore} used by the current McIDAS-V session. */
062    private final IdvObjectStore idvStore;
063
064    /**
065     * Return a new {@code JythonObjectStore} instance.
066     *
067     * <p>Use this method rather than the constructor.</p>
068     *
069     * @param mcidasv McIDAS-V instance that represents current session. Cannot
070     * be {@code null}.
071     *
072     * @return New instance of the {@code JythonObjectStore} class.
073     *
074     * @throws NullPointerException if {@code mcidasv} is {@code null}.
075     */
076    public static JythonObjectStore newInstance(final McIDASV mcidasv) {
077        return new JythonObjectStore(requireNonNull(mcidasv.getStore()));
078    }
079
080    /**
081     * Create a new {@code JythonObjectStore} wrapper object.
082     *
083     * @param store McIDAS-V object store. Cannot be {@code null}.
084     *
085     * @throws NullPointerException if {@code store} is {@code null}.
086     */
087    private JythonObjectStore(IdvObjectStore store) {
088        idvStore = requireNonNull(store);
089    }
090
091    /**
092     * Removes the value associated with the given {@code key} (if any).
093     *
094     * @param key Key whose associated value is to be removed. Cannot be
095     * {@code null}.
096     *
097     * @throws NullPointerException if {@code key} is {@code null}.
098     */
099    public void remove(String key) {
100        idvStore.remove(requireNonNull(key));
101    }
102
103    /**
104     * Returns the object associated with the given {@code key}. If {@code key}
105     * does not exist, {@code defaultValue} is returned.
106     *
107     * @param <T> Type of object that will be returned.
108     * @param key Key whose associated object is to be returned.
109     * Cannot be {@code null}.
110     * @param defaultValue Value to be returned if {@code key} is not valid.
111     * {@code null} is allowed.
112     *
113     * @return Object associated with {@code key} or {@code defaultValue} if
114     * {@code key} is not valid.
115     *
116     * @throws NullPointerException if {@code key} is {@code null}.
117     */
118    public <T> T getObject(String key, T defaultValue) {
119        T storedValue = (T)idvStore.get(requireNonNull(key));
120        if (storedValue == null) {
121            storedValue = defaultValue;
122        }
123        return storedValue;
124    }
125
126    /**
127     * Returns the {@code short} value associated with the given {@code key}.
128     * If {@code key} does not exist, {@code defaultValue} is returned.
129     *
130     * @param key Key whose associated {@code short} value is to be returned.
131     * Cannot be {@code null}.
132     * @param defaultValue Value to be returned if {@code key} is not valid.
133     *
134     * @return {@code short} value associated with {@code key} or
135     * {@code defaultValue} if {@code key} is not valid.
136     *
137     * @throws NullPointerException if {@code key} is {@code null}.
138     */
139    public short getShort(String key, short defaultValue) {
140        return idvStore.get(requireNonNull(key), defaultValue);
141    }
142
143    /**
144     * Returns the {@code char} value associated with the given {@code key}.
145     * If {@code key} does not exist, {@code defaultValue} is returned.
146     *
147     * @param key Key whose associated {@code char} value is to be returned.
148     * Cannot be {@code null}.
149     * @param defaultValue Value to be returned if {@code key} is not valid.
150     *
151     * @return {@code char} value associated with {@code key} or
152     * {@code defaultValue} if {@code key} is not valid.
153     *
154     * @throws NullPointerException if {@code key} is {@code null}.
155     */
156    public char getCharacter(String key, char defaultValue) {
157        return idvStore.get(requireNonNull(key), defaultValue);
158    }
159
160    /**
161     * Returns the {@code String} value associated with the given {@code key}.
162     * If {@code key} does not exist, {@code defaultValue} is returned.
163     *
164     * @param key Key whose associated {@code String} value is to be returned.
165     * Cannot be {@code null}.
166     * @param defaultValue Value to be returned if {@code key} is not valid.
167     *
168     * @return {@code String} value associated with {@code key} or
169     * {@code defaultValue} if {@code key} is not valid.
170     *
171     * @throws NullPointerException if {@code key} is {@code null}.
172     */
173    public String getString(String key, String defaultValue) {
174        return idvStore.get(requireNonNull(key), defaultValue);
175    }
176
177    /**
178     * Returns the {@code boolean} value associated with the given {@code key}.
179     * If {@code key} does not exist, {@code defaultValue} is returned.
180     *
181     * @param key Key whose associated {@code boolean} value is to be returned.
182     * Cannot be {@code null}.
183     * @param defaultValue Value to be returned if {@code key} is not valid.
184     *
185     * @return {@code boolean} value associated with {@code key} or
186     * {@code defaultValue} if {@code key} is not valid.
187     *
188     * @throws NullPointerException if {@code key} is {@code null}.
189     */
190    public boolean getBoolean(String key, boolean defaultValue) {
191        return idvStore.get(requireNonNull(key), defaultValue);
192    }
193
194    /**
195     * Returns the {@code double} value associated with the given {@code key}.
196     * If {@code key} does not exist, {@code defaultValue} is returned.
197     *
198     * @param key Key whose associated {@code double} value is to be returned.
199     * Cannot be {@code null}.
200     * @param defaultValue Value to be returned if {@code key} is not valid.
201     *
202     * @return {@code double} value associated with {@code key} or
203     * {@code defaultValue} if {@code key} is not valid.
204     *
205     * @throws NullPointerException if {@code key} is {@code null}.
206     */
207    public double getDouble(String key, double defaultValue) {
208        return idvStore.get(requireNonNull(key), defaultValue);
209    }
210
211    /**
212     * Returns the {@code float} value associated with the given {@code key}.
213     * If {@code key} does not exist, {@code defaultValue} is returned.
214     *
215     * @param key Key whose associated {@code float} value is to be returned.
216     * Cannot be {@code null}.
217     * @param defaultValue Value to be returned if {@code key} is not valid.
218     *
219     * @return {@code float} value associated with {@code key} or
220     * {@code defaultValue} if {@code key} is not valid.
221     *
222     * @throws NullPointerException if {@code key} is {@code null}.
223     */
224    public float getFloat(String key, float defaultValue) {
225        return idvStore.get(requireNonNull(key), defaultValue);
226    }
227
228    /**
229     * Returns the {@code int} value associated with the given {@code key}.
230     * If {@code key} does not exist, {@code defaultValue} is returned.
231     *
232     * @param key Key whose associated {@code int} value is to be returned.
233     * Cannot be {@code null}.
234     * @param defaultValue Value to be returned if {@code key} is not valid.
235     *
236     * @return {@code int} value associated with {@code key} or
237     * {@code defaultValue} if {@code key} is not valid.
238     *
239     * @throws NullPointerException if {@code key} is {@code null}.
240     */
241    public int getInteger(String key, int defaultValue) {
242        return idvStore.get(requireNonNull(key), defaultValue);
243    }
244
245    /**
246     * Returns the {@code long} value associated with the given {@code key}.
247     * If {@code key} does not exist, {@code defaultValue} is returned.
248     *
249     * @param key Key whose associated {@code long} value is to be returned.
250     * Cannot be {@code null}.
251     * @param defaultValue Value to be returned if {@code key} is not valid.
252     *
253     * @return {@code long} value associated with {@code key} or
254     * {@code defaultValue} if {@code key} is not valid.
255     *
256     * @throws NullPointerException if {@code key} is {@code null}.
257     */
258    public long getLong(String key, long defaultValue) {
259        return idvStore.get(requireNonNull(key), defaultValue);
260    }
261
262    /**
263     * Associates the given {@code key} with the given object.
264     *
265     * @param <T> Type of object to store.
266     * @param key Key to associate with the given {@code value}.
267     * Cannot be {@code null}.
268     * @param value Object to associate with {@code key}. Cannot be
269     * {@code null}.
270     *
271     * @throws NullPointerException if either {@code key} or {@code value} is
272     * {@code null}.
273     */
274    public <T> void putObject(String key, T value) {
275        idvStore.put(requireNonNull(key), requireNonNull(value));
276    }
277
278    /**
279     * Associates the given {@code key} with the given {@code short} value.
280     *
281     * @param key Key to associate with the given {@code value}.
282     * Cannot be {@code null}.
283     * @param value {@code short} value to associate with {@code key}.
284     *
285     * @throws NullPointerException if either {@code key} or {@code value} is
286     * {@code null}.
287     */
288    public void putShort(String key, short value) {
289        idvStore.put(requireNonNull(key), value);
290    }
291
292    /**
293     * Associates the given {@code key} with the given {@code char} value.
294     *
295     * @param key Key to associate with the given {@code value}.
296     * Cannot be {@code null}.
297     * @param value {@code char} value to associate with {@code key}.
298     *
299     * @throws NullPointerException if either {@code key} or {@code value} is
300     * {@code null}.
301     */
302    public void putCharacter(String key, char value) {
303        idvStore.put(requireNonNull(key), value);
304    }
305
306    /**
307     * Associates the given {@code key} with the given {@code String} value.
308     *
309     * @param key Key to associate with the given {@code value}.
310     * Cannot be {@code null}.
311     * @param value {@code String} value to associate with {@code key}.
312     * Cannot be {@code null}.
313     *
314     * @throws NullPointerException if either {@code key} or {@code value} is
315     * {@code null}.
316     */
317    public void putString(String key, String value) {
318        idvStore.put(requireNonNull(key), requireNonNull(value));
319    }
320
321    /**
322     * Associates the given {@code key} with the given {@code boolean} value.
323     *
324     * @param key Key to associate with the given {@code value}.
325     * Cannot be {@code null}.
326     * @param value {@code boolean} value to associate with {@code key}.
327     *
328     * @throws NullPointerException if either {@code key} or {@code value} is
329     * {@code null}.
330     */
331    public void putBoolean(String key, boolean value) {
332        idvStore.put(requireNonNull(key), value);
333    }
334
335    /**
336     * Associates the given {@code key} with the given {@code double} value.
337     *
338     * @param key Key to associate with the given {@code value}.
339     * Cannot be {@code null}.
340     * @param value {@code double} value to associate with {@code key}.
341     *
342     * @throws NullPointerException if either {@code key} or {@code value} is
343     * {@code null}.
344     */
345    public void putDouble(String key, double value) {
346        idvStore.put(requireNonNull(key), value);
347    }
348
349    /**
350     * Associates the given {@code key} with the given {@code float} value.
351     *
352     * @param key Key to associate with the given {@code value}.
353     * Cannot be {@code null}.
354     * @param value {@code float} value to associate with {@code key}.
355     *
356     * @throws NullPointerException if either {@code key} or {@code value} is
357     * {@code null}.
358     */
359    public void putFloat(String key, float value) {
360        idvStore.put(requireNonNull(key), value);
361    }
362
363    /**
364     * Associates the given {@code key} with the given {@code int} value.
365     *
366     * @param key Key to associate with the given {@code value}.
367     * Cannot be {@code null}.
368     * @param value {@code int} value to associate with {@code key}.
369     *
370     * @throws NullPointerException if either {@code key} or {@code value} is
371     * {@code null}.
372     */
373    public void putInteger(String key, int value) {
374        idvStore.put(requireNonNull(key), value);
375    }
376
377    /**
378     * Associates the given {@code key} with the given {@code long} value.
379     *
380     * @param key Key to associate with the given {@code value}.
381     * Cannot be {@code null}.
382     * @param value {@code long} value to associate with {@code key}.
383     *
384     * @throws NullPointerException if either {@code key} or {@code value} is
385     * {@code null}.
386     */
387    public void putLong(String key, long value) {
388        idvStore.put(requireNonNull(key), value);
389    }
390
391
392    public Set<String> keys() {
393        // yeah, i don't like forwarding stuff like this either. but remember,
394        // the intent is to eventually move away from the IDV object store
395        // altogether (so this kinda redundant call may eventually go away).
396        return idvStore.getKeys();
397    }
398
399
400    public Set<String> keys(String substring) {
401        Set<String> allKeys = idvStore.getKeys();
402        Set<String> matches = new TreeSet<>();
403        String lowerCase = substring.toLowerCase();
404        for (String key : allKeys) {
405            if (key.toLowerCase().contains(lowerCase)) {
406                matches.add(key);
407            }
408        }
409        return matches;
410    }
411
412    public List<PyTuple> items() {
413        Map<String, Object> table = idvStore.getTable();
414        List<PyTuple> l = new ArrayList<>(table.size());
415        for (Map.Entry<String, Object> entry : table.entrySet()) {
416            l.add(new PyTuple(new PyString(entry.getKey()), Py.java2py(entry.getValue())));
417        }
418        return l;
419    }
420
421    public List<PyTuple> items(String substring) {
422        Map<String, Object> allItems = idvStore.getTable();
423        List<PyTuple> l = new ArrayList<>(allItems.size());
424        String lowerCase = substring.toLowerCase();
425        for (Map.Entry<String, Object> entry : allItems.entrySet()) {
426            String key = entry.getKey();
427            if (key.toLowerCase().contains(lowerCase)) {
428                l.add(new PyTuple(new PyString(key), Py.java2py(entry.getValue())));
429
430            }
431        }
432        return l;
433    }
434
435    public String listKeys() {
436        Set<String> keys = idvStore.getKeys();
437        // 128 is just a guess as to the typical key length
438        StringBuilder s = new StringBuilder(keys.size() * 128);
439        for (String key : keys) {
440            s.append('"').append(key).append('"').append('\n');
441        }
442        return s.toString();
443    }
444
445    public String listMatchingKeys(String substring) {
446        Set<String> keys = idvStore.getKeys();
447        StringBuilder s = new StringBuilder(keys.size() * 128);
448        String lowerCase = substring.toLowerCase();
449        for (String key : keys) {
450            if (key.toLowerCase().contains(lowerCase)) {
451                s.append('"').append(key).append('"').append('\n');
452            }
453        }
454        return s.toString();
455    }
456
457    public String listItems() {
458        Set<String> keys = idvStore.getKeys();
459        Map<String, Object> table = idvStore.getTable();
460        StringBuilder s = new StringBuilder(keys.size() * 512);
461        for (String key : keys) {
462            Object value = table.get(key);
463            String type = value.getClass().getName();
464            s.append('"').append(key).append("\", ").append(type).append(':').append(value).append('\n');
465        }
466        return s.toString();
467    }
468
469    public String listMatchingItems(String substring) {
470        Set<String> keys = idvStore.getKeys();
471        Map<String, Object> table = idvStore.getTable();
472        String lowerCase = substring.toLowerCase();
473        StringBuilder s = new StringBuilder(keys.size() * 512);
474        for (String key : keys) {
475            if (key.toLowerCase().contains(lowerCase)) {
476                Object value = table.get(key);
477                String type = value.getClass().getName();
478                s.append('"').append(key).append("\", ").append(type).append(':').append(value).append('\n');
479            }
480        }
481        return s.toString();
482    }
483}