/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.io;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
/**
* <p>Simple map like class to store application and Codename One preference
* settings in the {@link com.codename1.io.Storage}. <br>
* Simple usage of the class for storing a {@code String} token:</p>
*
* <script src="https://gist.github.com/codenameone/fc7693ef69108e90057c.js"></script>
*
* <p>
* Notice that this class might get somewhat confusing with primitive numbers e.g. if you use
* {@code Preferences.set("primitiveLongValue", myLongNumber)} then invoke
* {@code Preferences.get("primitiveLongValue", 0)} you might get an exception!<br>
* This would happen because the value is physically a {@code Long} object but you are trying to get an
* {@code Integer}. <br>
* The workaround is to remain consistent and use code like this {@code Preferences.get("primitiveLongValue", (long)0)}.
* </p>
*
* @author Shai Almog
* @author Miguel Mu\u00f1oz
*/
public class Preferences {
private static Hashtable<String, Object> p;
private static final HashMap<String, ArrayList<PreferenceListener>> listenerMap = new HashMap<String, ArrayList<PreferenceListener>>();
private static String preferencesLocation = "CN1Preferences";
/**
* Block instantiation of preferences
*/
Preferences() {}
/**
* Sets the location within the storage of the preferences file to an arbitrary name. This is useful in a case
* of encryption where we would want preferences to use a different file name.
*
* @param storageFileName the name of the preferences file
*/
public static void setPreferencesLocation(String storageFileName) {
preferencesLocation = storageFileName;
p = null;
}
/**
* Returns the location within the storage of the preferences file to an arbitrary name. This is useful in a case
* of encryption where we would want preferences to use a different file name.
* @return the storage file name
*/
public static String getPreferencesLocation() {
return preferencesLocation;
}
private static Hashtable<String, Object> get() {
if(p == null) {
if(Storage.getInstance().exists(preferencesLocation)) {
p = (Hashtable<String, Object>)Storage.getInstance().readObject(preferencesLocation);
}
if(p == null) {
p = new Hashtable<String, Object>();
}
}
return p;
}
private static void save() {
Storage.getInstance().writeObject(preferencesLocation, p);
}
/**
* Sets a preference value, supported values are Strings, numbers and boolean
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param o a String a number or boolean
*/
private static void set(String pref, Object o) {
Object prior = get(pref, null);
if(o == null) {
get().remove(pref);
} else {
get().put(pref, o);
}
save();
fireChange(pref, prior, o);
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param s a String
*/
public static void set(String pref, String s) {
set(pref, (Object)s);
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param i a number
*/
public static void set(String pref, int i) {
set(pref, new Integer(i));
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param l a number
*/
public static void set(String pref, long l) {
set(pref, new Long(l));
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param d a number
*/
public static void set(String pref, double d) {
set(pref, new Double(d));
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param f a number
*/
public static void set(String pref, float f) {
set(pref, new Float(f));
}
/**
* Deletes a value for the given setting
*
* @param pref the preference value
*/
public static void delete(String pref) {
Object prior = get(pref, null);
get().remove(pref);
save();
fireChange(pref, prior, null);
}
/**
* Remove all preferences
*/
public static void clearAll() {
// We only need to save prior values for Preferences that actually have listeners.
Hashtable<String, Object> priorValues = null;
if (!listenerMap.keySet().isEmpty()) {
// Save all the Preferences for which there are registered listeners.
priorValues = new Hashtable<String, Object>();
for (String key : listenerMap.keySet()) {
final Object currentValue = get().get(key);
// We can't put null values in the hashtable. But we don't need to, because if we could we'd just be calling
// fireChange(Pref, null, null) and fireChange would do nothing.
if (currentValue != null) {
priorValues.put(key, currentValue);
}
}
}
get().clear();
save();
if (priorValues != null) {
for (String key : listenerMap.keySet()) {
fireChange(key, priorValues.get(key), null);
}
}
}
static Set<String> keySet() {
return p.keySet();
}
/**
* Sets a preference value
*
* @param pref the key any unique none null value that doesn't start with cn1
* @param b the value
*/
public static void set(String pref, boolean b) {
if(b) {
set(pref, Boolean.TRUE);
} else {
set(pref, Boolean.FALSE);
}
}
/**
* Gets the value as a String
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static String get(String pref, String def) {
Object t = get().get(pref);
if(t == null) {
return def;
}
return t.toString();
}
/**
* Gets the value as a number
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static int get(String pref, int def) {
Integer t = (Integer)get().get(pref);
if(t == null) {
return def;
}
return t.intValue();
}
/**
* Gets the value as a number
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static long get(String pref, long def) {
Long t = (Long)get().get(pref);
if(t == null) {
return def;
}
return t.longValue();
}
/**
* Gets the value as a number
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static double get(String pref, double def) {
Double t = (Double)get().get(pref);
if(t == null) {
return def;
}
return t.doubleValue();
}
/**
* Gets the value as a number
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static float get(String pref, float def) {
Float t = (Float)get().get(pref);
if(t == null) {
return def;
}
return t.floatValue();
}
/**
* Gets the value as a number
* @param pref the preference key
* @param def the default value
* @return the default value or the value
*/
public static boolean get(String pref, boolean def) {
Boolean t = (Boolean)get().get(pref);
if(t == null) {
return def;
}
return t.booleanValue();
}
/**
* Fires the PreferenceListeners if priorValue and value are not equal.
*
* @param pref The preference name
* @param priorValue The prior value, which may be null
* @param value The new value, which may be null
*/
private static void fireChange(final String pref, final Object priorValue, final Object value) {
//noinspection EqualsReplaceableByObjectsCall,ObjectEquality
boolean valueChanged = (priorValue != value) && ((priorValue == null) || !priorValue.equals(value));
if(valueChanged) {
ArrayList<PreferenceListener> listenerList = listenerMap.get(pref);
if(listenerList != null) {
// Loop backwards, in case the listener removes itself.
for(int i = listenerList.size() - 1; i >= 0; --i) {
// either value could be null
PreferenceListener listener = listenerList.get(i);
listener.preferenceChanged(pref, priorValue, value);
}
}
}
}
/**
* Adds a preference listener for the specified property to the list of listeners. When calling this method, it is
* advisable to also read the current value and set it, since the value may have changed since the last time the
* listener was removed. (Should this return the current value of the preference?)
*
* @param pref The preference to listen to
* @param listener The listener to add, which cannot be null.
*/
public static void addPreferenceListener(String pref, PreferenceListener listener) {
if(listener == null) {
// fail fast. Without this, it will fail when the listener is fired, when it's harder to trace back.
throw new NullPointerException("Null PreferenceListener not allowed");
}
ArrayList<PreferenceListener> listenerList = listenerMap.get(pref);
if(listenerList == null) {
listenerList = new ArrayList<PreferenceListener>();
listenerMap.put(pref, listenerList);
}
listenerList.add(listener);
}
/**
* Remove the listener for the specified preference.
*
* @param pref The preference that the listener listens to
* @param listener The listener to remove
* @return true if the listener was removed, false if it was not found.
*/
public static boolean removePreferenceListener(String pref, PreferenceListener listener) {
ArrayList<PreferenceListener> listenerList = listenerMap.get(pref);
if(listenerList != null) {
return listenerList.remove(listener);
}
return false;
}
}