All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.addthis.basis.kv.KVPairs Maven / Gradle / Ivy

There is a newer version: 3.0.4
Show newest version
/*
 * 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.addthis.basis.kv;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import com.addthis.basis.util.LessBytes;
import com.addthis.basis.util.LessNumbers;
import com.addthis.basis.util.LessStrings;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class that represents a collection of key/value pairs.
 */
public class KVPairs implements Iterable {
    protected static final Logger log = LoggerFactory.getLogger(KVPairs.class);
    private static final boolean putupper = System.getProperty("kv.putupper", "1").equals("1");
    private static final boolean getupper = System.getProperty("kv.getupper", "1").equals("1");

    public static KVPairs mergeAndCopy(KVPairs base, KVPairs overrider) {
        if (overrider == null) {
            return base;
        } else if (base == null) {
            return overrider;
        }
        KVPairs ret = overrider.getCopy();
        for (KVPair kv : base) {
            if (!ret.hasKey(kv.getKey())) {
                ret.addValue(kv.getKey(), kv.getValue());
            }
        }
        return ret;
    }

    public static KVPairs fromFullURL(String url) {
        KVPairs kvp = new KVPairs();
        kvp.addValue("_URLROOT_", kvp.parseURLPath(url, true));
        return kvp;
    }


    /*
     * Remove pairs with empty/null values
     */
    public static KVPairs clearEmpties(KVPairs values) {

        for (Iterator iter = values.elements(); iter.hasNext();) {
            KVPair kv = iter.next();

            if (LessStrings.isEmpty(kv.getValue())) {
                iter.remove();
            }
        }

        return values;
    }


    private String fixGetCase(String key) {
        return getupper ? key.toUpperCase() : key;
    }

    private HashMap pairs;

    /**
     * Constructs an empty KVPairs object.
     */
    public KVPairs() {
        pairs = new LinkedHashMap<>();
    }

    protected KVPairs(KVPairs kv) {
        this.pairs = kv.pairs;
    }

    /**
     * Constructs a KVPairs object from an urlencoded string.
     *
     * @param urlpath
     */
    public KVPairs(String urlpath) {
        this();
        if (urlpath != null) {
            parseURLPath(urlpath, false);
        }
    }

    public KVPairs(byte kbin[]) {
        this();
        fromBinArray(kbin);
    }


    /**
     * Returns a shallow clone of this KVPairs.
     */
    @SuppressWarnings("unchecked")
    public KVPairs getCopy() {
        KVPairs kv = new KVPairs(this);
        try {
            kv.pairs = (HashMap) pairs.clone();
        } catch (Exception ex) {
            log.trace("getCopy", ex);
        }
        return kv;
    }

    /**
     * Returns a more human readable (but not always decodable copy) for debugging.
     */
    public String getPrintable() {
        return getPrintable(false);
    }

    public String getPrintable(boolean newline) {
        StringBuilder sb = new StringBuilder();
        for (Iterator i = pairs.values().iterator(); i.hasNext();) {
            KVPair kv = i.next();
            sb.append(kv.getKey() + "='" + kv.getValue() + "'");
            if (i.hasNext()) {
                sb.append(newline ? '\n' : ' ');
            }
        }
        return sb.toString();
    }

    private void parsePair(String s) {
        KVPair kv = KVPair.parsePair(s);
        if (kv != null) {
            addPair(kv);
        }
    }

    /**
     * A faster tokenizer than StringTokenizer (about 2x)
     */
    private void parsePairs(String s, char del) {
        if (s.length() == 0) {
            return;
        }
        int i = 0;
        int j = s.indexOf(del);
        while (j >= 0) {
            if (j > 0) {
                parsePair(s.substring(i, j));
            }
            i = j + 1;
            j = s.indexOf(del, i);
        }
        parsePair(s.substring(i));
    }

    /**
     * Parses a urlencoded string of key/value pairs and adds the values
     * to this KVPairs object.
     *
     * @param urlPath
     * @param lookForQ
     * @return
     */
    public String parseURLPath(String urlPath, boolean lookForQ) {
        String filePath = urlPath;
        if (lookForQ) {
            int idx = urlPath.indexOf('?');
            if (idx < 0 || idx == urlPath.length()) {
                return urlPath;
            }
            filePath = urlPath.substring(0, idx);
            urlPath = urlPath.substring(idx + 1);
        }
        parsePairs(urlPath, '&');
        return filePath;
    }

    public KVPairs prefix(String prefix) {
        HashMap oldPairs = pairs;
        pairs = new LinkedHashMap<>();
        for (Map.Entry mapEntry : oldPairs.entrySet()) {
            KVPair kvpair = mapEntry.getValue();
            kvpair.setKey(prefix + kvpair.getKey());
            addPair(kvpair);
        }

        return this;
    }


