org.buzzjoe.SimpleDataStorage.SimpleDataStorage.java Source code

Java tutorial

Introduction

Here is the source code for org.buzzjoe.SimpleDataStorage.SimpleDataStorage.java

Source

package org.buzzjoe.SimpleDataStorage;

import java.io.*;
import org.apache.commons.codec.binary.Base64;

import java.util.ArrayList;
import java.util.Properties;

/**
 * A class for handling a simple way to store data on the local file system.
 * Data will be stored as XML and can be given as String or Integer.<br>
 * <br>
 * There's no need to make the file. SDS will do that for you when it 
 * needs to write to it the first time.<br>
 * <br>
 * Usage:<br>
 * <br>
 * (1) <br>
 * Make an Instance of SDS by giving it a file including it's path:<br>
 * <br>
 * SimpleDataStorage sds = new SimpleDataStorage('path/to/file.xml');<br>
 * <br>
 * (2.1)<br>
 * Get data:<br>
 * <br>
 * Object someData = sds.get('keyName');<br>
 * <br>
 * You will get a result of type Object. So you need to cast it to Integer,
 * String or whatever yourself.<br>
 * Keys can be tiled into sections when using a dot (buzzjoe.home).<br>
 * SDS will eat every SERIALIZEABLE data type you give it.
 * <br>
 * (2.2)<br>
 * Set data:<br>
 * <br>
 * String someData = 'Germany';<br>
 * sds.set('buzzjoe.home', someData)<br>
 * <br>
 * This Software uses the Apache Commons Codec library under Apache License 2.0<br>
 * Homepage: http://commons.apache.org/codec/<br>
 * License: http://www.apache.org/licenses/LICENSE-2.0.html<br>
 * <br>
 * License: free for all time<br>
 * <br>
 * Version: 0.3.3<br>
 * <br>
 * Just save data in destructor when autosave is enabled.<br>
 * <br>
 * Version: 0.3.2<br>
 * <br>
 * Changelog:<br>
 * <br>
 * 0.3.2:<br>
 * Fixed some issues when requesting a key that does not exist.<br>
 * <br>
 * 0.3.1:<br>
 * Bugfix for wrong decrement() when called without amount.<br>
 * <br>
 * 0.3:<br>
 * Improved performance. SDS now only writes out to a file if changes were 
 * made. So there are no writing-actions when just getting some information 
 * but not setting anyting.<br>
 * Even writeToFile() will do nothing if there wasn't any change.<br>
 * <br>
 * 0.2:<br>
 * Added increment(), decrement(), keyExists() and a switch for turning off 
 * autosave. When turned off writeToFile() must be called manually to store 
 * Data in an XML file.<br>
 * <br>
 * 0.1:<br>
 * Initial release<br>
 * 
 * @author BuzzJoe - michael@xenotek.de
 */
public class SimpleDataStorage {

    private String version = "0.3.2";

    /**
     * @var file The file's name which content is availabe in class' instance
     */
    private String file;

    /**
     * @var data Propreties container
     */
    private Properties data = new Properties();

    /**
     * @var Enables autosave when setting or deleting a key
     */
    private boolean doAutosave;

    /**
     * @var Flag that is set to true when any data has been changed
     */
    private boolean dataChanged = false;

    //-------------------------------------------------------------------------

    public String version() {
        return this.version;
    }

    //-------------------------------------------------------------------------

    /**
     * Stores Data out of this.file in local properties container this.data
     * 
     * @param file (String) Filename (XML, with path) to be read into local "data" Container
     */
    public SimpleDataStorage(String file, boolean autosave) {
        this.file = file;
        this.doAutosave = autosave;
        FileInputStream fis;
        try {
            fis = new FileInputStream(this.file);
            this.data.loadFromXML(fis);
            fis.close();
        } catch (Exception e) {
            // well. Seems like there was no file - That's no fault.
            // Anyway, that can be ignored. File will be made when we will
            // set some data the first time.
            // so: nothing to handle here
        }

    }

    //-------------------------------------------------------------------------

    /**
     * Constructor without autosave parameter. Autosave will be turned ON when
     * calling this.
     * @param file (String) Filename (XML, with path) to be read into local "data" Container
     */
    public SimpleDataStorage(String file) {
        this(file, true);
    }

    //-------------------------------------------------------------------------

    /**
     * Write this.data into XML file before gc-ing.
     */
    public void finalize() {
        if (this.dataChanged & this.doAutosave)
            this.writeToFile();
    }

    //-------------------------------------------------------------------------

