001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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 http://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 key Key whose associated object is to be returned.
108     * Cannot be {@code null}.
109     * @param defaultValue Value to be returned if {@code key} is not valid.
110     * {@code null} is allowed.
111     *
112     * @return Object associated with {@code key} or {@code defaultValue} if
113     * {@code key} is not valid.
114     *
115     * @throws NullPointerException if {@code key} is {@code null}.
116     */
117    public <T> T getObject(String key, T defaultValue) {
118        T storedValue = (T)idvStore.get(requireNonNull(key));
119        if (storedValue == null) {
120            storedValue = defaultValue;
121        }
122        return storedValue;
123    }
124
125    /**
126     * Returns the {@code short} value associated with the given {@code key}.
127     * If {@code key} does not exist, {@code defaultValue} is returned.
128     *
129     * @param key Key whose associated {@code short} value is to be returned.
130     * Cannot be {@code null}.
131     * @param defaultValue Value to be returned if {@code key} is not valid.
132     *
133     * @return {@code short} value associated with {@code key} or
134     * {@code defaultValue} if {@code key} is not valid.
135     *
136     * @throws NullPointerException if {@code key} is {@code null}.
137     */
138    public short getShort(String key, short defaultValue) {
139        return idvStore.get(requireNonNull(key), defaultValue);
140    }
141
142    /**
143     * Returns the {@code char} value associated with the given {@code key}.
144     * If {@code key} does not exist, {@code defaultValue} is returned.
145     *
146     * @param key Key whose associated {@code char} value is to be returned.
147     * Cannot be {@code null}.
148     * @param defaultValue Value to be returned if {@code key} is not valid.
149     *
150     * @return {@code char} value associated with {@code key} or
151     * {@code defaultValue} if {@code key} is not valid.
152     *
153     * @throws NullPointerException if {@code key} is {@code null}.
154     */
155    public char getCharacter(String key, char defaultValue) {
156        return idvStore.get(requireNonNull(key), defaultValue);
157    }
158
159    /**
160     * Returns the {@code String} value associated with the given {@code key}.
161     * If {@code key} does not exist, {@code defaultValue} is returned.
162     *
163     * @param key Key whose associated {@code String} value is to be returned.
164     * Cannot be {@code null}.
165     * @param defaultValue Value to be returned if {@code key} is not valid.
166     *
167     * @return {@code String} value associated with {@code key} or
168     * {@code defaultValue} if {@code key} is not valid.
169     *
170     * @throws NullPointerException if {@code key} is {@code null}.
171     */
172    public String getString(String key, String defaultValue) {
173        return idvStore.get(requireNonNull(key), defaultValue);
174    }
175
176    /**
177     * Returns the {@code boolean} value associated with the given {@code key}.
178     * If {@code key} does not exist, {@code defaultValue} is returned.
179     *
180     * @param key Key whose associated {@code boolean} value is to be returned.
181     * Cannot be {@code null}.
182     * @param defaultValue Value to be returned if {@code key} is not valid.
183     *
184     * @return {@code boolean} value associated with {@code key} or
185     * {@code defaultValue} if {@code key} is not valid.
186     *
187     * @throws NullPointerException if {@code key} is {@code null}.
188     */
189    public boolean getBoolean(String key, boolean defaultValue) {
190        return idvStore.get(requireNonNull(key), defaultValue);
191    }
192
193    /**
194     * Returns the {@code double} value associated with the given {@code key}.
195     * If {@code key} does not exist, {@code defaultValue} is returned.
196     *
197     * @param key Key whose associated {@code double} value is to be returned.
198     * Cannot be {@code null}.
199     * @param defaultValue Value to be returned if {@code key} is not valid.
200     *
201     * @return {@code double} value associated with {@code key} or
202     * {@code defaultValue} if {@code key} is not valid.
203     *
204     * @throws NullPointerException if {@code key} is {@code null}.
205     */
206    public double getDouble(String key, double defaultValue) {
207        return idvStore.get(requireNonNull(key), defaultValue);
208    }
209
210    /**
211     * Returns the {@code float} value associated with the given {@code key}.
212     * If {@code key} does not exist, {@code defaultValue} is returned.
213     *
214     * @param key Key whose associated {@code float} value is to be returned.
215     * Cannot be {@code null}.
216     * @param defaultValue Value to be returned if {@code key} is not valid.
217     *
218     * @return {@code float} value associated with {@code key} or
219     * {@code defaultValue} if {@code key} is not valid.
220     *
221     * @throws NullPointerException if {@code key} is {@code null}.
222     */
223    public float getFloat(String key, float defaultValue) {
224        return idvStore.get(requireNonNull(key), defaultValue);
225    }
226
227    /**
228     * Returns the {@code int} value associated with the given {@code key}.
229     * If {@code key} does not exist, {@code defaultValue} is returned.
230     *
231     * @param key Key whose associated {@code int} value is to be returned.
232     * Cannot be {@code null}.
233     * @param defaultValue Value to be returned if {@code key} is not valid.
234     *
235     * @return {@code int} value associated with {@code key} or
236     * {@code defaultValue} if {@code key} is not valid.
237     *
238     * @throws NullPointerException if {@code key} is {@code null}.
239     */
240    public int getInteger(String key, int defaultValue) {
241        return idvStore.get(requireNonNull(key), defaultValue);
242    }
243
244    /**
245     * Returns the {@code long} value associated with the given {@code key}.
246     * If {@code key} does not exist, {@code defaultValue} is returned.
247     *
248     * @param key Key whose associated {@code long} value is to be returned.
249     * Cannot be {@code null}.
250     * @param defaultValue Value to be returned if {@code key} is not valid.
251     *
252     * @return {@code long} value associated with {@code key} or
253     * {@code defaultValue} if {@code key} is not valid.
254     *
255     * @throws NullPointerException if {@code key} is {@code null}.
256     */
257    public long getLong(String key, long defaultValue) {
258        return idvStore.get(requireNonNull(key), defaultValue);
259    }
260
261    /**
262     * Associates the given {@code key} with the given object.
263     *
264     * @param key Key to associate with the given {@code value}.
265     * Cannot be {@code null}.
266     * @param value Object to associate with {@code key}. Cannot be
267     * {@code null}.
268     *
269     * @throws NullPointerException if either {@code key} or {@code value} is
270     * {@code null}.
271     */
272    public <T> void putObject(String key, T value) {
273        idvStore.put(requireNonNull(key), requireNonNull(value));
274    }
275
276    /**
277     * Associates the given {@code key} with the given {@code short} value.
278     *
279     * @param key Key to associate with the given {@code value}.
280     * Cannot be {@code null}.
281     * @param value {@code short} value to associate with {@code key}.
282     *
283     * @throws NullPointerException if either {@code key} or {@code value} is
284     * {@code null}.
285     */
286    public void putShort(String key, short value) {
287        idvStore.put(requireNonNull(key), value);
288    }
289
290    /**
291     * Associates the given {@code key} with the given {@code char} value.
292     *
293     * @param key Key to associate with the given {@code value}.
294     * Cannot be {@code null}.
295     * @param value {@code char} value to associate with {@code key}.
296     *
297     * @throws NullPointerException if either {@code key} or {@code value} is
298     * {@code null}.
299     */
300    public void putCharacter(String key, char value) {
301        idvStore.put(requireNonNull(key), value);
302    }
303
304    /**
305     * Associates the given {@code key} with the given {@code String} value.
306     *
307     * @param key Key to associate with the given {@code value}.
308     * Cannot be {@code null}.
309     * @param value {@code String} value to associate with {@code key}.
310     * Cannot be {@code null}.
311     *
312     * @throws NullPointerException if either {@code key} or {@code value} is
313     * {@code null}.
314     */
315    public void putString(String key, String value) {
316        idvStore.put(requireNonNull(key), requireNonNull(value));
317    }
318
319    /**
320     * Associates the given {@code key} with the given {@code boolean} value.
321     *
322     * @param key Key to associate with the given {@code value}.
323     * Cannot be {@code null}.
324     * @param value {@code boolean} value to associate with {@code key}.
325     *
326     * @throws NullPointerException if either {@code key} or {@code value} is
327     * {@code null}.
328     */
329    public void putBoolean(String key, boolean value) {
330        idvStore.put(requireNonNull(key), value);
331    }
332
333    /**
334     * Associates the given {@code key} with the given {@code double} value.
335     *
336     * @param key Key to associate with the given {@code value}.
337     * Cannot be {@code null}.
338     * @param value {@code double} value to associate with {@code key}.
339     *
340     * @throws NullPointerException if either {@code key} or {@code value} is
341     * {@code null}.
342     */
343    public void putDouble(String key, double value) {
344        idvStore.put(requireNonNull(key), value);
345    }
346
347    /**
348     * Associates the given {@code key} with the given {@code float} value.
349     *
350     * @param key Key to associate with the given {@code value}.
351     * Cannot be {@code null}.
352     * @param value {@code float} value to associate with {@code key}.
353     *
354     * @throws NullPointerException if either {@code key} or {@code value} is
355     * {@code null}.
356     */
357    public void putFloat(String key, float value) {
358        idvStore.put(requireNonNull(key), value);
359    }
360
361    /**
362     * Associates the given {@code key} with the given {@code int} value.
363     *
364     * @param key Key to associate with the given {@code value}.
365     * Cannot be {@code null}.
366     * @param value {@code int} value to associate with {@code key}.
367     *
368     * @throws NullPointerException if either {@code key} or {@code value} is
369     * {@code null}.
370     */
371    public void putInteger(String key, int value) {
372        idvStore.put(requireNonNull(key), value);
373    }
374
375    /**
376     * Associates the given {@code key} with the given {@code long} value.
377     *
378     * @param key Key to associate with the given {@code value}.
379     * Cannot be {@code null}.
380     * @param value {@code long} value to associate with {@code key}.
381     *
382     * @throws NullPointerException if either {@code key} or {@code value} is
383     * {@code null}.
384     */
385    public void putLong(String key, long value) {
386        idvStore.put(requireNonNull(key), value);
387    }
388
389
390    public Set<String> keys() {
391        // yeah, i don't like forwarding stuff like this either. but remember,
392        // the intent is to eventually move away from the IDV object store
393        // altogether (so this kinda redundant call may eventually go away).
394        return idvStore.getKeys();
395    }
396
397
398    public Set<String> keys(String substring) {
399        Set<String> allKeys = idvStore.getKeys();
400        Set<String> matches = new TreeSet<>();
401        String lowerCase = substring.toLowerCase();
402        for (String key : allKeys) {
403            if (key.toLowerCase().contains(lowerCase)) {
404                matches.add(key);
405            }
406        }
407        return matches;
408    }
409
410    public List<PyTuple> items() {
411        Map<String, Object> table = idvStore.getTable();
412        List<PyTuple> l = new ArrayList<>(table.size());
413        for (Map.Entry<String, Object> entry : table.entrySet()) {
414            l.add(new PyTuple(new PyString(entry.getKey()), Py.java2py(entry.getValue())));
415        }
416        return l;
417    }
418
419    public List<PyTuple> items(String substring) {
420        Map<String, Object> allItems = idvStore.getTable();
421        List<PyTuple> l = new ArrayList<>(allItems.size());
422        String lowerCase = substring.toLowerCase();
423        for (Map.Entry<String, Object> entry : allItems.entrySet()) {
424            String key = entry.getKey();
425            if (key.toLowerCase().contains(lowerCase)) {
426                l.add(new PyTuple(new PyString(key), Py.java2py(entry.getValue())));
427
428            }
429        }
430        return l;
431    }
432
433    public String listKeys() {
434        Set<String> keys = idvStore.getKeys();
435        // 128 is just a guess as to the typical key length
436        StringBuilder s = new StringBuilder(keys.size() * 128);
437        for (String key : keys) {
438            s.append('"').append(key).append('"').append('\n');
439        }
440        return s.toString();
441    }
442
443    public String listMatchingKeys(String substring) {
444        Set<String> keys = idvStore.getKeys();
445        StringBuilder s = new StringBuilder(keys.size() * 128);
446        String lowerCase = substring.toLowerCase();
447        for (String key : keys) {
448            if (key.toLowerCase().contains(lowerCase)) {
449                s.append('"').append(key).append('"').append('\n');
450            }
451        }
452        return s.toString();
453    }
454
455    public String listItems() {
456        Set<String> keys = idvStore.getKeys();
457        Map<String, Object> table = idvStore.getTable();
458        StringBuilder s = new StringBuilder(keys.size() * 512);
459        for (String key : keys) {
460            Object value = table.get(key);
461            String type = value.getClass().getName();
462            s.append('"').append(key).append("\", ").append(type).append(':').append(value).append('\n');
463        }
464        return s.toString();
465    }
466
467    public String listMatchingItems(String substring) {
468        Set<String> keys = idvStore.getKeys();
469        Map<String, Object> table = idvStore.getTable();
470        String lowerCase = substring.toLowerCase();
471        StringBuilder s = new StringBuilder(keys.size() * 512);
472        for (String key : keys) {
473            if (key.toLowerCase().contains(lowerCase)) {
474                Object value = table.get(key);
475                String type = value.getClass().getName();
476                s.append('"').append(key).append("\", ").append(type).append(':').append(value).append('\n');
477            }
478        }
479        return s.toString();
480    }
481}