    /**
     * Merges values from provided KVPairs overriding any values
     * with the same keys in this KVPairs.
     *
     * @param add
     */
    public synchronized KVPairs merge(KVPairs add) {
        if (add != null) {
            pairs.putAll(add.pairs);
        }
        return this;
    }

    /**
     * merge which is empty-value-aware- if there is an empty value in the target, override it with a
     * non-empty value form the source
     */
    public KVPairs mergeNotEmpty(KVPairs add) {
        if (add != null) {
            for (String key : add.pairs.keySet()) {
                KVPair newVal = add.getPair(key);
                KVPair thisVal = getPair(key);
                if ((newVal != null) && (!LessStrings.isEmpty(newVal.getValue()))) {
                    if ((thisVal == null) || (LessStrings.isEmpty(thisVal.getValue()))) {
                        addPair(newVal);
                    }
                }
            }
        }
        return this;
    }


    public KVPairs add(String key, String val) {
        addValue(key, val);
        return this;
    }

    public KVPairs add(String key, int val) {
        return add(key, Integer.toString(val));
    }

    private static KVPair createPair(String key, String value) {
        return new KVPair(key, value);
    }

    /**
     * Adds a key/value pair.
     *
     * @param key
     * @param val
     * @return
     */
    public KVPair addValue(String key, String val) {
        return addPair(createPair(key, val));
    }

    public KVPair addValue(String key, int value) {
        return addPair(createPair(key, Integer.toString(value)));
    }

    public KVPair addValue(String key, long value) {
        return addPair(createPair(key, Long.toString(value)));
    }

    public KVPair addValue(String key, float value) {
        return addPair(createPair(key, Float.toString(value)));
    }

    public KVPair addValue(String key, double value) {
        return addPair(createPair(key, Double.toString(value)));
    }

    public KAPair addValue(String key, String... values) {
        KAPair pair = new KAPair(key, values);
        addPair(pair);
        return pair;
    }

    /**
     * putValue() synonym for addValue()
     */
    public KVPair putValue(String key, String val) {
        return addValue(key, val);
    }

    public KVPair putValue(String key, int value) {
        return addValue(key, value);
    }

    public KVPair putValue(String key, long value) {
        return addValue(key, value);
    }

    public KVPair putValue(String key, float value) {
        return addValue(key, value);
    }

    public KVPair putValue(String key, double value) {
        return addValue(key, value);
    }

    public KAPair putValue(String key, String... values) {
        return addValue(key, values);
    }

    /**
     * Adds a key/value pair from a KVPair object.
     *
     * @param pair
     * @return
     */
    public final synchronized KVPair addPair(KVPair pair) {
        String key = pair.getKey();
        return pairs.put(putupper ? key.toUpperCase() : key, pair);
    }

    public KVPair putPair(KVPair pair) {
        return addPair(pair);
    }

    /**
     * Replaces the value for an existing key and adds a new key/value pair
     * for a missing key.  Preserves original key and it's case.
     */
    public final void replaceOrAdd(String key, String value) {
        KVPair pair = getPair(key);
        if (pair != null) {
            pair.setValue(value);
        } else {
            addValue(key, value);
        }
    }

    /**
     * Replaces the value for an existing key and adds a new key/value pair
     * for a missing key.  Preserves original key and it's case.
     */
    public final void replaceOrAdd(String key, long value) {
        KVPair pair = getPair(key);
        if (pair != null) {
            pair.setValue(Long.toString(value));
        } else {
            addValue(key, value);
        }
    }

    /**
     * Replaces the value for an existing key and adds a new key/value pair
     * for a missing key.  Preserves original key and it's case.
     */
    public final void replaceOrAdd(String key, int value) {
        KVPair pair = getPair(key);
        if (pair != null) {
            pair.setValue(Integer.toString(value));
        } else {
            addValue(key, value);
        }
    }

    /**
     * Changes the key associated with a value.  Does nothing if the
     * key does not exist.
     *
     * @param okey
     * @param nkey
     * @return true if the key exists, false otherwise
     */
    public final boolean renameValue(String okey, String nkey) {
        KVPair p = pairs.remove(fixGetCase(okey));
        if (p != null) {
            p.setKey(nkey);
            addPair(p);
            return true;
        }
        return false;
    }

    /**
     * Remove the key and value for a given key.
     */
    public final KVPair removePair(String key) {
        return pairs.remove(fixGetCase(key));
    }

