/*
* Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.amazonaws.services.dynamodbv2.document;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.checkInvalidAttrName;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.checkInvalidAttribute;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.rejectNullInput;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.rejectNullOrEmptyInput;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.rejectNullValue;
import static com.amazonaws.services.dynamodbv2.document.internal.InternalUtils.valToString;
import static com.amazonaws.util.BinaryUtils.copyAllBytesFrom;
import static com.amazonaws.util.BinaryUtils.copyBytesFrom;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.amazonaws.services.dynamodbv2.document.internal.InternalUtils;
import com.amazonaws.services.dynamodbv2.document.internal.ItemValueConformer;
import com.amazonaws.util.Base64;
import com.amazonaws.util.json.Jackson;
/**
* An <a href=
* "http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html"
* >item</a> in DynamoDB. An item is a collection of attributes. Each attribute
* has a name and a value. An attribute value can be one of the followings:
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
* For an <code>Item</code> to be successfully persisted in DynamoDB, at a
* minimum the respective attributes for the primary key must be specified.
*/
public class Item {
private static final String DUPLICATE_VALUES_FOUND_IN_INPUT = "Duplicate values found in input";
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private static final ItemValueConformer valueConformer = new ItemValueConformer();
/**
* Returns true if the specified attribute exists with a null value; false
* otherwise.
*/
public boolean isNull(String attrName) {
return attributes.containsKey(attrName)
&& attributes.get(attrName) == null;
}
/**
* Returns true if this item contains the specified attribute; false
* otherwise.
*/
public boolean isPresent(String attrName) {
return attributes.containsKey(attrName);
}
/**
* Returns the value of the specified attribute in the current item as a
* string; or null if the attribute either doesn't exist or the attribute
* value is null.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public String getString(String attrName) {
Object val = attributes.get(attrName);
return valToString(val);
}
/**
* Sets the value of the specified attribute in the current item to the
* given string value.
*/
public Item withString(String attrName, String val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Returns the value of the specified attribute in the current item as a
* <code>BigDecimal</code>; or null if the attribute either doesn't exist or
* the attribute value is null.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException if the attribute value is not a valid
* representation of a {@code BigDecimal}.
*/
public BigDecimal getNumber(String attrName) {
Object val = attributes.get(attrName);
return toBigDecimal(val);
}
private BigDecimal toBigDecimal(Object val) {
if (val == null)
return null;
return val instanceof BigDecimal
? (BigDecimal) val
: new BigDecimal(val.toString())
;
}
/**
* Returns the value of the specified attribute in the current item as an
* <code>BigInteger</code>; or null if the attribute doesn't exist.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public BigInteger getBigInteger(String attrName) {
BigDecimal bd = getNumber(attrName);
return bd == null ? null : bd.toBigInteger();
}
/**
* Returns the value of the specified attribute in the current item as a
* <code>short</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public short getShort(String attrName) {
BigDecimal bd = getNumber(attrName);
if (bd == null)
throw new NumberFormatException
("value of " + attrName + " is null");
return bd.shortValue();
}
/**
* Returns the value of the specified attribute in the current item as an
* <code>int</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public int getInt(String attrName) {
BigDecimal bd = getNumber(attrName);
if (bd == null)
throw new NumberFormatException
("value of " + attrName + " is null");
return bd.intValue();
}
/**
* Returns the value of the specified attribute in the current item as an
* <code>long</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public long getLong(String attrName) {
BigDecimal bd = getNumber(attrName);
if (bd == null)
throw new NumberFormatException
("value of " + attrName + " is null");
return bd.longValue();
}
/**
* Returns the value of the specified attribute in the current item as a
* <code>float</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public float getFloat(String attrName) {
BigDecimal bd = getNumber(attrName);
if (bd == null)
throw new NumberFormatException
("value of " + attrName + " is null");
return bd.floatValue();
}
/**
* Returns the value of the specified attribute in the current item as a
* <code>double</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws NumberFormatException
* if the attribute value is null or not a valid representation
* of a {@code BigDecimal}.
*/
public double getDouble(String attrName) {
BigDecimal bd = getNumber(attrName);
if (bd == null)
throw new NumberFormatException
("value of " + attrName + " is null");
return bd.doubleValue();
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withNumber(String attrName, BigDecimal val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withNumber(String attrName, Number val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, toBigDecimal(val));
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withInt(String attrName, int val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, Integer.valueOf(val));
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBigInteger(String attrName, BigInteger val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, val);
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withShort(String attrName, short val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, Short.valueOf(val));
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withFloat(String attrName, float val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, Float.valueOf(val));
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withDouble(String attrName, double val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, Double.valueOf(val));
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withLong(String attrName, long val) {
checkInvalidAttrName(attrName);
return withNumber(attrName, Long.valueOf(val));
}
/**
* Returns the value of the specified attribute in the current item as a
* byte array; or null if the attribute either doesn't exist or the
* attribute value is null.
*
* @throws UnsupportedOperationException
* If the attribute value involves a byte buffer which is not
* backed by an accessible array
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a byte array
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public byte[] getBinary(String attrName) {
Object val = attributes.get(attrName);
return toByteArray(val);
}
/**
* Returns the value of the specified attribute in the current item as a
* <code>ByteBuffer</code>; or null if the attribute either doesn't exist or
* the attribute value is null.
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a byte array
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public ByteBuffer getByteBuffer(String attrName) {
Object val = attributes.get(attrName);
return toByteBuffer(val);
}
/**
* This method is assumed to be only called from a getter method, but NOT
* from a setter method.
*/
private byte[] toByteArray(Object val) {
if (val == null)
return null;
if (val instanceof byte[])
return (byte[]) val;
if (val instanceof ByteBuffer) {
// Defensive code but execution should never get here. The internal
// representation of binary should always be
// byte[], not ByteBuffer. This allows Item to be converted into
// a JSON string via Jackson without causing trouble.
return copyAllBytesFrom((ByteBuffer)val);
}
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a byte array");
}
private ByteBuffer toByteBuffer(Object val) {
if (val == null)
return null;
if (val instanceof byte[])
return ByteBuffer.wrap((byte[])val);
if (val instanceof ByteBuffer) {
// Defensive code but execution should never get here. The internal
// representation of binary should always be
// byte[], not ByteBuffer. This allows Item to be converted into
// a JSON string via Jackson without causing trouble.
return (ByteBuffer)val;
}
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a ByteBuffer");
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBinary(String attrName, byte[] val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBinary(String attrName, ByteBuffer val) {
checkInvalidAttribute(attrName, val);
// convert ByteBuffer to bytes to keep Jackson happy
attributes.put(attrName, copyBytesFrom(val));
return this;
}
/**
* Returns the value of the specified attribute in the current item as a set
* of strings; or null if the attribute either doesn't exist or the
* attribute value is null.
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a set of
* strings because of duplicate elements
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Set<String> getStringSet(String attrName) {
Object val = attributes.get(attrName);
if (val == null)
return null;
Set<String> stringSet = new LinkedHashSet<String>();
if (val instanceof Collection) {
Collection<?> col = (Collection<?>) val;
if (col.size() == 0)
return stringSet;
for (Object element: col) {
String s = element == null ? null : valToString(element);
if (!stringSet.add(s))
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of strings because of duplicate elements");
}
return stringSet;
}
stringSet.add(valToString(val));
return stringSet;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withStringSet(String attrName, Set<String> val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withStringSet(String attrName, String ...val) {
checkInvalidAttribute(attrName, val);
Set<String> strSet = new LinkedHashSet<String>(Arrays.asList(val));
if (strSet.size() != val.length)
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
attributes.put(attrName, strSet);
return this;
}
/**
* Returns the value of the specified attribute in the current item as a set
* of BigDecimal's; or null if the attribute either doesn't exist or the
* attribute value is null.
*
* @throws NumberFormatException
* if the attribute involves a value that is not a valid
* representation of a {@code BigDecimal}.
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a set of
* <code>BigDecimal</code>'s because of duplicate elements
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Set<BigDecimal> getNumberSet(String attrName) {
Object val = attributes.get(attrName);
if (val == null)
return null;
Set<BigDecimal> numSet = new LinkedHashSet<BigDecimal>();
if (val instanceof Collection) {
Collection<?> col = (Collection<?>) val;
if (col.size() == 0)
return numSet;
for (Object element: col) {
BigDecimal bd = toBigDecimal(element);
if (!numSet.add(bd))
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of BigDecimal's because of duplicate elements");
}
return numSet;
} else if (val instanceof BigDecimal) {
numSet.add((BigDecimal)val);
return numSet;
} else {
numSet.add(new BigDecimal(val.toString()));
return numSet;
}
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBigDecimalSet(String attrName, Set<BigDecimal> val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBigDecimalSet(String attrName, BigDecimal ... vals) {
checkInvalidAttribute(attrName, vals);
Set<BigDecimal> set = new LinkedHashSet<BigDecimal>(Arrays.asList(vals));
if (set.size() != vals.length)
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
attributes.put(attrName, set);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withNumberSet(String attrName, Number ... vals) {
checkInvalidAttribute(attrName, vals);
Set<BigDecimal> set = InternalUtils.toBigDecimalSet(vals);
if (set.size() != vals.length)
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
return withBigDecimalSet(attrName, set);
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withNumberSet(String attrName, Set<Number> vals) {
checkInvalidAttribute(attrName, vals);
Set<BigDecimal> set = InternalUtils.toBigDecimalSet(vals);
if (set.size() != vals.size())
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
return withBigDecimalSet(attrName, set);
}
/**
* Returns the value of the specified attribute in the current item as a set
* of byte arrays; or null if the attribute either doesn't exist or the
* attribute value is null.
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a set of byte
* arrays
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Set<byte[]> getBinarySet(String attrName) {
Object val = attributes.get(attrName);
if (val == null)
return null;
Set<byte[]> binarySet = new LinkedHashSet<byte[]>();
if (val instanceof Collection) {
Collection<?> col = (Collection<?>) val;
if (col.size() == 0)
return binarySet;
for (Object element: col) {
byte[] ba = toByteArray(element);
if (!binarySet.add(ba))
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of byte arrays because of duplicate elements");
}
return binarySet;
} else if (val instanceof byte[]) {
binarySet.add((byte[])val);
return binarySet;
} else if (val instanceof ByteBuffer) {
// Defensive code but execution should never get here. The internal
// representation of binary should always be
// byte[], not ByteBuffer. This allows Item to be converted into
// a JSON string via Jackson without causing trouble.
ByteBuffer bb = (ByteBuffer) val;
binarySet.add(copyAllBytesFrom(bb));
return binarySet;
}
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of byte arrays");
}
/**
* Returns the value of the specified attribute in the current item as a set
* of <code>ByteBuffer</code>; or null if the attribute either doesn't exist
* or the attribute value is null.
*
* @throws IncompatibleTypeException
* if the attribute value cannot be converted into a set of
* <code>ByteBuffer</code>
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Set<ByteBuffer> getByteBufferSet(String attrName) {
Object val = attributes.get(attrName);
if (val == null)
return null;
Set<ByteBuffer> binarySet = new LinkedHashSet<ByteBuffer>();
if (val instanceof Collection) {
Collection<?> col = (Collection<?>) val;
if (col.size() == 0)
return binarySet;
for (Object element: col) {
ByteBuffer ba = toByteBuffer(element);
if (!binarySet.add(ba))
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of ByteBuffer because of duplicate elements");
}
return binarySet;
} else if (val instanceof ByteBuffer) {
// Defensive code but execution should never get here. The internal
// representation of binary should always be
// byte[], not ByteBuffer. This allows Item to be converted into
// a JSON string via Jackson without causing trouble.
binarySet.add((ByteBuffer)val);
return binarySet;
} else if (val instanceof byte[]) {
binarySet.add(ByteBuffer.wrap((byte[])val));
return binarySet;
}
throw new IncompatibleTypeException(val.getClass()
+ " cannot be converted into a set of ByteBuffer");
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBinarySet(String attrName, Set<byte[]> val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, val);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withByteBufferSet(String attrName, Set<ByteBuffer> val) {
checkInvalidAttribute(attrName, val);
// convert ByteBuffer to bytes to keep Jackson happy
Set<byte[]> set = new LinkedHashSet<byte[]>(val.size());
for (ByteBuffer bb: val)
set.add(copyBytesFrom(bb));
attributes.put(attrName, set);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBinarySet(String attrName, byte[] ... vals) {
checkInvalidAttribute(attrName, vals);
Set<byte[]> set = new LinkedHashSet<byte[]>(Arrays.asList(vals));
if (set.size() != vals.length)
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
attributes.put(attrName, set);
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withBinarySet(String attrName, ByteBuffer ... vals) {
checkInvalidAttribute(attrName, vals);
// convert ByteBuffer to bytes to keep Jackson happy
Set<byte[]> set = new LinkedHashSet<byte[]>(vals.length);
for (ByteBuffer bb: vals)
set.add(copyBytesFrom(bb));
if (set.size() != vals.length)
throw new IllegalArgumentException(DUPLICATE_VALUES_FOUND_IN_INPUT);
attributes.put(attrName, set);
return this;
}
/**
* Returns the value of the specified attribute in the current item as a set
* of <code>T</code>'s.; or null if the attribute either doesn't exist or
* the attribute value is null.
*
* @throws ClassCastException
* if the attribute involves a value that cannot be casted to
* <code>T</code>
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public <T> List<T> getList(String attrName) {
Object val = attributes.get(attrName);
if (val == null)
return null;
if (val instanceof List) {
@SuppressWarnings("unchecked")
List<T> ret = (List<T>)val;
return ret;
}
List<T> list = new ArrayList<T>();
if (val instanceof Collection) {
Collection<?> col = (Collection<?>)val;
for (Object element: col) {
@SuppressWarnings("unchecked")
T t = (T)element;
list.add(t);
}
return list;
}
@SuppressWarnings("unchecked")
T t = (T)val;
list.add(t);
return list;
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withList(String attrName, List<?> val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, valueConformer.transform(val));
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given values as a list.
*/
public Item withList(String attrName, Object ... vals) {
checkInvalidAttribute(attrName, vals);
List<Object> list_in = Arrays.asList(vals);
attributes.put(attrName, valueConformer.transform(list_in));
return this;
}
/**
* Returns the value of the specified attribute in the current item as a map
* of string-to-<code>T</code>'s; or null if the attribute either doesn't
* exist or the attribute value is null. Note that any numeric type of a
* map is always canonicalized into <code>BigDecimal</code>, and therefore
* if <code>T</code> referred to a <code>Number</code> type, it would need
* to be <code>BigDecimal</code> to avoid a class cast exception.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws ClassCastException
* if the attribute is not a map of string to <code>T</code>
*/
@SuppressWarnings("unchecked")
public <T> Map<String, T> getMap(String attrName) {
return (Map<String, T>)attributes.get(attrName);
}
/**
* Convenient method to return the specified attribute in the current item
* as a (copy of) map of string-to-<code>T</code>'s where T must be a
* subclass of <code>Number</code>; or null if the attribute doesn't
* exist.
*
* @param attrName
* the attribute name
* @param valueType
* the specific number type of the value to be returned.
* Currently, only<ul>
* <li><code>Short</code></li>
* <li><code>Integer</code></li>
* <li><code>Long</code></li>
* <li><code>Float</code></li>
* <li><code>Double</code></li>
* <li><code>Number</code></li>
* <li><code>BigDecimal</code></li>
* <li><code>BigInteger</code></li>
* </ul>are supported.
*
* @throws UnsupportedOperationException
* if the value type is not supported
* @throws ClassCastException
* if the attribute is not a map of string to numbers
*/
@SuppressWarnings("unchecked")
public <T extends Number> Map<String, T> getMapOfNumbers(String attrName,
Class<T> valueType) {
if (valueType == Short.class
|| valueType == Integer.class
|| valueType == Long.class
|| valueType == Float.class
|| valueType == Double.class
|| valueType == Number.class
|| valueType == BigDecimal.class
|| valueType == BigInteger.class) {
final Map<String, BigDecimal> src =
(Map<String, BigDecimal>)attributes.get(attrName);
if (src == null)
return null;
final Map<String, T> dst = new LinkedHashMap<String, T>(src.size());
for (Map.Entry<String,BigDecimal> e: src.entrySet()) {
final String key = e.getKey();
final BigDecimal val = e.getValue();
if (val == null) {
dst.put(key, null);
} else if (valueType == Short.class) {
dst.put(key, (T)Short.valueOf(val.shortValue()));
} else if (valueType == Integer.class) {
dst.put(key, (T)Integer.valueOf(val.intValue()));
} else if (valueType == Long.class) {
dst.put(key, (T)Long.valueOf(val.longValue()));
} else if (valueType == Float.class) {
dst.put(key, (T)Float.valueOf(val.floatValue()));
} else if (valueType == Double.class) {
dst.put(key, (T)Double.valueOf(val.doubleValue()));
} else if (valueType == BigDecimal.class || valueType == Number.class) {
dst.put(key, (T)val);
} else if (valueType == BigInteger.class) {
dst.put(key, (T)val.toBigInteger());
}
}
return dst;
} else {
throw new UnsupportedOperationException("Value type " + valueType
+ " is not currently supported");
}
}
/**
* Convenient method to return the value of the specified attribute in the
* current item as a map of string-to-<code>Object</code>'s; or null if the
* attribute either doesn't exist or the attribute value is null. Note that
* any numeric type of the map will be returned as <code>BigDecimal</code>.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*
* @throws ClassCastException if the attribute is not a map
*/
@SuppressWarnings("unchecked")
public Map<String, Object> getRawMap(String attrName) {
return (Map<String, Object>)attributes.get(attrName);
}
/**
* Sets the value of the specified attribute in the current item to the
* given value.
*/
public Item withMap(String attrName, Map<String, ?> val) {
checkInvalidAttribute(attrName, val);
attributes.put(attrName, valueConformer.transform(val));
return this;
}
/**
* Sets the value of the specified attribute in the current item to the
* given JSON document in the form of a string.
*/
public Item withJSON(String attrName, String json) {
checkInvalidAttribute(attrName, json);
attributes.put(attrName,
valueConformer.transform(Jackson.fromJsonString(json, Object.class)));
return this;
}
/**
* Returns the value of the specified attribute in the current item as a
* JSON string; or null if the attribute either doesn't
* exist or the attribute value is null.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public String getJSON(String attrName) {
checkInvalidAttrName(attrName);
Object val = attributes.get(attrName);
return val == null ? null : Jackson.toJsonString(val);
}
/**
* Returns the value of the specified attribute in the current item as a
* JSON string with pretty indentation; or null if the attribute either
* doesn't exist or the attribute value is null.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public String getJSONPretty(String attrName) {
checkInvalidAttrName(attrName);
Object val = attributes.get(attrName);
return val == null ? null : Jackson.toJsonPrettyString(val);
}
/**
* Returns the value of the specified attribute in the current item as a
* non-null Boolean.
*
* @throws IncompatibleTypeException
* if either the attribute doesn't exist or if the attribute
* value cannot be converted into a non-null Boolean value
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Boolean getBOOL(String attrName) {
final Object val = attributes.get(attrName);
if (val instanceof Boolean)
return (Boolean)val;
if (val instanceof String) {
if ("1".equals(val))
return true;
if ("0".equals(val))
return false;
return Boolean.valueOf((String)val);
}
throw new IncompatibleTypeException("Value of attribute " + attrName
+ " of type " + getTypeOf(attrName)
+ " cannot be converted into a boolean value");
}
/**
* Returns the value of the specified attribute in the current item as a
* primitive boolean.
*
* @throws IncompatibleTypeException
* if either the attribute doesn't exist or if the attribute
* value cannot be converted into a boolean value
*/
public boolean getBoolean(String attrName) {
final Boolean b = getBOOL(attrName);
return b.booleanValue();
}
/**
* Sets the value of the specified attribute in the current item to the
* boolean value.
*/
public Item withBoolean(String attrName, boolean val) {
checkInvalidAttrName(attrName);
attributes.put(attrName, Boolean.valueOf(val));
return this;
}
/**
* Sets the value of the specified attribute to null.
*/
public Item withNull(String attrName) {
checkInvalidAttrName(attrName);
attributes.put(attrName, null);
return this;
}
/**
* Sets the value of the specified attribute to the given value. An
* attribute value can be a
* <ul>
* <li>Number</li>
* <li>String</li>
* <li>binary (ie byte array or byte buffer)</li>
* <li>boolean</li>
* <li>null</li>
* <li>list (of any of the types on this list)</li>
* <li>map (with string key to value of any of the types on this list)</li>
* <li>set (of any of the types on this list)</li>
* </ul>
*/
public Item with(String attrName, Object val) {
if (val == null)
return withNull(attrName);
if (val instanceof String)
return withString(attrName, (String)val);
if (val instanceof Number)
return withNumber(attrName, (Number)val);
if (val instanceof byte[])
return withBinary(attrName, (byte[])val);
if (val instanceof ByteBuffer)
return withBinary(attrName, (ByteBuffer)val);
if (val instanceof Boolean)
return withBoolean(attrName, (Boolean)val);
if (val instanceof List)
return withList(attrName, (List<?>)val);
if (val instanceof Map) {
@SuppressWarnings("unchecked")
Map<String,?> map = (Map<String,?>)val;
return withMap(attrName, map);
}
if (val instanceof Set) {
Set<?> set = (Set<?>)val;
// Treat an empty set as a set of String
if (set.size() == 0) {
@SuppressWarnings("unchecked")
Set<String> ss = (Set<String>)val;
return withStringSet(attrName, ss);
}
// Try to locate the first non-null element and use that as the
// representative type
Object representative = null;
for (Object o: set) {
if (o != null)
representative = o;
}
// If all elements are null, treat the element type as String
if (representative == null || representative instanceof String) {
@SuppressWarnings("unchecked")
Set<String> ss = (Set<String>)val;
return withStringSet(attrName, ss);
}
if (representative instanceof Number) {
@SuppressWarnings("unchecked")
Set<Number> ns = (Set<Number>)val;
return withNumberSet(attrName, ns);
}
if (representative instanceof byte[]) {
@SuppressWarnings("unchecked")
Set<byte[]> bs = (Set<byte[]>)val;
return withBinarySet(attrName, bs);
}
if (representative instanceof ByteBuffer) {
@SuppressWarnings("unchecked")
Set<ByteBuffer> bs = (Set<ByteBuffer>)val;
return withByteBufferSet(attrName, bs);
}
throw new UnsupportedOperationException("Set of "
+ representative.getClass() + " is not currently supported");
}
throw new UnsupportedOperationException("Input type "
+ val.getClass() + " is not currently supported");
}
/**
* Convenient methods - sets the attributes of this item from the given
* key attributes.
*/
public Item withPrimaryKey(PrimaryKey primaryKey) {
rejectNullValue(primaryKey);
if (primaryKey.getComponents().size() == 0)
throw new IllegalArgumentException("primary key must not be empty");
for (KeyAttribute ka: primaryKey.getComponents())
this.with(ka.getName(), ka.getValue());
return this;
}
/**
* Convenient method to set the attributes of this item from the given
* hash-only primary key name and value.
*/
public Item withPrimaryKey(String hashKeyName, Object hashKeyValue) {
return withKeyComponent(hashKeyName, hashKeyValue);
}
/**
* Convenient method to set the attributes of this item from the given
* hash and range primary key.
*/
public Item withPrimaryKey(String hashKeyName, Object hashKeyValue,
String rangeKeyName, Object rangeKeyValue) {
return withKeyComponent(hashKeyName, hashKeyValue)
.withKeyComponent(rangeKeyName, rangeKeyValue);
}
/**
* Convenient methods - sets the attributes of this item from the specified
* key components.
*/
public Item withKeyComponents(KeyAttribute ...components) {
rejectNullOrEmptyInput(components);
for (KeyAttribute ka: components) {
rejectNullValue(ka);
this.with(ka.getName(), ka.getValue());
}
return this;
}
/**
* Convenient methods - sets an attribute of this item for the specified
* key attribute name and value.
*/
public Item withKeyComponent(String keyAttrName, Object keyAttrValue) {
return with(keyAttrName, keyAttrValue);
}
/**
* Returns the value of the specified attribute in the current item as an
* object; or null if the attribute either doesn't exist or the attribute
* value is null.
* <p>
* An attribute value can be a
* <ul>
* <li>Number</li>
* <li>String</li>
* <li>binary (ie byte array or byte buffer)</li>
* <li>boolean</li>
* <li>null</li>
* <li>list (of any of the types on this list)</li>
* <li>map (with string key to value of any of the types on this list)</li>
* <li>set (of any of the types on this list)</li>
* </ul>
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Object get(String attrName) {
return attributes.get(attrName);
}
/**
* Returns the type of the specified attribute in the current item; or null
* if the attribute either doesn't exist or the attribute value is null.
*
* @see #isNull(String) #isNull(String) to check if the attribute value is
* null.
* @see #isPresent(String) #isPresent(String) to check if the attribute
* value is present.
*/
public Class<?> getTypeOf(String attrName) {
Object val = attributes.get(attrName);
return val == null ? null : val.getClass();
}
/**
* Removes the specified attribute from the current item.
*/
public Item removeAttribute(String attrName) {
checkInvalidAttrName(attrName);
attributes.remove(attrName);
return this;
}
/**
* Returns all attributes of the current item.
*/
public Iterable<Entry<String, Object>> attributes() {
return new LinkedHashMap<String, Object>(attributes).entrySet();
}
/**
* Returns true if this item has the specified attribute; false otherwise.
*/
public boolean hasAttribute(String attrName) {
return attributes.containsKey(attrName);
}
/**
* Returns all attributes of the current item as a map.
*/
public Map<String, Object> asMap() {
return new LinkedHashMap<String,Object>(attributes);
}
/**
* Returns the number of attributes of this item.
*/
public int numberOfAttributes() {
return attributes.size();
}
/**
* Convenient factory method - instantiates an <code>Item</code> from the
* given map.
*
* @param attributes
* simple Java types; not the DyanmoDB types
*/
public static Item fromMap(Map<String, Object> attributes) {
if (attributes == null)
return null;
Item item = new Item();
for (Map.Entry<String, Object> e : attributes.entrySet())
item.with(e.getKey(), e.getValue());
return item;
}
/**
* Convenient factory method - instantiates an <code>Item</code> from the
* given JSON string.
*
* @return an <code>Item</code> initialized from the given JSON document;
* or null if the input is null.
*/
public static Item fromJSON(String json) {
if (json == null)
return null;
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>)
valueConformer.transform(Jackson.fromJsonString(json, Map.class));
return fromMap(map);
}
/**
* Returns this item as a JSON string. Note all binary data will become
* base-64 encoded in the resultant string.
*/
public String toJSON() {
return Jackson.toJsonString(this.attributes);
}
/**
* Utility method to decode the designated binary attributes from base-64
* encoding; converting binary lists into binary sets.
*
* @param binaryAttrNames
* names of binary attributes or binary set attributes currently
* base-64 encoded (typically when converted from a JSON string.)
*
* @see #fromJSON(String)
*/
public Item base64Decode(String ... binaryAttrNames) {
rejectNullInput(binaryAttrNames);
// Verify all attributes are good
for (String attrName: binaryAttrNames) {
checkInvalidAttrName(attrName);
if (String.class == getTypeOf(attrName)) {
String b64 = getString(attrName);
Base64.decode(b64);
} else {
Set<String> b64s = getStringSet(attrName);
for (String b64: b64s)
Base64.decode(b64);
}
}
// Decodes b64 into binary
for (String attrName: binaryAttrNames) {
if (String.class == getTypeOf(attrName)) {
String b64 = getString(attrName);
byte[] bytes = Base64.decode(b64);
withBinary(attrName, bytes);
} else {
Set<String> b64s = getStringSet(attrName);
Set<byte[]> binarySet = new LinkedHashSet<byte[]>(b64s.size());
for (String b64: b64s)
binarySet.add(Base64.decode(b64));
withBinarySet(attrName, binarySet);
}
}
return this;
}
/**
* Utility method to converts the designated attributes from
* <code>List</code> into <code>Set</code>, throwing
* <code>IllegalArgumentException</code> should there be duplicate elements.
*
* @param listAttrNames
* names of attributes to be converted.
*
* @see #fromJSON(String)
*/
public Item convertListsToSets(String ... listAttrNames) {
rejectNullInput(listAttrNames);
// Verify all attributes are good
for (String attrName: listAttrNames) {
checkInvalidAttrName(attrName);
if (List.class.isAssignableFrom(getTypeOf(attrName))) {
List<?> list = getList(attrName);
if (list != null) {
for (Object e: list) {
if (e instanceof String) {
Set<String> ss = getStringSet(attrName);
if (list.size() != ss.size())
throw new IllegalArgumentException("List cannot be converted to Set due to duplicate elements");
} else if (e instanceof Number) {
Set<BigDecimal> ss = getNumberSet(attrName);
if (list.size() != ss.size())
throw new IllegalArgumentException("List cannot be converted to Set due to duplicate elements");
} else if (e instanceof byte[]) {
Set<byte[]> ss = getBinarySet(attrName);
if (list.size() != ss.size())
throw new IllegalArgumentException("List cannot be converted to Set due to duplicate elements");
}
}
}
} else {
throw new IllegalArgumentException("Attribute " + attrName + " is not a list");
}
}
// Do the conversion
for (String attrName: listAttrNames) {
checkInvalidAttrName(attrName);
List<?> list = getList(attrName);
if (list != null) {
boolean converted = false;
for (Object e: list) {
if (e instanceof String) {
Set<String> set = getStringSet(attrName);
withStringSet(attrName, set);
converted = true;
break;
} else if (e instanceof Number) {
Set<BigDecimal> set = getNumberSet(attrName);
withBigDecimalSet(attrName, set);
converted = true;
break;
} else if (e instanceof byte[]) {
Set<byte[]> set = getBinarySet(attrName);
withBinarySet(attrName, set);
converted = true;
break;
}
}
if (!converted) {
// All elements are null. So treat it as a String set.
Set<String> set = getStringSet(attrName);
withStringSet(attrName, set);
}
}
}
return this;
}
/**
* Returns this item as a pretty JSON string. Note all binary data will
* become base-64 encoded in the resultant string.
*/
public String toJSONPretty() {
return Jackson.toJsonPrettyString(this.attributes);
}
@Override
public String toString() {
return "{ Item: " + attributes.toString() + " }";
}
@Override
public int hashCode() {
return attributes.hashCode();
}
@Override
public boolean equals(Object in) {
if (in instanceof Item) {
Item that = (Item)in;
return this.attributes.equals(that.attributes);
} else {
return false;
}
}
}