/*
* Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson
*
* 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 org.iternine.jeppetto.dao.dynamodb;
import org.iternine.jeppetto.dao.JeppettoException;
import org.iternine.jeppetto.dao.persistable.PersistableList;
import org.iternine.jeppetto.dao.persistable.PersistableMap;
import org.iternine.jeppetto.enhance.Enhancer;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ConversionUtil {
//-------------------------------------------------------------
// Constants
//-------------------------------------------------------------
private static final Set<Class> STORE_AS_NUMBER_CLASSES = new HashSet<Class>() {{
add(Date.class);
add(byte.class);
add(short.class);
add(int.class);
add(long.class);
add(float.class);
add(double.class);
add(Number.class);
add(BigDecimal.class);
add(BigInteger.class);
add(Byte.class);
add(Short.class);
add(Integer.class);
add(Long.class);
add(Float.class);
add(Double.class);
}};
//-------------------------------------------------------------
// Methods - Public - Static
//-------------------------------------------------------------
@SuppressWarnings("unchecked")
public static Map<String, AttributeValue> getItemFromObject(final DynamoDBPersistable dynamoDBPersistable) {
Map<String, AttributeValue> itemMap = new HashMap<String, AttributeValue>();
for (Iterator<String> dirtyFields = dynamoDBPersistable.__getDirtyFields(); dirtyFields.hasNext(); ) {
String dirtyField = dirtyFields.next();
Object object = dynamoDBPersistable.__get(dirtyField);
// We don't save nulls. If it turns out we need it, we could have a "saveNulls" configuration value.
// If saveNulls and attributeValue == null: itemMap.put(dirtyField, new AttributeValue().withNULL(Boolean.TRUE));
// Note we might want something like a thread local 'PersistenceContext' with saveNulls or have a
// DynamoDBPersistable.saveNulls(saveNulls) method
if (object != null) {
itemMap.put(dirtyField, toAttributeValue(object));
}
}
return itemMap;
}
public static <T> AttributeValue toAttributeValue(final T value) {
return toAttributeValue(value, null);
}
/*
* In the future, we may want to support storing associated objects w/in this value in different
* DynamoDB tables. In that case, as this method was navigating the value and came upon a ...
* TODO: finish comment
*/
@SuppressWarnings("unchecked")
public static <T> AttributeValue toAttributeValue(final T value, final Class collectionType) {
if (value == null) {
return null;
} else if (String.class.isAssignableFrom(value.getClass())) {
return new AttributeValue((String) value);
} else if (Number.class.isAssignableFrom(value.getClass())) {
return new AttributeValue().withN(value.toString());
} else if (Boolean.class.isAssignableFrom(value.getClass())) {
return new AttributeValue().withBOOL((Boolean) value);
} else if (Character.class.isAssignableFrom(value.getClass())) {
return new AttributeValue(value.toString());
} else if (Byte.class.isAssignableFrom(value.getClass())) {
return new AttributeValue().withN(value.toString());
} else if (byte[].class.isAssignableFrom(value.getClass())) {
return new AttributeValue().withB(ByteBuffer.wrap((byte[]) value));
} else if (Date.class.isAssignableFrom(value.getClass())) {
return new AttributeValue().withN(Long.toString(((Date) value).getTime()));
} else if (Enum.class.isAssignableFrom(value.getClass())) {
return new AttributeValue(((Enum) value).name());
} else if (Set.class.isAssignableFrom(value.getClass())) {
Set valueSet = (Set) value;
Set<String> strings = new HashSet<String>(valueSet.size());
for (Object next : valueSet) {
strings.add(toString(next));
}
if (collectionType != null && STORE_AS_NUMBER_CLASSES.contains(collectionType)) {
return new AttributeValue().withNS(strings);
} else {
return new AttributeValue().withSS(strings);
}
} else if (List.class.isAssignableFrom(value.getClass())) {
List valueList = (List) value;
List<AttributeValue> attributeValues = new ArrayList<AttributeValue>(valueList.size());
for (Object next : valueList) {
attributeValues.add(toAttributeValue(next));
}
return new AttributeValue().withL(attributeValues);
} else if (Map.class.isAssignableFrom(value.getClass())) {
Map<String, ?> valueMap = (Map<String, Object>) value;
Map<String, AttributeValue> attributeValueMap = new HashMap<String, AttributeValue>(valueMap.size());
for (Map.Entry<String, ?> entry : valueMap.entrySet()) {
attributeValueMap.put(entry.getKey(), toAttributeValue(entry.getValue()));
}
return new AttributeValue().withM(attributeValueMap);
} else if (AttributeValue.class.isAssignableFrom(value.getClass())) {
return (AttributeValue) value;
} else {
Enhancer<T> enhancer = EnhancerHelper.getPersistableEnhancer((Class<T>) value.getClass());
DynamoDBPersistable dynamoDBPersistable = (DynamoDBPersistable) enhancer.enhance(value);
return new AttributeValue().withM(getItemFromObject(dynamoDBPersistable));
}
}
public static <T> T getObjectFromItem(final Map<String, AttributeValue> item, final Class<T> targetType) {
T t = EnhancerHelper.getPersistableEnhancer(targetType).newInstance();
((DynamoDBPersistable) t).__putAll(item);
return t;
}
public static Object fromAttributeValue(final AttributeValue attributeValue, final Class targetType,
final Class collectionType) {
// TODO: byte[]
if (String.class.isAssignableFrom(targetType)) {
return attributeValue.getS();
} else if (Integer.class.isAssignableFrom(targetType) || int.class.isAssignableFrom(targetType)) {
return Integer.valueOf(attributeValue.getN());
} else if (Long.class.isAssignableFrom(targetType) || long.class.isAssignableFrom(targetType)) {
return Long.valueOf(attributeValue.getN());
} else if (Double.class.isAssignableFrom(targetType) || double.class.isAssignableFrom(targetType)) {
return Double.valueOf(attributeValue.getN());
} else if (Float.class.isAssignableFrom(targetType) || float.class.isAssignableFrom(targetType)) {
return Float.valueOf(attributeValue.getN());
} else if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) {
return attributeValue.getBOOL();
} else if (Character.class.isAssignableFrom(targetType) || char.class.isAssignableFrom(targetType)) {
return attributeValue.getS().charAt(0);
} else if (Byte.class.isAssignableFrom(targetType) || byte.class.isAssignableFrom(targetType)) {
return Byte.valueOf(attributeValue.getN());
} else if (Short.class.isAssignableFrom(targetType) || short.class.isAssignableFrom(targetType)) {
return Short.valueOf(attributeValue.getN());
} else if (Date.class.isAssignableFrom(targetType)) {
return new Date(Long.parseLong(attributeValue.getN()));
} else if (Enum.class.isAssignableFrom(targetType)) {
//noinspection unchecked
return Enum.valueOf((Class<Enum>) targetType, attributeValue.getS());
} else if (Set.class.isAssignableFrom(targetType)) {
Set result = new HashSet();
List<String> strings;
if (attributeValue.getSS() != null) {
strings = attributeValue.getSS();
} else {
strings = attributeValue.getNS();
}
for (String string : strings) {
//noinspection unchecked
result.add(fromString(string, collectionType));
}
return result;
} else if (List.class.isAssignableFrom(targetType)) {
List<AttributeValue> attributeValues = attributeValue.getL();
List result = new PersistableList(attributeValues.size());
for (AttributeValue value : attributeValues) {
// NB: We currently only pick up the first type level for an attribute value. So if, for example,
// someone had a List<List<Integer>>, we would not be able to rebuild it. The solution is
// TODO: change the way this works to handle nested types?
//noinspection unchecked
result.add(fromAttributeValue(value, collectionType, null));
}
return result;
} else if (Map.class.isAssignableFrom(targetType)) {
Map<String, AttributeValue> attributeValues = attributeValue.getM();
Map result = new PersistableMap(attributeValues.size());
for (Map.Entry<String, AttributeValue> entry : attributeValues.entrySet()) {
//noinspection unchecked
result.put(entry.getKey(), fromAttributeValue(entry.getValue(), collectionType, null));
}
return result;
} else {
return getObjectFromItem(attributeValue.getM(), targetType);
}
}
public static Collection<AttributeValue> toAttributeValueList(Object value) {
Collection<AttributeValue> attributeValueList = new ArrayList<AttributeValue>();
if (value.getClass().isArray()) {
int length = Array.getLength(value);
for (int i = 0; i < length; i ++) {
attributeValueList.add(toAttributeValue(Array.get(value, i)));
}
} else if (Collection.class.isAssignableFrom(value.getClass())) {
for (Object item : (Collection) value) {
attributeValueList.add(toAttributeValue(item));
}
} else {
throw new JeppettoException("Expected either array or Collection object.");
}
return attributeValueList;
}
//-------------------------------------------------------------
// Methods - Private
//-------------------------------------------------------------
private static String toString(Object value) {
if (Date.class.isAssignableFrom(value.getClass())) {
return Long.toString(((Date) value).getTime());
} else if (Enum.class.isAssignableFrom(value.getClass())) {
return ((Enum) value).name();
}
return value.toString();
}
private static Object fromString(String string, Class type) {
if (type == null || String.class.isAssignableFrom(type)) {
return string;
} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
return Integer.valueOf(string);
} else if (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)) {
return Long.valueOf(string);
} else if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {
return Double.valueOf(string);
} else if (Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) {
return Float.valueOf(string);
} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
return Boolean.valueOf(string);
} else if (Character.class.isAssignableFrom(type) || char.class.isAssignableFrom(type)) {
return string.charAt(0);
} else if (Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type)) {
return Byte.valueOf(string);
} else if (Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type)) {
return Short.valueOf(string);
} else if (Date.class.isAssignableFrom(type)) {
return new Date(Long.parseLong(string));
} else if (Enum.class.isAssignableFrom(type)) {
//noinspection unchecked
return Enum.valueOf((Class<Enum>) type, string);
}
throw new RuntimeException("Unhandled type: " + type);
}
}