    /**
     * Returns a string value or null of the key is absent.
     * Removes any matching key and data.
     */
    public String takeValue(String key) {
        KVPair p = removePair(key);
        return (p != null ? p.getValue() : null);
    }

    /**
     * Returns a string value or default of the key is absent.
     * Removes any matching key and data.
     */
    public String takeValue(String key, String def) {
        KVPair p = removePair(key);
        return (p != null ? p.getValue() : def);
    }

    /**
     * Integer takeValue() with base10.
     * Removes any matching key and data.
     */
    public int takeValue(String key, int def) {
        return takeValue(key, def, 10);
    }

    /**
     * Returns an integer value using the specified base or default
     * if the key is absent or value malformed.
     * Removes any matching key and data.
     */
    public int takeValue(String key, int def, int base) {
        String val = takeValue(key);
        return (val != null ? LessNumbers.parseInt(val, def, base) : def);
    }

    /**
     * Returns an integer value using the specified base or default
     * if the key is absent or value malformed.
     * Removes any matching key and data.
     */
    public float takeValue(String key, float def) {
        String val = takeValue(key);
        return (val != null ? LessNumbers.parseFloat(val, def) : def);
    }

    /**
     * Long takeValue() with base10.
     * Removes any matching key and data.
     */
    public long takeValue(String key, long def) {
        return takeValue(key, def, 10);
    }

    /**
     * Returns a long value using the specified base or default
     * if the key is absent or value malformed.
     * Removes any matching key and data.
     */
    public long takeValue(String key, long def, int base) {
        String val = takeValue(key);
        return (val != null ? LessNumbers.parseLong(val, def, base) : def);
    }

    /**
     * Return KVPair wrapper object for a given key.
     *
     * @param key
     * @return KVPair object or null if no key matches.
     */
    public final synchronized KVPair getPair(String key) {
        return pairs.get(fixGetCase(key));
    }

    /**
     * Returns true if a given key exists.
     *
     * @param key
     * @return true if key exists
     */
    public final boolean hasKey(String key) {
        if (key == null) {
            return false;
        }
        return pairs.containsKey(fixGetCase(key));
    }

    /**
     * @return true if contains a pair with same key and value as given pair (not an object comparison)
     */
    public final boolean hasPair(KVPair pair) {
        return pair != null && pair.equals(getPair(pair.getKey()));
    }

    /**
     * Return value for a given key or null if key does not exist.
     *
     * @param key
     * @return value or null if key does not exist.
     */
    public String getValue(String key) {
        return getValue(key, null);
    }

    /**
     * Return value for a given key or default if key does not exist.
     *
     * @param key
     * @param def default value to return if key does not exist
     * @return value or default if key does not exist.
     */
    public String getValue(String key, String def) {
        KVPair pair = getPair(key);
        return (pair != null ? pair.getValue() : def);
    }

    /**
     * Return value for a given key or default if key does not exist.
     *
     * @param key
     * @param def default value to return if key does not exist
     * @return value or default if key does not exist.
     */
    public int getIntValue(String key, int def) {
        return getIntValue(10, key, def);
    }

    /**
     * Return value for a given key or default if key does not exist.
     *
     * @param radix base for converting string to number
     * @param key
     * @param def   default value to return if key does not exist
     * @return value or default if key does not exist.
     */
    public int getIntValue(int radix, String key, int def) {
        KVPair pair = getPair(key);
        return pair != null ? LessNumbers.parseInt(pair.getValue(), def, radix) : def;
    }

    /**
     * Return value for a given key or default if key does not exist.
     *
     * @param key
     * @param def default value to return if key does not exist
     * @return value or default if key does not exist.
     */
    public long getLongValue(String key, long def) {
        return getLongValue(10, key, def);
    }

    /**
     * Return value for a given key or default if key does not exist.
     *
     * @param radix base for converting string to number
     * @param key
     * @param def   default value to return if key does not exist
     * @return value or default if key does not exist.
     */
    public long getLongValue(int radix, String key, long def) {
        KVPair pair = getPair(key);
        return pair != null ? LessNumbers.parseLong(pair.getValue(), def, radix) : def;
    }

    public float getFloatValue(String key, float def) {
        String val = getValue(key);
        return val != null ? LessNumbers.parseFloat(val, def) : def;
    }

    public double getDoubleValue(String key, double def) {
        String val = getValue(key);
        return val != null ? LessNumbers.parseDouble(val, def) : def;
    }

    /**
     * Changes the value for a given key.  Does not add key/value.
     *
     * @param key
     * @param value
     * @return previous value or null if the key doesn't exist
     */
    public final String setValue(String key, String value) {
        String oval = null;
        KVPair p = getPair(key);
        if (p != null) {
            oval = p.getValue();
            p.setValue(value);
        }
        return oval;
    }

