Data.Storage.java Source code

Java tutorial

Introduction

Here is the source code for Data.Storage.java

Source

package Data;

import DataTools.ConvertObjectToJson;
import DataTools.Utils;
import StorageHelper.Converter;
import StorageHelper.CreatorHelper;
import StorageHelper.PathParser;
import StorageHelper.RetrieveByPath;
import StorageHelper.Update;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import org.dom4j.Attribute;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import javax.sip.message.Response;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 Copyright 2016 Alianza Inc.
    
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
    
 http://www.apache.org/licenses/LICENSE-2.0
    
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
    
 */

/**
 * A place to hold data that endpoints will need to interact with the system
 * can handle different formats of data
 */
public class Storage {

    private final HashMap<String, Object> storage;

    private String xmlArrayKey = "item";

    /**
     * default constructor
     */
    public Storage() {
        storage = new HashMap<>();
    }

    /**
     * constructor that populates the collections with given data
     * @param json JSONObject of data
     */
    public Storage(JSONObject json) {
        this();
        addJson(json);
    }

    /**
     * constructor that populates the collections with given data
     * @param json JSONArray of data
     */
    public Storage(JSONArray json) {
        this();
        addJson(json);
    }

    /**
     * constructor that populates the collections with given data
     * saves html data as a string with the key html
     * @param data String of data or json String
     */
    public Storage(String data) {
        storage = CreatorHelper.createDataStorage(data).toMap();
    }

    /**
     * constructor that populates the collections with given sip data response
     * @param sipResponse SIPResponse from a sip request
     */
    public Storage(Response sipResponse) {
        this();
        addSip(sipResponse);
    }

    /**
     * constructor that populates the collections with given data
     * @param model ax model of data
     */
    public <T> Storage(T model) {
        this();
        addModel(model);
    }

    /**
     * constructor that populates the collections with given data
     * @param dataMap hash map of data
     */
    public Storage(HashMap<String, ?> dataMap) {
        this();
        addMap(dataMap);
    }

    /**
     * constructor that populates the collections with given data
     * @param array of data
     */
    public Storage(ArrayList array) {
        this();
        addArray(array);
    }

    /**
     * adding an item to the collection
     * @param key index item is stored by
     * @param value actual data being stored
     */
    public void put(String key, Object value) {
        //make sure it is an object and not something else, force polymorphism
        if (value instanceof DateTime) {
            put(key, (DateTime) value);
        } else if (value instanceof ZonedDateTime) {
            put(key, (DateTime) value);
        } else if (value instanceof String) {
            put(key, value.toString());
        } else if (value instanceof Storage) {
            put(key, (Storage) value);
        } else if (value instanceof Double) {
            //does it have to be a double?
            int iValue = ((Double) value).intValue();
            int maxValue = new Double((Double) value + 0.99999).intValue();
            if (iValue < maxValue) {
                storage.put(key, value);
            } else {
                put(key, iValue);
            }
        } else {
            ConvertObjectToJson jsonConverter = new ConvertObjectToJson();
            JSONObject jsonValue = jsonConverter.convertToJson(value);
            if (jsonValue == null) {
                storage.put(key, value);
            } else {
                storage.put(key, jsonValue);
            }
        }
    }

    /**
     * adding a date time item to the collection
     * @param key index item is stored at
     * @param value actual date being stored, a date time object
     */
    public void put(String key, DateTime value) {
        storage.put(key, ConvertObjectToJson.cleanupDate(value));
    }

    /**
     * adding a date time item to the collection
     * @param key index item is stored at
     * @param value actual date being stored, a date time object
     */
    public void put(String key, ZonedDateTime value) {
        storage.put(key, ConvertObjectToJson.cleanupDate(value));
    }

    /**
     * adding an item to the collection, if its string see if it is json string
     * saves html data as a string
     * @param key index item is stored by
     * @param value string of the data, can be json string or just a string
     */
    public void put(String key, String value) {
        String tempValue = value.trim();
        try {
            if (Utils.isJsonArray(tempValue))
                storage.put(key, new JSONArray(tempValue));
            else if (Utils.isJSONObject(tempValue))
                storage.put(key, new JSONObject(tempValue));
            else if (Utils.isXml(tempValue)) {
                String xmlValue = Converter.cleanXmlHeader(tempValue);
                if (xmlValue.startsWith("<html>") && xmlValue.endsWith("</html>")) {
                    storage.put(key, xmlValue);
                } else {
                    String xmlString = "<" + key + ">" + xmlValue + "</" + key + ">";
                    xmlString = Converter.cleanXml(xmlString);
                    addXml(xmlString);
                }
            } else
                storage.put(key, value);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * adding a data Storage to the collection
     * @param key index item is stored by
     * @param value actual data storage data to be stored
     */
    public void put(String key, Storage value) {
        try {
            //find out if it is an array or json
            //if the collection is empty then hashmap
            boolean isArray = true;
            for (String storageKey : value.keys())
                for (char k : storageKey.toCharArray())
                    //if there is just one key not a digit then not array
                    if (!Character.isDigit(k))
                        isArray = false;

            //if the collection is empty then hashmap
            if (value.keys().size() > 0 && isArray) {
                HashMap<String, Object> newJson = new HashMap<>();
                newJson.put(key, value.toJsonArray());
                addMap(newJson);
            } else {
                JSONObject newJson = new JSONObject();
                newJson.put(key, value.toJson());
                addJson(newJson);
            }

        } catch (JSONException e) {
            Utils.debug("unable to add to Data Storage:\nStorage: " + toString() + "\nAdding: " + value.toString(),
                    "error");
            e.printStackTrace();
        }
    }

    /**
     * adding an item to the collection that is from a path, and value is a string
     * @param keys index path, item is stored by
     * @param value actual data being stored, if string can format it as json or xml
     */
    public void put(String[] keys, String value) {
        Object parsedValue = ConvertObjectToJson.convertStringToObject(value);
        put(keys, parsedValue);
    }

    /**
     * adding an item to the collection that is from a path
     * @param keys index path, item is stored by
     * @param value actual data being stored
     */
    public void put(String[] keys, Object value) {
        //if it is a datetime object it needs to be formatted correctly
        if (value instanceof DateTime) {
            value = ConvertObjectToJson.cleanupDate((DateTime) value);
        } else if (value instanceof ZonedDateTime) {
            value = ConvertObjectToJson.cleanupDate((ZonedDateTime) value);
        }
        //only one item then use the normal puts
        if (keys.length == 1) {
            put(keys[0], value);
            return;
        }

        {
            //make sure path exists
            RetrieveByPath pathSearch = new RetrieveByPath(this);
            pathSearch.assurePathExists(keys);
        }
        //remove the last key of the path
        String lastKey = keys[keys.length - 1];
        String[] path = Utils.copyArrayExceptLast(keys);

        //find the containing object
        Object item = findElementByPath(path);

        try {
            Update update = new Update(item);
            update.performUpdate(lastKey, value);
        } catch (IllegalAccessException | InvocationTargetException e) {
            Utils.debug("PATH INVALID:\nPATH: " + path + "\nDATA: " + toJson(), "error");
            e.printStackTrace();
        }
    }

    /**
     * put the value as a string, do not try to read it as json or xml
     * @param key key value is saved at
     * @param value saved
     */
    public void putString(String key, String value) {
        storage.put(key, value);
    }

    /**
     * put the value as a string, do not try to read it as json or xml
     * find key from path
     * @param key path saved at
     * @param value saved data
     */
    public void putString(String[] key, String value) {
        put(key, (Object) value);
    }

    /**
     * add an attribute, best use with xml
     * @param path the path of the element
     * @param attrKey the key of the attribute being added
     * @param attrValue value of the attribute being added
     */
    public void putAttribute(String[] path, String attrKey, String attrValue) {
        int lastIndex = path.length - 1;
        String lastKey = path[lastIndex];
        //don't do anything if its an array, the key would be a number if an array
        boolean isNumber = true;
        for (char c : lastKey.toCharArray()) {
            if (!Character.isDigit(c)) {
                isNumber = false;
            }
        }
        //not supporting array attributes or adding attributes to non existent elements
        if (isNumber || !has(path))
            return;

        path[lastIndex] = lastKey + Converter.attributesId;

        //create attributes json
        if (!has(path)) {
            put(path, new JSONObject());
        }

        //create new array
        String[] attrPath = new String[path.length + 1];
        //populate the path to the attribute
        for (int i = 0; i < path.length; ++i) {
            attrPath[i] = path[i];
        }
        attrPath[path.length] = attrKey;

        put(attrPath, attrValue);
        //change the data back to how it was
        path[lastIndex] = lastKey;
    }

    /**
     * remove item from collection
     * @param key key of item to remove
     */
    public void remove(String key) {
        storage.remove(key);
    }

    /**
     * remove item from collection found by path
     * @param keys an array of the path to delete the item
     */
    public void remove(String[] keys) {
        //remove the last key of the path
        String lastKey = keys[keys.length - 1];
        String[] path = Utils.copyArrayExceptLast(keys);

        //find the containing object
        Object item = findElementByPath(path);

        //perform the remove operation, casting to what it is to allow remove to be called
        try { //set
            Update update = new Update(item);
            update.performRemove(lastKey);
        } catch (Exception e) {
            Utils.debug("PATH INVALID:\nPATH: " + path + "\nDATA: " + toJson(), "error");
            e.printStackTrace();
        }
    }

    /**
     * get a single item from collection
     * @param key key to find the stored item
     * @return T the found item cast to expected type
     */
    public <T> T get(String key) {
        return (T) storage.get(key);
    }

    /**
     * get a single item from collection as an int
     * @param key key to find stored item
     * @return int the item as an int
     */
    public int getInt(String key) {
        //get the int value of doubles
        String intValue = getString(key).split("\\.")[0];
        return Integer.parseInt(intValue);
    }

    /**
     * get a single item from collection as a String
     * @param key key to find the stored item
     * @return String the item as a string
     */
    public String getString(String key) {
        return storage.get(key).toString();
    }

    /**
     * get a single item from collection
     * @param key index to find the stored item
     * @return T the found item cast to expected type
     */
    public <T> T get(int key) {
        return get(String.valueOf(key));
    }

    /**
     * get an object in collection that might be a sub object
     * enter the path in an array, index of array is parent to child order
     * index 0 is parent index 1 is child
     * if it is an array just put the index as a string "0" will return first item in array
     * example:
     * {'results':[{'item':12}]}
     * {"results", "0", "item"} returns 12
     * @param path string array of the path
     * @return Object the found item from the path
     */
    public <T> T get(String[] path) {
        return (T) findElementByPath(path);
    }

    /**
     * get an object by path like other get but returns a string
     * @param keys the keys in order for the path to retrieve
     * @return
     */
    public String getString(String[] keys) {
        return get(keys).toString();
    }

    /**
     * get an object by path like other get but returns an int
     * @param keys the keys in order for the path to retrieve
     * @return
     */
    public int getInt(String[] keys) {
        String foundInt = getString(keys).split("\\.")[0];
        return Integer.parseInt(foundInt);
    }

    /**
     * get a saved attribute, best used with xml
     * @param path path of the element where attribute is
     * @param key key of desired attribute
     * @return String value of attribute
     */
    public String getAttribute(String[] path, String key) {
        return getAttributes(path).get(key).toString();
    }

    /**
     * get all the attributes for that key
     * @param path the path to the key
     * @return HashMap of the attributes
     */
    public HashMap<String, Object> getAttributes(String[] path) {
        int lastIndex = path.length - 1;
        String lastKey = path[lastIndex];
        path[lastIndex] = lastKey + Converter.attributesId;
        return getAsDataStorage(path).toMap();
    }

    /**
     * get object from collection as a Data Storage object
     * give the path as an array like previous get(String[])
     * @param path string array of the path
     * @return DataStorage object populated with found object
     */
    public Storage getAsDataStorage(String[] path) {
        Object found = findElementByPath(path);
        return CreatorHelper.createDataStorage(found);
    }

    /**
     * get object from collection as a Data Storage object
     * @param key string key of object, top level
     * @return DataStorage object populated with found object
     */
    public Storage getAsDataStorage(String key) {
        Object found = get(key);
        return CreatorHelper.createDataStorage(found);
    }

    /**
     * get object from collection as a Data Storage object
     * @param key index of object, top level
     * @return DataStorage object populated with found object
     */
    public Storage getAsDataStorage(int key) {
        Object found = get(key);
        return CreatorHelper.createDataStorage(found);
    }

    /**
     * extract array from collection
     * if the collection is not an array it will be empty
     * @return ArrayList from the collection
     */
    public ArrayList toArray() {
        ArrayList items = new ArrayList();
        for (int i = 0; has(String.valueOf(i)); ++i)
            items.add(get(i));

        return items;
    }

    /**
     * get the size of the collection
     * @return int number of items in top level of collection
     */
    public int size() {
        return storage.size();
    }

    /**
     * see if collection has this key
     * @param key the key we are looking for to exist
     * @return boolean if it found the key in the collection
     */
    public boolean has(String key) {
        return storage.containsKey(key);
    }

    /**
     * See if collection has a key based on a path
     * @param path Path to the key to check
     * @return boolean if it found the key in the collection
     */
    public boolean has(String[] path) {
        String key = path[path.length - 1];
        if (key != null) {
            for (String[] p : findPaths(key)) {
                if (Arrays.equals(p, path)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * gets a set of all available keys at highest level
     * @return Set a set of the keys
     */
    public Set<String> keys() {
        return storage.keySet();
    }

    /**
     * add a json object to the collection
     * converts the json and adds to the current collection
     * @param json JSONObject you want to add to the collection
     */
    public void addJson(JSONObject json) {
        //gson setup
        Gson converter = new Gson();
        Type hashmapType = new TypeToken<HashMap<String, Object>>() {
        }.getType();

        HashMap<String, Object> converted = converter.fromJson(json.toString(), hashmapType);
        addMap(converted);
    }

    /**
     * add a json object to the collection
     * converts the json and adds to the current collection
     * @param json JSONArray you want to add to the collection
     */
    public void addJson(JSONArray json) {
        for (int i = 0; i < json.length(); ++i)
            try {
                Object nextItem = json.get(i);
                if (nextItem.getClass().getTypeName().contains("JSONObject"))
                    nextItem = new Storage((JSONObject) nextItem).toMap();
                put(String.valueOf(i), nextItem);
            } catch (JSONException e) {
                e.printStackTrace();
            }
    }

    /**
     * add a xml to the collection, in the form of a string
     * converts the xml and adds to the current collection
     * @param xml String that is in xml format
     */
    public void addXml(String xml) {
        try {
            addXml(Converter.xmlStringToElement(xml));
        } catch (DocumentException e) {
            //the xml thing just didn't work out, its not you its me
            storage.put("data", xml);
        }
    }

    /**
     * add a xml to the collection, as an xml node
     * @param xmlFile org.w3c.dom.Node object, parent node of xml
     */
    public void addXml(Element xmlFile) {
        ArrayList<String> path = new ArrayList<>();
        recurseOverElements(path, xmlFile);
    }

    /**
     * add a model to the collection
     * @param model model to get data from, example com.alianza.az.model.Account
     * @param <T> the type the model is
     */
    public <T> void addModel(T model) {
        if (model instanceof Storage) {
            addModel((Storage) model);
        } else {
            ConvertObjectToJson converter = new ConvertObjectToJson();
            JSONObject jsonObject = converter.convertToJson(model);

            addJson(jsonObject);
        }
    }

    /**
     * add a model to the collection
     * @param model model to get data from, example com.alianza.az.model.Account
     * @param <T> the type the model is
     */
    public <T> void addModel(Storage model) {
        addMap(model.toMap());
    }

    /**
     * add a HashMap of data to the current collection
     * @param adding hashmap of data
     */
    public void addMap(HashMap<String, ?> adding) {
        for (String key : adding.keySet()) {
            put(key, adding.get(key));
        }
    }

    /**
     * add an array of data to the current collection
     * @param array ArrayList of data to add
     */
    public void addArray(List array) {
        for (int i = 0; i < array.size(); ++i) {
            put(String.valueOf(i), array.get(i));
        }
    }

    /**
     * take the response from sip and format to save data
     * @param sipResponse the response from the sip request
     */
    public void addSip(Response sipResponse) {
        HashMap<String, Object> sipData = new HashMap<>();

        String[] allSip = sipResponse.toString().split("\r\n");

        //first line is the status, response code
        sipData.put("statusCode", allSip[0]);

        //get the response data, first line is the status
        for (int i = 1; i < allSip.length; ++i) {
            String[] rowData = allSip[i].split(": ");

            //if : is not splitting them then it must be the body
            //use = to split
            if (rowData.length < 2)
                rowData = allSip[i].split("=");

            //sometimes empty space just skip those
            if (rowData.length >= 2)
                sipData.put(rowData[0], rowData[1]);
        }

        addMap(sipData);
    }

    /**
     * get the full collection in json form
     * @return JSONObject entire collection as json, or null if error
     */
    public JSONObject toJson() {
        Converter convert = new Converter(storage);
        return convert.convertToJson();
    }

    /**
     * get the an array in json array form
     * @return JSONArray the collection as an array, will only contain array items
     */
    public JSONArray toJsonArray() {
        Converter converter = new Converter(storage);
        return converter.convertToJsonArray();
    }

    /**
     * get the full collection in xml form as a string
     * @return String entire collection as xml string
     */
    public String toXml() {
        Converter converter = new Converter(storage);
        converter.setXmlArrayKey(xmlArrayKey);
        return converter.convertToXml();
    }

    /**
     * get the full collection as a hashMap
     * @return HashMap(String, Object) the full collection in hash map form
     */
    public HashMap<String, Object> toMap() {
        return new HashMap<>(storage);
    }

    /**
     * convert the storage object to a pojo object using faster xml object mapper
     * @param valueType the type of object you want it to be mapped to
     * @param <T>
     * @return T object of type T with data mapped as it is in the storage object
     */
    public <T> T toObject(Class<T> valueType) {
        return ConvertObjectToJson.convertToObject(toString(), valueType);
    }

    /**
     * find all paths leading to a specific key in the collection
     * find any strings that match that key name and give a full path to get to them
     * @param searchKey string key looking for in the collection
     * @return ArrayList of String[], each entry in array list is a path to the key searched for
     */
    public ArrayList<String[]> findPaths(String searchKey) {
        Converter convert = new Converter(storage);
        JSONObject json = convert.convertToJson();

        PathParser pathParser = new PathParser(json);
        return pathParser.findPaths(searchKey);
    }

    /**
     * when converting an object to xml if there is an array it needs a key to list the items
     * the default is item but if it needs to be different place in here what it should be
     * @param key xml array key
     */
    public void setXmlArrayKey(String key) {
        xmlArrayKey = key;
    }

    @Override
    public String toString() {
        return toJson().toString();
    }

    @Override
    public boolean equals(Object otherStorage) {
        //first check to see if they are even right types
        if (!(otherStorage instanceof Storage) || ((Storage) otherStorage).toMap().size() != storage.size()) {
            return false;
        }

        boolean same = true;
        HashMap<String, Object> other = ((Storage) otherStorage).toMap();

        for (Map.Entry<String, Object> entry : storage.entrySet()) {
            //because of json objects we need to test by string too
            if (other.get(entry.getKey()) != entry.getValue()
                    && !other.get(entry.getKey()).toString().equals(entry.getValue().toString())) {
                same = false;
            }
        }
        return same;
    }

    private Object findElementByPath(String[] path) {
        if (path.length < 1) {
            return this;
        }
        RetrieveByPath pathSearch = new RetrieveByPath(this);
        return pathSearch.findElementByPath(path);
    }

    /**
     * helper for adding xml
     * @param path where the xml will be added
     * @param xmlData the data as an xml Element
     */
    private void addXmlWithPath(ArrayList<String> path, Element xmlData) {
        boolean isXml = xmlData.elements().size() > 0;

        //set the key
        String[] thePath = new String[path.size()];
        path.toArray(thePath);

        //put either the value or an imbeded object
        if (isXml && !has(thePath)) {
            put(thePath, new Storage());
        } else {
            put(thePath, xmlData.getStringValue());
        }
        for (int i = 0; i < xmlData.attributeCount(); ++i) {
            //get attribute data
            Attribute attr = xmlData.attribute(i);

            //save attribute
            putAttribute(thePath, attr.getName(), attr.getValue());
        }

        //if it is xml need to see all children
        if (isXml) {
            recurseOverElements(path, xmlData);
        }
    }

    /**
     * helper for adding xml, goes over each element and add it to the storage, recursively
     * @param path current path to save items
     * @param xmlData data being saved as an xml Element
     */
    private void recurseOverElements(ArrayList<String> path, Element xmlData) {
        String[] thePath = new String[path.size()];
        path.toArray(thePath);

        List<Element> elements = (List<Element>) xmlData.elements();
        //different if an array
        boolean isArray = false;
        if (elements.size() > 1)
            isArray = elements.get(0).getName().equals(elements.get(1).getName());

        //if its an array then it should be saved as an array
        if (isArray && getAsDataStorage(thePath).toArray().size() < 1) {
            put(thePath, new JSONArray());
        }
        int i = 0;
        for (Element element : elements) {
            String key = isArray ? String.valueOf(i++) : element.getName();
            ArrayList<String> tempPath = (ArrayList<String>) path.clone();
            tempPath.add(key.replace(Converter.nsReplacer, ":"));
            addXmlWithPath(tempPath, element);
        }
    }
}