    /**
     * Returns all available properties in this.data
     * 
     * @return All stored properties. Returns Instance of java.util.Properties
     */
    public Properties getAll() {
        return this.data;
    }

    //-------------------------------------------------------------------------

    /**
     * Returns properties value out of this.data container.
     * 
     * @param propertyName Property to get  
     * @return a string containing the value of requestet proprety 
     */
    public Object get(String propertyName) {

        String data = this.data.getProperty(propertyName);

        try {
            int integer = Integer.parseInt(data);
            // Looks like we've got an Integer value. Return it.
            return integer;
        } catch (NumberFormatException nFE) {
            // whooops. This wasn't integer. Let's try if we can deserialize...
            try {

                byte[] decoded = Base64.decodeBase64(data);

                ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(decoded));
                Object oData = in.readObject();
                in.close();

                return oData;

            } catch (Exception e) {
                // Nope. Not even serialized stuff. So it must be a string
                return data;
            }
        }
    }

    //-------------------------------------------------------------------------

    /**
     * Does the same like this.get() but will return an int Object.<br>
     * Returns 0 when key does not exist.<br>
     * ATTENTION! This method is NOT suitable for proofing if a special key 
     * exists!<br>
     * Use this.keyExists(key); for that!
     * 
     * @param keyName
     * @return int Object
     */
    public int getInt(String keyName) {

        int result = 0;

        // see if requested key exists
        if (this.keyExists(keyName)) {
            Object data = this.get(keyName);

            try {
                result = (Integer) data;
            } catch (Exception e) {
                System.out.println("SimpleDataStorage - Error: Can't cast to int. Requested key " + keyName
                        + " is not an Integer type.");
            }
        }

        return result;
    }

    //-------------------------------------------------------------------------

    /**
     * Does the same like this.get() but will return an ArrayList Object.
     * Returns empty ArrayList Object when key does not exist.<br>
     * ATTENTION! This method is NOT suitable for proofing if a special key 
     * exists!<br>
     * Use this.keyExists(key); for that!
     * 
     * @param keyName
     * @return ArrayList Object
     */
    @SuppressWarnings("unchecked")
    public ArrayList<Object> getArrayList(String keyName) {
        ArrayList<Object> result = new ArrayList<Object>();

        if (this.keyExists(keyName)) {
            Object data = this.get(keyName);
            try {
                result = (ArrayList<Object>) data;
            } catch (Exception e) {
                System.out.println("SimpleDataStorage - Error: Can't cast to ArrayList. Requested key " + keyName
                        + " is not an ArrayList type.");
            }
        }
        return result;
    }

    //-------------------------------------------------------------------------

    /**
     * Does the same like this.get() but will return an String Object.
     * Returns an empty String when key does not exist.<br>
     * ATTENTION! This method is NOT suitable for proofing if a special key 
     * exists!<br>
     * Use this.keyExists(key); for that!
     * 
     * @param keyName
     * @return String Object
     */
    public String getString(String keyName) {
        String result = new String();

        if (this.keyExists(keyName)) {
            Object data = this.get(keyName);
            try {
                result = (String) data;
            } catch (Exception e) {
                System.out.println("SimpleDataStorage - Error: Can't cast to int. Requested key " + keyName
                        + " is not an String type.");
            }
        }
        return result;
    }

    //-------------------------------------------------------------------------
    /**
     * Stores some Data in this.data container.<br>
     * 
     * @param keyName f.e. 'myKey' or 'buzzjoe.home'
     * @param data data to be stored. Accepts String and Integer by default. Other datatypes will be serialized.
     * @param serializeData set to true if you want to serialize in every case
     * 
     */
    public void set(String keyName, Object data, boolean serializeData) {

        if ((data instanceof Integer || data instanceof String) && !serializeData) {
            this.data.setProperty(keyName, data.toString());
            this.dataChanged = true;
        } else {

            // okay. Seems like we got something to serialize
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buf = null;
            try {
                ObjectOutput out = new ObjectOutputStream(bos);
                out.writeObject(data);
                out.close();
                buf = bos.toByteArray();

            } catch (Exception e) {
                System.out.println("SimpleDataStorage - Error: Given datatype for key " + keyName
                        + " is not serializeable. Can't store it.");
                System.out.println(e.toString());
            }

            byte[] encoded = Base64.encodeBase64(buf);
            String outString = "";
            try {
                outString = new String(encoded, "ASCII");
            } catch (Exception e) {

            }
            this.data.setProperty(keyName, outString);
            this.dataChanged = true;
        }

        // this is a very bad idea. But it is the only secure way to store
        // the data to the file system. 
        // Needs some polish because method is called after every changing action
        if (this.doAutosave)
            this.writeToFile();
    }

    //-------------------------------------------------------------------------

    /**
     * Second method for handling different parameter.<br>
     * See other this.set() method for more details.
     * 
     * @param keyName f.e. 'myProperty' or 'buzzjoe.home'
     * @param data data to be stored. Accepts String and Integer by default. Other datatypes will be serialized.
     */
    public void set(String keyName, Object data) {
        this.set(keyName, data, false);
    }

    //-------------------------------------------------------------------------

    /**
     * Delete a property out of the this.data storage
     * 
     * @param keyName Property to delete
     */
    public void delete(String keyName) {
        this.data.remove(keyName);
        this.dataChanged = true;

        // this is a very bad idea. But it is the only secure way to store
        // the data to the file system. 
        // Needs some polish because method is called after every changing action
        if (this.doAutosave)
            this.writeToFile();
    }

    //-------------------------------------------------------------------------

    /**
     * Write this.data to this.file as XML<br>
     * Will do nothing when not data has been changed before.
     */
    public void writeToFile() {

        // data changed? If not then do nothing. That's good for performance.
        if (this.dataChanged) {

            String filePath;

            // check if path to file was given and extract it
            try {
                filePath = this.file.substring(0, this.file.lastIndexOf("/"));
            } catch (StringIndexOutOfBoundsException e) {
                filePath = "";
            }

            // if there was a file path given, then make folders if needed
            if (filePath != "") {
                File folder = new File(filePath);
                folder.mkdirs();
            }

            // make file if needed and write XML data to it.
            try {
                FileOutputStream fos = new FileOutputStream(this.file);
                this.data.storeToXML(fos, this.file, "UTF-8");
                fos.close();
                // data has been written to file. So this.dataChanged can be set back to false
                this.dataChanged = false;
            } catch (Exception e) {
                System.out.println("SimpleDataStorage - Error: Unable to write to " + this.file);
            }
        }
    }

    //-------------------------------------------------------------------------

    /**
     * Returns either true if searched key exists or false if not.
     * 
     * @param keyName Properties key (f.e. "buzzjoe.age")
     * @return boolean
     */
    public boolean keyExists(String keyName) {
        return this.data.containsKey(keyName);
    }

    //-------------------------------------------------------------------------

    /**
     * Returns either true if automatic writing to file is enabled or false
     * if not.
     * 
     * @return boolean
     */
    public boolean autosaveEnabled() {
        return this.doAutosave;
    }

    //-------------------------------------------------------------------------

    /**
     * Increments key's value by amount.<br>
     * If no amount is given, key will be incremented by 1<br>
     * ATTENTION! This will cause wrong data if you try to increment a non-int
     * key!
     * 
     * @param key Properties key (f.e. "buzzjoe.age")
     * @param amount Amount that should be incremented to value
     * @return new amount of key
     */
    public int increment(String key, int amount) {
        int newAmount = this.getInt(key) + amount;
        this.set(key, newAmount);
        this.dataChanged = true;
        return newAmount;
    }

    //-------------------------------------------------------------------------

    /**
     * Increments key's value by 1<br>
     * ATTENTION! This will cause wrong data if you try to increment a non-int
     * key!
     * 
     * @param key Properties key (f.e. "buzzjoe.age")
     * @return new amount of key
     */
    public int increment(String key) {
        return this.increment(key, 1);
    }

    //-------------------------------------------------------------------------

    /**
     * Decrements key's value by amount.<br>
     * If no amount is given, key will be incremented by 1<br>
     * ATTENTION! This will cause wrong data if you try to increment a non-int
     * key!
     * 
     * @param key Properties key (f.e. "buzzjoe.age")
     * @param amount Amount that should be incremented to value
     * @return new amount of key
     */
    public int decrement(String key, int amount) {
        int newAmount = this.getInt(key) - amount;
        this.set(key, newAmount);
        this.dataChanged = true;
        return newAmount;
    }

    //-------------------------------------------------------------------------

    /**
     * Decrements key's value by 1<br>
     * ATTENTION! This will cause wrong data if you try to increment a non-int
     * key!
     * 
     * @param key Properties key (f.e. "buzzjoe.age")
     * @return new amount of key
     */
    public int decrement(String key) {
        return this.decrement(key, 1);
    }

    //-------------------------------------------------------------------------
}