    /**
     * @return number of KVPair objects represented.
     */
    public final int count() {
        return pairs.size();
    }

    /**
     * Returns an Iterator of key Strings.
     *
     * @return an Iterator of key Strings
     */
    public final Iterator keys() {
        return pairs.keySet().iterator();
    }

    /**
     * Returns an Iterator of key Strings.
     *
     * @return an Iterator of key Strings
     */
    public final Set keySet() {
        return pairs.keySet();
    }

    public final Collection values() {
        return pairs.values();
    }

    public Iterator iterator() {
        return elements();
    }

    /**
     * Returns an Iterator of KVPair elements.
     *
     * @return an Iterator of KVPair elements
     */
    public final Iterator elements() {
        return pairs.values().iterator();
    }


    public String toURLParams() {
        return toString();
    }

    public void fromURLParams(String str) {
        parseURLPath(str, false);
    }

    public byte[] toBinArray() {
        int size = 0;
        ArrayList arr = new ArrayList<>(count() * 2);
        for (KVPair kv : values()) {
            String v = kv.getValue();
            byte key[] = LessBytes.toBytes(kv.getKey());
            byte val[] = v != null ? LessBytes.toBytes(kv.getValue()) : new byte[0];
            size += key.length + val.length;
            arr.add(key);
            arr.add(val);
        }
        byte out[] = new byte[size + arr.size()];
        size = 0;
        for (byte el[] : arr) {
            for (int i = 0; i < el.length; i++) {
                out[size + i] = el[i] != 0 ? el[i] : (byte) ' ';
            }
            size += el.length + 1;
            out[size - 1] = 0;
        }
        return out;
    }

    public KVPairs fromBinArray(byte bin[]) {
        int pos = 0;
        while (pos < bin.length) {
            String key = null;
            String val = null;
            for (int i = pos; i < bin.length; i++) {
                if (bin[i] == 0) {
                    key = new String(bin, pos, i - pos);
                    pos = i + 1;
                    break;
                }
                if (i == bin.length - 1) {
                    key = new String(bin, pos, i - pos + 1);
                    pos = i + 1;
                    break;
                }
            }
            for (int i = pos; i < bin.length; i++) {
                if (bin[i] == 0) {
                    val = new String(bin, pos, i - pos);
                    pos = i + 1;
                    break;
                }
                if (i == bin.length - 1) {
                    val = new String(bin, pos, i - pos + 1);
                    pos = i + 1;
                    break;
                }
            }
            addValue(key, val);
        }
        return this;
    }

    /**
     * Returns a urlencoded string comprised of all the "name"=value&"name"=value pairs.
     */
    public synchronized String toQuotedKeyString() {
        StringBuilder sb = new StringBuilder();
        for (Iterator i = pairs.values().iterator(); i.hasNext();) {
            sb.append(i.next().toQuotedKeyString());
            if (i.hasNext()) {
                sb.append("&");
            }
        }
        return sb.toString();
    }

    /**
     * Returns a urlencoded string comprised of all the name=value&name=value pairs.
     */
    public synchronized String toString() {
        StringBuilder sb = new StringBuilder();
        for (Iterator i = pairs.values().iterator(); i.hasNext();) {
            sb.append(i.next().toString());
            if (i.hasNext()) {
                sb.append("&");
            }
        }
        return sb.toString();
    }

    /**
     * basic equals function, fast-fail, can be optimized for speed by iterating through keyset only once
     * TODO: override hashcode() method as recommended by java api
     *
     * @return true if the other KVPairs set has the equal keys and values
     */
    public synchronized boolean equals(KVPairs other) {
        if (!keySet().equals(other.keySet())) {
            return false;
        }  // compare key sets (tests for extra/missing pairs in other set)
        for (String key : keySet()) {
            if (!getPair(key).equals(other.getPair(key))) {
                return false;
            }  // compare pairs
        }
        return true;
    }

    /**
     * multi-pair convenience constructors
     */
    public KVPairs(String ka, String va) {
        this();
        addValue(ka, va);
    }

    public KVPairs(String ka, String va, String kb, String vb) {
        this();
        addValue(ka, va);
        addValue(kb, vb);
    }

    public KVPairs(String ka, String va, String kb, String vb, String kc, String vc) {
        this();
        addValue(ka, va);
        addValue(kb, vb);
        addValue(kc, vc);
    }

    public KVPairs(String ka, String va, String kb, String vb, String kc, String vc, String kd, String vd) {
        this();
        addValue(ka, va);
        addValue(kb, vb);
        addValue(kc, vc);
        addValue(kd, vd);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy