com.qualogy.qafe.core.datastore.Data.java Source code

Java tutorial

Introduction

Here is the source code for com.qualogy.qafe.core.datastore.Data.java

Source

/**
 * Copyright 2008-2015 Qualogy Solutions B.V.
 *
 * 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.
 */
package com.qualogy.qafe.core.datastore;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;

import com.qualogy.qafe.bind.commons.type.Parameter;

/**
 * Element to be inserted in datastore. This class wraps a Map (CaseInsensitiveMap in this matter)
 * and so supports key, value get and add (put).
 * 
 * The CaseInsensitiveMap allows the data in this class to be searched case insensitive, so for example:
 * 
 * Map map = new CaseInsensitiveMap();
 * map.put("One", "a");
 * map.put("Two", "b");
 * map.put("one", "c");
 * map.put(null, "d");//will throw an illegalargumentexception since key cannot be null
 * 
 * - creates a CaseInsensitiveMap with two entries 
 * - map.get(null) will throw an illegalargumentexception since key cannot be null 
 * - map.get("ONE") returns "c". 
 * - the Set returned by keySet() equals {"one", "two"}. 
 * 
 * NOTE: the CaseInsensitiveMap has one disadvantage, being that the keys are converted before storage
 * 
 * LastTouched (for cleanup purposes) is updated after the following actions on this object:
 *  - add
 *  - get
 *  - create (in constructor)
 * 
 * @author 
 */
@SuppressWarnings("unchecked") //generic class
public class Data {

    private long lastTouched;
    private Map elements;

    public Map getElements() {
        return elements;
    }

    private Data() {
        //elements = new CaseInsensitiveMap();
        elements = new DataMap();
        updateLastTouched();
    }

    protected static Data create() {
        return new Data();
    }

    protected void clear() {
        elements.clear();
    }

    /**
     * lastTouched is updated after get
     * @param key
     * @return
     */
    protected final Object get(String key) throws DataNotFoundException {

        updateKey(key);

        Object result = elements;
        try {
            if (key.indexOf(Parameter.OBJECT_DELIMITER) > -1) {//cheaper to check fst before creating tokenizer
                result = getNested(result, key);
            } else {
                result = getResult(result, key);
            }
        } catch (DataNotFoundException e) {
            throw new DataNotFoundException("Data cannot be found for [" + key + "]", e);
        }

        updateLastTouched();

        return result;
    }

    protected final Object find(String key) {

        Object result = null;
        try {
            result = get(key);
        } catch (DataNotFoundException e) {
            //nothing since it is a find 
        }
        return result;
    }

