edu.ku.brc.af.prefs.AppPreferences.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.af.prefs.AppPreferences.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package edu.ku.brc.af.prefs;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Timer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.prefs.BackingStoreException;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import edu.ku.brc.ui.UIHelper;

/**
 * A reference implementation for the preferences/properties system. These are persisted every 30 seconds or whatever the
 * value "java.util.prefs.syncInterval" is set to. They are also saved automatically at shutdown. These is no real need
 * to call flush.
 *
 * @code_status Complete
 *
 * @author rods
 *
 */
public class AppPreferences {
    public enum AppPrefType {
        local, remote, global
    }

    public static final String factoryName = "edu.ku.brc.af.prefs.AppPrefsIOIFace"; //$NON-NLS-1$
    public static final String factoryGlobalName = "edu.ku.brc.af.prefs.AppPrefsIOIFaceGlobal"; //$NON-NLS-1$

    // Static Data Members
    public final static String LOCALFILENAME = "user.properties"; //$NON-NLS-1$

    private static final String NOT_INIT = "AppPrefs have not been initialized!"; //$NON-NLS-1$

    protected static final Logger log = Logger.getLogger(AppPreferences.class);

    protected static AppPreferences instanceRemote = null;
    protected static AppPreferences instanceLocal = null;
    protected static AppPreferences instanceGlobal = null;

    // instanceRemote Data Member
    protected Properties properties = null;
    protected String dirPath;
    protected boolean isChanged = false;
    protected AppPrefType prefType;
    protected String localFileName = null;

    protected String saverClassName = null;
    protected AppPrefsIOIFace appPrefsIO = null;
    protected boolean isEnabled = true;

    protected Hashtable<String, List<AppPrefsChangeListener>> listeners = new Hashtable<String, List<AppPrefsChangeListener>>();

    // Used for Synchronizing the Preferences to the Backend store
    protected static Timer syncTimer = null; // Daemon Thread
    protected static boolean connectedToDB = false;
    protected static AtomicBoolean blockTimer = new AtomicBoolean(false);

    /**
     * Constructor for Remote, Global and Local prefs.
     * @param prefType remote is per user in the DB, global is in the DB, local on local disk
     */
    protected AppPreferences(final AppPrefType prefType) {
        this.prefType = prefType;

        if (prefType == AppPrefType.remote) {
            this.appPrefsIO = createFactoryIO(factoryName);

        } else if (prefType == AppPrefType.global) {
            // Start by checking to see if we have a Remote IO impl

            this.appPrefsIO = createFactoryIO(factoryGlobalName);

        } else {
            this.localFileName = LOCALFILENAME;
            this.appPrefsIO = new AppPrefsDiskIOIImpl();
            this.appPrefsIO.setAppPrefsMgr(this);
        }
    }

    /**
     * @param factoryClassName
     * @param factoryNm
     * @return
     */
    private AppPrefsIOIFace createFactoryIO(final String factoryNm) {
        this.saverClassName = System.getProperty(factoryNm, null);
        if (this.saverClassName == null) {
            throw new InternalError("System Property '" + factoryNm + "' must be set!"); //$NON-NLS-1$ //$NON-NLS-2$
        }

        AppPrefsIOIFace prefIO = null;
        try {
            prefIO = (AppPrefsIOIFace) Class.forName(this.saverClassName).newInstance();
            prefIO.setAppPrefsMgr(this);

        } catch (Exception e) {
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(AppPreferences.class, e);
            InternalError error = new InternalError(
                    "Can't instantiate " + this.saverClassName + " factory for " + factoryNm); //$NON-NLS-1$ //$NON-NLS-2$
            error.initCause(e);
            throw error;
        }
        return prefIO;
    }

    /**
     * 
     */
    public static void setBlockTimer() {
        blockTimer.set(true);
    }

    /**
     * @return
     */
    public static boolean hasRemotePrefs() {
        return instanceRemote != null;
    }

    /**
     * Returns the singleton.
     * @return the singleton
     */
    public static AppPreferences getRemote() {
        if (instanceRemote == null) {
            instanceRemote = new AppPreferences(AppPrefType.remote);
        }
        return instanceRemote;
    }

    /**
     * Returns the singleton.
     * @return the singleton
     */
    public static AppPreferences getGlobalPrefs() {
        if (instanceGlobal == null) {
            instanceGlobal = new AppPreferences(AppPrefType.global);
        }
        return instanceGlobal;
    }

