com.addthis.basis.kv.KVPairs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of basis Show documentation
Show all versions of basis Show documentation
AddThis core java classes
/*
* 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);
}
}