    /**
     * @pre key is not null
     * @pre key is to lower case
     * method returns null when no data found and throws keynotfoundException when key has not been registered
     * @param from
     * @param key
     * @return
     */
    private Object getResult(Object from, String key) throws DataNotFoundException {

        Object result = null;
        if (from != null) {
            if (!(from instanceof Map))
                throw new IllegalArgumentException(
                        "Trying to get nested result from a non-nested object. Key that was used: " + key
                                + " on object:" + from + "(" + from.getClass() + ")");

            boolean isList = key.indexOf("[") > -1;
            String getKey = (isList) ? key.substring(0, key.indexOf("[")) : key;

            if (!((Map) from).containsKey(getKey) && !getKey.contains("" + Parameter.ATTRIBUTE_DELIMITER))
                throw new DataNotFoundException("Data cannot be found for endkey [" + key + "]");

            result = ((Map) from).get(getKey);

            if (isList && result instanceof List) {
                int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));
                result = ((List) result).get(i);
            } else {
                if (key.contains("" + Parameter.ATTRIBUTE_DELIMITER)) {
                    String attribute = key.substring(key.indexOf(Parameter.ATTRIBUTE_DELIMITER) + 1, key.length());
                    String elementKey = key.substring(0, key.indexOf(Parameter.ATTRIBUTE_DELIMITER));
                    Object object = elements.get(elementKey);
                    if (object instanceof Collection) {
                        if (Parameter.ATTRIBUTE_SIZE.equals(attribute)) {
                            result = ((Collection) object).size();
                        }
                    }
                }
            }

        }
        /* Commented coz when only one value is selected in listbox, 
         * its converted to hashmap instead of keeping the type as list itself
         * 
         * if(result instanceof List && ((List)result).size()==1){
           result = ((List)result).get(0);
        }*/
        return result;
    }

    /**
     * @pre key is not null
     * @pre key is to lower case    
     * @param result
     * @param key
     * @return
     */
    private Object getNested(Object result, String key) throws DataNotFoundException {
        String[] tokens = StringUtils.split(key, Parameter.OBJECT_DELIMITER);
        for (int i = 0; i < tokens.length; i++) {
            result = getResult(result, tokens[i]);
        }
        return result;
    }

    protected boolean contains(String key) {
        //TODO:come on!!!
        try {
            get(key);
            return true;
        } catch (DataNotFoundException e) {
            return false;
        }
    }

    /**
     * method to add data to this
     * lastTouched is updated after add
     * @param key
     * @param value
     */

    protected final void add(String key, Object value) {

        key = updateKey(key);

        String[] tokens = StringUtils.split(key, Parameter.OBJECT_DELIMITER);

        Object mother = elements;
        for (int i = 0; i < tokens.length; i++) {
            if (i == tokens.length - 1) {//last token
                String partkey = tokens[i];
                if (tokens[i].indexOf("[") > -1) {
                    partkey = tokens[i].substring(0, tokens[i].indexOf("["));
                    List tmpList = (List) ((Map) mother).get(partkey);
                    if (tmpList == null)
                        tmpList = new ArrayList();
                    value = addToList(tokens[i], tmpList, value);
                }
                ((Map) mother).put(partkey, value);
                break;
            }
            //if more tokens, find next object in the map 
            //(assumption for map is because if there are tokens left, token search can only be on a map)
            mother = getNextObj((Map) mother, tokens[i]);
        }

        updateLastTouched();
    }

    /**
     * @pre within the list there can be no more map, assuming the last token is past on to this method
     * @param token
     * @param mother
     * @param value
     */
    private List addToList(String token, List mother, Object value) {
        int indexOfLastStartingBracket = token.indexOf("[");
        int indexOfLastClosingBracket = token.indexOf("]");
        if (indexOfLastClosingBracket < 0)
            throw new IllegalArgumentException(
                    "specified token [" + token + "], but object for this key is a list, so index is needed");
        while (indexOfLastClosingBracket > -1) {//in case of f.i. key[0][0].key
            String indexStr = token.substring(indexOfLastStartingBracket + 1, indexOfLastClosingBracket);
            if (!NumberUtils.isNumber(indexStr))
                throw new IllegalArgumentException(
                        "index on data, set by [<index>], must be a number, found [" + indexStr + "]");
            indexOfLastStartingBracket = token.indexOf("[", indexOfLastClosingBracket + 1);
            indexOfLastClosingBracket = token.indexOf("]", indexOfLastClosingBracket + 1);
            if (indexOfLastClosingBracket < 0) {//if this is the last, add the value
                mother.add(Integer.parseInt(indexStr), value);
            } else {
                mother = (List) mother.get(Integer.parseInt(indexStr));
            }
        }
        return mother;
    }

    /**
     * @pre this is not the deepest nested object in line
     * @param mother
     * @param token
     */
    private Object getNextObj(Map<String, Object> mother, String token) {
        Object result = null;

        boolean isList = token.indexOf("[") > -1;//according to the user this is a list
        String key = isList ? token.substring(0, token.indexOf("[")) : token;//get root key, without index

        if (!isList && !(mother.get(key) instanceof Map)) {//create the map if not exists
            mother.put(key, new HashMap<String, Object>());
        } else if (isList && !(mother.get(key) instanceof List)) {//create the list if not exists
            mother.put(key, new ArrayList<Object>());
        }

        if (isList) {
            Object tmpresult = (List) mother.get(key);
            int indexOfLastStartingBracket = token.indexOf("[");
            int indexOfLastClosingBracket = token.indexOf("]");
            while (indexOfLastClosingBracket > -1) {//in case of f.i. key[0][0].key
                String indexStr = token.substring(indexOfLastStartingBracket + 1, indexOfLastClosingBracket);

                indexOfLastStartingBracket = token.indexOf("[", indexOfLastClosingBracket + 1);
                indexOfLastClosingBracket = token.indexOf("]", indexOfLastClosingBracket + 1);

                if (!NumberUtils.isNumber(indexStr))
                    throw new IllegalArgumentException(
                            "index on data, set by [<index>], must be a number, found [" + indexStr + "]");

                int listSize = ((List) tmpresult).size();
                if (listSize == Integer.parseInt(indexStr)) {
                    if (indexOfLastClosingBracket > 0) {
                        ((List) tmpresult).add(new ArrayList());
                    } else {
                        ((List) tmpresult).add(new HashMap());
                    }
                } else if (Integer.parseInt(indexStr) > listSize) {
                    throw new IllegalArgumentException("trying to add data on position [" + indexStr
                            + "] in a list of size [" + listSize + "]");
                }
                tmpresult = ((List) tmpresult).get(Integer.parseInt(indexStr));
            }
            result = tmpresult;
        } else {
            result = mother.get(key);
        }
        return result;
    }

    /**
     * method validates key and updates it to lowercase 
     * @param key
     */
    private String updateKey(String key) {
        if (key == null)
            throw new IllegalArgumentException("Cannot store data for null key");

        if (key.length() == 0)
            throw new IllegalArgumentException("Cannot store data for empty key");

        if (key.endsWith("" + Parameter.OBJECT_DELIMITER))
            throw new IllegalArgumentException("Cannot end key with " + Parameter.OBJECT_DELIMITER);

        return key;
    }

    protected boolean isTouchedAfter(long time) {
        return lastTouched > time;
    }

    private void updateLastTouched() {
        lastTouched = Calendar.getInstance().getTime().getTime();
    }

    protected String toLogString() {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(lastTouched);
        StringBuilder builder = new StringBuilder(255);
        builder.append("Last touched > " + cal.getTime() + (!elements.isEmpty() ? "\n" : ""));

        DataToLogStringBuilder.build(elements, builder);
        return builder.toString();
    }
}