    /**
     * Flushes the values and then terminates the Prefs so a new one can be created.
     * Also, set 'conenctedToDB' to true.
     */
    public static void startup() {
        connectedToDB = true;
    }

    /**
     * Flushes the values and then terminates the Prefs so a new one can be created.
     */
    public static void shutdownPrefs() {
        if (syncTimer != null) {
            connectedToDB = false;
            syncTimer.cancel();
            syncTimer.purge();
            syncTimer = null;
        }
    }

    private static void shutdownPref(final AppPreferences pref) throws BackingStoreException {
        if (pref != null) {
            if (connectedToDB) {
                pref.flush();
            }
            pref.listeners.clear();
            pref.appPrefsIO = null;
        }

    }

    /**
     * Flushes the values and then terminates the Prefs so a new one can be created.
     */
    public static void shutdownRemotePrefs() {
        // Flush and shutdown the Remote Store
        try {
            blockTimer.set(true);

            shutdownPref(instanceRemote);
            shutdownPref(instanceGlobal);

            instanceRemote = null;
            instanceGlobal = null;

            blockTimer.set(false);

        } catch (BackingStoreException ex) {
            log.error(ex);
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(AppPreferences.class, ex);
        }
    }

    /**
     * Flushes the values and then terminates the Prefs so a new one can be created.
     */
    public static void shutdownLocalPrefs() {
        // Flush and shutdown the Local Store
        try {
            if (instanceLocal != null && !blockTimer.get()) {
                blockTimer.set(true);
                if (instanceLocal != null) {
                    AppPreferences local = instanceLocal;
                    instanceLocal = null;
                    local.flush();
                    local.listeners.clear();
                    local.appPrefsIO = null;
                }
                blockTimer.set(false);
            }

        } catch (BackingStoreException ex) {
            log.error(ex);
            edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
            edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(AppPreferences.class, ex);
        }
    }

    /**
     * 
     */
    public static void shutdownAllPrefs() {
        try {
            AppPreferences.getLocalPrefs().flush();
        } catch (BackingStoreException ex) {
        }

        AppPreferences.shutdownRemotePrefs();
        AppPreferences.shutdownPrefs();
    }

    /**
     * Returns the singleton.
     * @return the singleton
     */
    public static AppPreferences getLocalPrefs() {
        if (instanceLocal == null) {
            instanceLocal = new AppPreferences(AppPrefType.local);
        }

        return instanceLocal;
    }

    /**
     * The directory path.
     * @return directory path.
     */
    public String getDirPath() {
        return dirPath;
    }

    /**
     * Sets the path to where it is suppose to save them.
     * @param dirPath the path.
     */
    public void setDirPath(String dirPath) {
        this.dirPath = dirPath;
    }

    /**
     * Whether it has been changed.
     * @return Whether it has been changed.
     */
    public boolean isChanged() {
        return isChanged;
    }

    /**
     * Sets whether it has changed.
     * @param isChanged true - changed, false not
     */
    public void setChanged(boolean isChanged) {
        this.isChanged = isChanged;
    }

    /**
     * Whether it is remote.
     * @return Whether it is remote.
     */
    public boolean isRemote() {
        return prefType == AppPrefType.remote;
    }

    /**
     * The file name of the disk file.
     * @return The file name of the disk file.
     */
    public String getLocalFileName() {
        return localFileName;
    }

    /**
     * The Properties object.
     * @return The Properties object.
     */
    public Properties getProperties() {
        return properties;
    }

    /**
     * Sets the properties file.
     * @param properties the properties file.
     */
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    /**
     * Gets a string value.
     * @param name the name of the pref
     * @param defaultValue the default value
     * @return the value as a String.
     */
    public String get(final String name, final String defaultValue) {
        return get(name, defaultValue, false);
    }

    /**
     * Gets a string value.
     * @param name the name of the pref
     * @param defaultValue the default value
     * @param doDefVal set the default value if it isn't there
     * @return the value as a String.
     */
    public String get(final String name, final String defaultValue, boolean doDefVal) {
        if (properties == null) {
            load();
        }

        String val = properties.getProperty(name);
        if (val == null && doDefVal && defaultValue != null) {
            put(name, defaultValue);
        }
        return val != null ? val : defaultValue;
    }

    /**
     * Sets a String value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void put(final String name, final String value) {
        if (properties == null) {
            load();
        }
        properties.setProperty(name, value);
        isChanged = true;
        notifyListeners(new AppPrefsChangeEvent(this, name, value.toString()));
    }

    /**
     * Returns the value as a Integer.
     * @param name the name
     * @param defaultValue the default value
     * @return the value as a Integer.
     */
    public Integer getInt(final String name, final Integer defaultValue) {
        return getInt(name, defaultValue, false);
    }

    /**
     * Returns the value as a Integer.
     * @param name the name
     * @param defaultValue the default value
     * @param doDefVal set the default value if it isn't there
     * @return the value as a Integer.
     */
    public Integer getInt(final String name, final Integer defaultValue, final boolean doDefVal) {
        String val = get(name, null);
        if (val == null && doDefVal && defaultValue != null) {
            putInt(name, defaultValue);
        }
        return val == null ? (defaultValue == null ? null : defaultValue) : Integer.valueOf(val);
    }

    /**
     * Sets a Integer value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void putInt(final String name, final Integer value) {
        put(name, value.toString());
    }

    /**
     * Returns the value as a Long.
     * @param name the name
     * @param defaultValue the default value
     * @return the value as a Long.
     */
    public Long getLong(final String name, final Long defaultValue) {
        String val = get(name, (defaultValue == null ? null : Long.toString(defaultValue)));
        return val == null ? null : Long.valueOf(val);
    }

    /**
     * Sets a Long value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void putLong(final String name, final Long value) {
        put(name, value.toString());
    }

    /**
     * Gets a value as Boolean.
     * @param name the name of the pref
     * @param defaultValue the default value
     * @param doDefVal set the default value if it isn't there
     * @return the value as a String.
     */
    public Boolean getBoolean(final String name, final Boolean defaultValue, final boolean doDefVal) {
        String val = get(name, null);
        if (val == null && doDefVal && defaultValue != null) {
            putBoolean(name, defaultValue);
        }
        return val == null ? (defaultValue == null ? null : defaultValue) : Boolean.valueOf(val);
    }

    /**
     * Returns the value as a Boolean.
     * @param name the name
     * @param defaultValue the default value
     * @return the value as a Boolean.
     */
    public Boolean getBoolean(final String name, final Boolean defaultValue) {
        return getBoolean(name, defaultValue, false);
    }

    /**
     * Sets a Boolean value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void putBoolean(final String name, final Boolean value) {
        put(name, value.toString());
    }

    /**
     * Returns the value as a Double.
     * @param name the name
     * @param defaultValue the default value
     * @return the value as a Double.
     */
    public Double getDouble(final String name, final Double defaultValue) {
        String val = get(name, (defaultValue == null ? null : Double.toString(defaultValue)));
        return val == null ? null : Double.valueOf(val);
    }

    /**
     * Sets a Double value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void putDouble(final String name, final Double value) {
        put(name, value.toString());
    }

    /**
     * Returns the value as a Float.
     * @param name the name
     * @param defaultValue the default value
     * @return the value as a Float
     */
    public Float getFloat(final String name, final Float defaultValue) {
        String val = get(name, (defaultValue == null ? null : Float.toString(defaultValue)));
        return val == null ? null : Float.valueOf(val);
    }

    /**
     * Sets a Float value into a pref.
     * @param name the name
     * @param value the new value
     */
    public void putFloat(final String name, final Float value) {
        put(name, value.toString());
    }

    /**
     * Gets a value as Color
     * @param name the name of the pref
     * @param defaultValue the default value
     * @param doDefVal set the default value if it isn't there
     * @return the value as a String.
     */
    public Color getColor(final String name, final Color defaultColor, final boolean doDefVal) {
        String colorStr = get(name, null);
        if (StringUtils.isNotEmpty(colorStr)) {
            try {
                return UIHelper.parseRGB(colorStr);

            } catch (Exception ex) {
                edu.ku.brc.af.core.UsageTracker.incrHandledUsageCount();
                edu.ku.brc.exceptions.ExceptionTracker.getInstance().capture(AppPreferences.class, ex);

            }
        } else {
            putColor(name, defaultColor);
        }
        return defaultColor;
    }

    /**
     * Gets a value as Color
     * @param name the name of the pref
     * @param defaultValue the default value
     * @return the value as a String.
     */
    public Color getColor(final String name, final Color defaultColor) {
        return getColor(name, defaultColor, false);
    }

    /**
     * @param name
     * @param color
     */
    public void putColor(final String name, final Color color) {
        put(name, String.format("%d, %d, %d", color.getRed(), color.getGreen(), color.getBlue())); //$NON-NLS-1$
    }

    /**
     * @param name
     * @param color
     */
    public void putColor(final String name, final String color) {
        put(name, color);
    }

    /**
     * Removes a pref by name.
     * @param name the name
     */
    public void remove(final String name) {
        if (properties == null) {
            throw new RuntimeException(NOT_INIT);
        }
        List<AppPrefsChangeListener> list = listeners.get(name);
        if (list != null) {
            list.clear();
            listeners.remove(name);
        }
        properties.remove(name);
        isChanged = true;
    }

    /**
     * Returns true if the pref already exists.
     * @param name the name
     * @return true if the pref already exists.
     */
    public boolean exists(final String name) {
        if (properties == null) {
            load();
            //throw new RuntimeException(NOT_INIT + appPrefsIO.getClass().getName());
        }
        return properties.get(name) != null;
    }

    /**
     * Returns a list of all the attrs for a given node.
     * @param nodeName the name of the node
     * @return array of strings a list of all the attrs for a given node.
     */
    public String[] keys(final String nodeName) {
        if (properties == null) {
            throw new RuntimeException(NOT_INIT);
        }
        String[] keys = null;
        if (nodeName != null && nodeName.length() > 0) {
            List<String> names = new ArrayList<String>();
            int len = nodeName.length();
            for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
                String name = (String) e.nextElement();
                if (name.startsWith(nodeName)) {
                    String nm = name.substring(len + 1, name.length());
                    int inx = nm.indexOf('.');
                    if (inx == -1) {
                        names.add(nm);
                    }
                }
            }
            keys = new String[names.size()];
            int inx = 0;
            for (String s : names) {
                keys[inx++] = s;
            }
        } else {
            keys = new String[0];

        }
        return keys;
    }

    /**
     * Returns a list of all the children names for a given node.
     * @param nodeName the node's name
     * @return  a list of all the children names for a given node.
     */
    public String[] childrenNames(final String nodeName) {
        if (properties == null) {
            throw new RuntimeException(NOT_INIT);
        }
        int len = nodeName.length();
        List<String> names = new ArrayList<String>();
        for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
            String name = (String) e.nextElement();
            log.info("[" + name + "]"); //$NON-NLS-1$ //$NON-NLS-2$
            if (name.startsWith(nodeName)) {
                String nm = name.substring(len + 1, name.length());
                int inx = nm.indexOf('.');
                int inxLast = nm.lastIndexOf('.');
                if (inx > 0 && inx == inxLast) {
                    names.add(nm.substring(0, inx));
                }
            }
        }
        String[] childNames = new String[names.size()];
        int inx = 0;
        for (String s : names) {
            childNames[inx++] = s;
        }
        return childNames;
    }

    /**
     * Loads the preferences from either just the local file, or from the remote and local "locations" and then synchronizes them, 
     * meaning both the local and the remote will have the same values where the newer values are copied to the older one.
     * @param dirPath the directory path to where the prefs file will be created.
     * @return return the AppPreferences that was loaded.
     */
    public AppPreferences load() {
        if (properties == null) {
            if (appPrefsIO.exists()) {
                appPrefsIO.load();
            } else {
                properties = new Properties();
            }
        }

        return this;
    }

    /**
     * Saves the contents to disk.
     * @throws BackingStoreException
     */
    public synchronized void flush() throws BackingStoreException {
        /*if (!isRemote)
        {
        System.err.println(instanceLocal+"  "+(instanceLocal == null ? 0 : Calendar.getInstance().getTime().getTime()));
            
        putLong("update_time", instanceLocal == null ? 0 : Calendar.getInstance().getTime().getTime());
        }*/

        // Only flush the properties if they are loaded and have changed
        if (isChanged && properties != null && appPrefsIO != null) {
            appPrefsIO.flush();
        }

    }

    /**
     * Adds a change listener for a pref.
     * @param name the name
     * @param l the listener
     */
    public void addChangeListener(final String name, final AppPrefsChangeListener l) {
        if (properties == null) {
            throw new RuntimeException(NOT_INIT);
        }
        List<AppPrefsChangeListener> list = listeners.get(name);
        if (list == null) {
            list = new ArrayList<AppPrefsChangeListener>();
            listeners.put(name, list);
        }
        list.add(l);
    }

    /**
     * Removes a change listener for a pref.
     * @param name the name
     * @param l the listener
     */
    public void removeChangeListener(final String name, final AppPrefsChangeListener l) {
        if (properties == null) {
            throw new RuntimeException(NOT_INIT);
        }
        List<AppPrefsChangeListener> list = listeners.get(name);
        if (list != null) {
            list.remove(l);
        }
    }

    /**
     * Tells prefs whether it is ok to access the database.
     * @param connectedToDB true - it is ok, false it isn't
     */
    public static void setConnectedToDB(boolean connectedToDB) {
        AppPreferences.connectedToDB = connectedToDB;
    }

    /**
     * Notifies listener of a property change.
     * @param e the change event
     */
    protected void notifyListeners(AppPrefsChangeEvent e) {
        List<AppPrefsChangeListener> list = listeners.get(e.getKey());
        if (list != null) {
            for (AppPrefsChangeListener l : list) {
                l.preferenceChange(e);
            }
        }
    }

    /**
     * Schedules a timer for flushing and saving the Prefs.
     */
    /*private static void schedulePrefSynching()
    {
    // Add periodic timer task to periodically sync cached prefs
    if (syncTimer == null)
    {
        syncTimer = new Timer(true); // Daemon Thread
        syncTimer.schedule(new TimerTask() {
            @Override
            public void run() 
            {
                if (!blockTimer.get())
                {
                    syncPrefs();
                }
            }
        }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);
    }
    }*/

    //---------------------------------------------------------------------------------------
    //-- The Code below is re-purposed from Sun's Preferences.java
    //-- Since our prefs are eccentially working like theirs we need them to flush/save automatically
    //-- and to make sure they get saved on exit
    //---------------------------------------------------------------------------------------

    /**
     * Sync interval in seconds.
     */
    /*@SuppressWarnings("unchecked") //$NON-NLS-1$
    private static final int SYNC_INTERVAL = Math.max(1,
    Integer.parseInt((String)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return System.getProperty("java.util.prefs.syncInterval", "30"); //$NON-NLS-1$ //$NON-NLS-2$
            }
    })));
        
    static {
        
    schedulePrefSynching();
        
    // Add shutdown hook to flush cached prefs on normal termination
    AccessController.doPrivileged(new PrivilegedAction<Object>() {
        public Object run() {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    if (syncTimer != null)
                    {
                        syncTimer.cancel();
                    }
                    if (!blockTimer.get())
                    {
                        syncPrefs();
                    }
                }
            });
            return null;
        }
    });
    }*/

    /**
     * Saves the prefs
     */
    protected static void syncPrefs() {
        /*
         * Synchronization necessary because userRoot and systemRoot are
         * lazily initialized.
         */
        AppPreferences prefsLocal;
        AppPreferences prefsRemote;

        synchronized (AppPreferences.class) {
            prefsLocal = instanceLocal;
            prefsRemote = instanceRemote;
        }

        try {
            if (prefsLocal != null) {
                prefsLocal.flush();
            }

        } catch (BackingStoreException e) {
            log.error("Couldn't flush the local prefs: " + e); //$NON-NLS-1$
            e.printStackTrace();
        }

        try {
            if (connectedToDB && prefsRemote != null) {
                prefsRemote.flush();
            }
        } catch (BackingStoreException e) {
            log.error("Couldn't flush the remote prefs: " + e); //$NON-NLS-1$
            e.printStackTrace();
        }
    }

    /**
     * @param enabled
     */
    public void setEnabled(final boolean enabled) {
        this.isEnabled = enabled;
    }

    /**
     * @return whether the preferences are available. (Whether they have been setup).
     */
    public boolean isAvailable() {
        return isEnabled && appPrefsIO != null && appPrefsIO.isAvailable();
    }

    /**
     * @return the last date the prefs were saved or null.
     */
    public Date getLastSavedDate() {
        if (appPrefsIO != null) {
            return appPrefsIO.lastSavedDate();
        }
        return null;
    }

    //-------------------------------------------------------------------------
    //-- AppPrefsIOIFace Interface for performing Prefs IO
    //-------------------------------------------------------------------------
    public interface AppPrefsIOIFace {
        /**
         * Sets the Prefs that this will act on.
         * @param appPrefsMgr the prefs
         */
        public abstract void setAppPrefsMgr(AppPreferences appPrefsMgr);

        /**
         * See if the prefs have been persisted yet (or ever).
         * @return true if they have been saved, false if they have not been
         */
        public abstract boolean exists();

        /**
         * Returns the date of the last time they were save or null if they have not.
         * @return the date of the last time they were save or null if they have not
         */
        public abstract Date lastSavedDate();

        /**
         * Loads the Prefs.
         */
        public abstract void load();

        /**
         * Flushes the Prefs to disk or database.
         */
        public abstract void flush() throws BackingStoreException;

        /**
         * @return whether the preferences are available. (Whether they have been setup).
         */
        public abstract boolean isAvailable();

    }

}