/*
Copyright (c) 2012 LinkedIn Corp.
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.linkedin.data.template;
import com.linkedin.data.ByteString;
import com.linkedin.data.DataComplex;
import com.linkedin.data.DataList;
import com.linkedin.data.DataMap;
import com.linkedin.data.TestUtil;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.MapDataSchema;
import com.linkedin.data.schema.RecordDataSchema;
import java.lang.reflect.Constructor;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.testng.annotations.Test;
import static com.linkedin.data.TestUtil.asMap;
import static com.linkedin.data.TestUtil.noCommonDataComplex;
import static com.linkedin.data.TestUtil.out;
import static org.testng.Assert.*;
/**
* Unit tests for map {@link DataTemplate}'s.
*
* @author slim
*/
public class TestMapTemplate
{
static <E> void assertCollectionEquals(Collection<E> c1, Collection<E> c2)
{
assertTrue(c1.containsAll(c2));
assertTrue(c2.containsAll(c1));
}
public static <MapTemplate extends AbstractMapTemplate<E>, E>
void testMap(Class<MapTemplate> templateClass,
MapDataSchema schema,
Map<String, E> input,
Map<String, E> adds)
{
try
{
Exception exc = null;
// constructor and putall
MapTemplate map1 = templateClass.newInstance();
map1.putAll(input);
assertEquals(map1, input);
/*
Constructor[] constructors = templateClass.getConstructors();
for (Constructor c : constructors)
{
out.println(c);
}
*/
try
{
int size = input.size();
// constructor(int capacity)
Constructor<MapTemplate> capacityConstructor = templateClass.getConstructor(int.class);
MapTemplate map = capacityConstructor.newInstance(input.size());
assertEquals(map, Collections.emptyMap());
map.putAll(input);
assertEquals(input, map);
map.clear();
assertEquals(size, input.size());
// constructor(int capacity, float loadFactor)
Constructor<MapTemplate> loadFactorConstructor = templateClass.getConstructor(int.class, float.class);
map = loadFactorConstructor.newInstance(input.size(), input.size() * 1.4f);
assertEquals(map, Collections.emptyMap());
map.putAll(input);
assertEquals(input, map);
map.clear();
assertEquals(size, input.size());
// constructor(Map<String, E>)
Constructor<MapTemplate> mapConstructor = templateClass.getConstructor(Map.class);
map = mapConstructor.newInstance(input);
assertEquals(input, map);
map.clear();
assertEquals(size, input.size());
// constructor(DataMap)
Constructor<MapTemplate> dataMapConstructor = templateClass.getConstructor(DataMap.class);
map = dataMapConstructor.newInstance(map1.data());
assertEquals(map1, map);
assertEquals(input, map);
map.clear();
assertEquals(map1, map);
}
catch (Exception e)
{
assertSame(e, null);
}
// test wrapping
map1.clear();
map1.putAll(input);
DataMap dataMap2 = new DataMap();
MapTemplate map2 = DataTemplateUtil.wrap(dataMap2, schema, templateClass);
for (Map.Entry<String, E> e : input.entrySet())
{
E v = e.getValue();
Object o;
if (v instanceof DataTemplate)
{
o = ((DataTemplate<?>) v).data();
}
else if (v instanceof Enum)
{
o = v.toString();
}
else
{
o = v;
}
dataMap2.put(e.getKey(), o);
assertEquals(map2.hashCode(), dataMap2.hashCode());
}
assertEquals(map1, map2);
MapTemplate map2a = DataTemplateUtil.wrap(dataMap2, templateClass);
assertEquals(map1, map2a);
assertSame(map2a.data(), map2.data());
// valueClass()
assertSame(map1.valueClass(), input.values().iterator().next().getClass());
// equals(), hashCode()
map1.clear();
map1.putAll(input);
assertTrue(map1.equals(map1));
assertTrue(map1.equals(input));
assertFalse(map1.equals(null));
assertFalse(map1.equals(adds));
assertFalse(map1.equals(new HashMap<String,E>()));
map2.clear();
Map<String,E> hashMap2 = new HashMap<String,E>();
List<Map.Entry<String,E>> inputList = new ArrayList<Map.Entry<String, E>>(input.entrySet());
int lastHash = 0;
for (int i = 0; i < inputList.size(); ++i)
{
Map.Entry<String,E> entry = inputList.get(i);
map2.put(entry.getKey(), entry.getValue());
hashMap2.put(entry.getKey(), entry.getValue());
if (i == (inputList.size() - 1))
{
assertTrue(map1.equals(map2));
assertTrue(map1.equals(hashMap2));
}
else
{
assertFalse(map1.equals(map2));
assertFalse(map1.equals(hashMap2));
}
int newHash = map2.hashCode();
if (i > 0)
{
assertFalse(lastHash == newHash);
}
lastHash = newHash;
}
// schema()
MapDataSchema schema1 = map1.schema();
assertTrue(schema1 != null);
assertEquals(schema1.getType(), DataSchema.Type.MAP);
assertEquals(schema1, schema);
// get(Object key), put(K key, V value, containsKey(Object key), containsValue(Object value), toString
MapTemplate map3 = templateClass.newInstance();
for (Map.Entry<String, E> e : input.entrySet())
{
String key = e.getKey();
E value = e.getValue();
E replaced = map3.put(key, value);
assertTrue(replaced == null);
E got = map3.get(key);
assertTrue(got != null);
assertEquals(value, got);
assertSame(value, got);
assertTrue(map3.containsKey(key));
assertTrue(map3.containsValue(value));
assertTrue(map3.toString().contains(key + "=" + value));
}
assertNull(map3.get(null));
assertNull(map3.get(1));
assertNull(map3.get(new Object()));
assertNull(map3.get("not found"));
assertEquals(map3, input);
Iterator<Map.Entry<String, E>> e2 = adds.entrySet().iterator();
for (Map.Entry<String, E> e : input.entrySet())
{
if (e2.hasNext() == false)
break;
E newValue = e2.next().getValue();
String key = e.getKey();
E value = e.getValue();
assertTrue(map3.containsKey(key));
assertTrue(map3.containsValue(value));
E replaced = map3.put(key, newValue);
assertTrue(replaced != null);
assertEquals(replaced, value);
assertSame(replaced, value);
assertTrue(map3.containsKey(key));
assertTrue(map3.containsValue(newValue));
assertTrue(map3.toString().contains(key));
assertTrue(map3.toString().contains(newValue.toString()));
}
// put(String key, E value) with replacement of existing value
map3.clear();
String testKey = "testKey";
E lastValue = null;
for (Map.Entry<String, E> e : input.entrySet())
{
E putResult = map3.put(testKey, e.getValue());
if (lastValue != null)
{
assertEquals(putResult, lastValue);
}
lastValue = e.getValue();
}
// remove(Object key), containsKey(Object key), containsValue(Object value)
MapTemplate map4 = templateClass.newInstance();
map4.putAll(input);
int map4Size = map4.size();
for (Map.Entry<String, E> e : input.entrySet())
{
String key = e.getKey();
E value = e.getValue();
assertTrue(map4.containsKey(key));
assertTrue(map4.containsValue(value));
E removed = map4.remove(key);
assertTrue(removed != null);
assertEquals(value, removed);
assertSame(value, removed);
assertFalse(map4.containsKey(key));
assertFalse(map4.containsValue(value));
map4Size--;
assertEquals(map4Size, map4.size());
}
assertTrue(map4.isEmpty());
assertTrue(map4.size() == 0);
assertFalse(map4.containsValue(null));
assertFalse(map4.containsValue(new StringArray()));
// clone and copy return types
TestDataTemplateUtil.assertCloneAndCopyReturnType(templateClass);
// clone
exc = null;
map4 = templateClass.newInstance();
map4.putAll(input);
try
{
exc = null;
@SuppressWarnings("unchecked")
MapTemplate map4Clone = (MapTemplate) map4.clone();
assertTrue(map4Clone.getClass() == templateClass);
assertEquals(map4Clone, map4);
assertTrue(map4Clone != map4);
for (Map.Entry<String, Object> entry : map4.data().entrySet())
{
if (entry.getValue() instanceof DataComplex)
{
assertSame(map4Clone.data().get(entry.getKey()), entry.getValue());
}
}
String key = map4Clone.keySet().iterator().next();
map4Clone.remove(key);
assertEquals(map4Clone.size(), map4.size() - 1);
assertFalse(map4Clone.equals(map4));
assertTrue(map4.entrySet().containsAll(map4Clone.entrySet()));
map4.remove(key);
assertEquals(map4Clone, map4);
}
catch (CloneNotSupportedException e)
{
exc = e;
}
assert(exc == null);
//copy
MapTemplate map4a = templateClass.newInstance();
map4a.putAll(input);
try
{
@SuppressWarnings("unchecked")
MapTemplate map4aCopy = (MapTemplate) map4a.copy();
assertTrue(map4aCopy.getClass() == templateClass);
assertEquals(map4a, map4aCopy);
boolean hasComplex = false;
for (Map.Entry<String, Object> entry : map4a.data().entrySet())
{
if (entry.getValue() instanceof DataComplex)
{
assertNotSame(map4aCopy.data().get(entry.getKey()), entry.getValue());
hasComplex = true;
}
}
assertTrue(DataTemplate.class.isAssignableFrom(map4a._valueClass) == false || hasComplex);
assertTrue(noCommonDataComplex(map4a.data(), map4aCopy.data()));
boolean mutated = false;
for (Object value : map4aCopy.data().values())
{
if (value instanceof DataComplex)
{
mutated |= TestUtil.mutateDataComplex((DataComplex) value);
}
}
assertEquals(mutated, hasComplex);
if (mutated)
{
assertNotEquals(map4aCopy, map4a);
}
else
{
assertEquals(map4aCopy, map4a);
String key = map4aCopy.keySet().iterator().next();
map4aCopy.remove(key);
assertFalse(map4aCopy.containsKey(key));
assertTrue(map4a.containsKey(key));
}
}
catch (CloneNotSupportedException e)
{
exc = e;
}
assert(exc == null);
// entrySet, keySet, values, clear
MapTemplate map5 = templateClass.newInstance();
map5.putAll(input);
assertEquals(map5.entrySet(), input.entrySet());
assertCollectionEquals(map5.entrySet(), input.entrySet());
assertEquals(map5.keySet(), input.keySet());
assertCollectionEquals(map5.keySet(), input.keySet());
assertCollectionEquals(map5.values(), input.values());
assertEquals(map5.size(), input.size());
assertFalse(map5.isEmpty());
map5.clear();
assertEquals(map5.size(), 0);
assertTrue(map5.isEmpty());
map5.putAll(adds);
assertEquals(map5.entrySet(), adds.entrySet());
assertEquals(map5.keySet(), adds.keySet());
assertCollectionEquals(map5.values(), adds.values());
assertEquals(map5.size(), adds.size());
assertFalse(map5.isEmpty());
map5.clear();
assertEquals(map5.size(), 0);
assertTrue(map5.isEmpty());
// entrySet contains
MapTemplate map6 = templateClass.newInstance();
Set<Map.Entry<String, E>> entrySet6 = map6.entrySet();
for (Map.Entry<String, E> e : input.entrySet())
{
assertFalse(entrySet6.contains(e));
map6.put(e.getKey(), e.getValue());
assertTrue(entrySet6.contains(e));
}
for (Map.Entry<String, E> e : adds.entrySet())
{
assertFalse(entrySet6.contains(e));
}
assertFalse(entrySet6.contains(null));
assertFalse(entrySet6.contains(1));
assertFalse(entrySet6.contains(new Object()));
assertFalse(entrySet6.contains(new AbstractMap.SimpleEntry<String, Object>(null, null)));
assertFalse(entrySet6.contains(new AbstractMap.SimpleEntry<String, Object>("xxxx", null)));
assertFalse(entrySet6.contains(new AbstractMap.SimpleEntry<String, Object>("xxxx", "xxxx")));
assertFalse(entrySet6.contains(new AbstractMap.SimpleEntry<String, Object>("xxxx", new Object())));
// entrySet iterator
for (Map.Entry<String, E> e : map6.entrySet())
{
assertTrue(map6.containsKey(e.getKey()));
assertTrue(map6.containsValue(e.getValue()));
}
try
{
exc = null;
map6.entrySet().iterator().remove();
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
// entrySet containsAll
assertTrue(map6.entrySet().containsAll(input.entrySet()));
assertTrue(input.entrySet().containsAll(map6.entrySet()));
// entrySet add
for (Map.Entry<String, E> e : input.entrySet())
{
try
{
exc = null;
entrySet6.add(e);
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
}
// entrySet remove
for (Map.Entry<String, E> e : input.entrySet())
{
try
{
exc = null;
entrySet6.remove(e);
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
}
Object invalidEntries[] = { null, 1, new Object() };
for (Object e : invalidEntries)
{
try
{
exc = null;
entrySet6.remove(e);
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
}
// entrySet addAll
try
{
exc = null;
entrySet6.addAll(adds.entrySet());
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
// entrySet clear
try
{
exc = null;
entrySet6.clear();
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
// entrySet removeAll
Collection<?> collectionsToRemove[] = { adds.entrySet(), Collections.emptySet() };
for (Collection<?> c : collectionsToRemove)
{
try
{
exc = null;
entrySet6.removeAll(c);
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
}
// entrySet retainAll
try
{
exc = null;
entrySet6.retainAll(adds.entrySet());
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc != null);
assertTrue(exc instanceof UnsupportedOperationException);
// entrySet equals, isEmpty
MapTemplate map7 = templateClass.newInstance();
MapTemplate map8 = templateClass.newInstance();
map8.putAll(input);
Set<Map.Entry<String, E>> entrySet7 = map7.entrySet();
assertTrue(entrySet7.isEmpty());
Map<String, E> hashMap7 = new HashMap<String, E>();
lastHash = 0;
for (int i = 0; i < inputList.size(); ++i)
{
Map.Entry<String,E> entry = inputList.get(i);
map7.put(entry.getKey(), entry.getValue());
hashMap7.put(entry.getKey(), entry.getValue());
if (i == (inputList.size() - 1))
{
assertTrue(entrySet7.equals(map8.entrySet()));
}
else
{
assertFalse(entrySet7.equals(map8.entrySet()));
assertTrue(entrySet7.equals(hashMap7.entrySet()));
}
assertTrue(entrySet7.toString().contains(entry.getKey()));
assertTrue(entrySet7.toString().contains(entry.getValue().toString()));
int newHash = entrySet7.hashCode();
if (i > 0)
{
assertFalse(lastHash == newHash);
}
lastHash = newHash;
assertFalse(entrySet7.isEmpty());
}
assertTrue(entrySet7.equals(input.entrySet()));
assertFalse(map7.entrySet().equals(null));
assertFalse(map7.entrySet().equals(new Object()));
// test Map.Entry.set()
MapTemplate map9 = templateClass.newInstance();
map9.putAll(input);
lastValue = null;
for (Map.Entry<String, E> e : map9.entrySet())
{
E value = e.getValue();
if (lastValue != null)
{
exc = null;
try
{
E ret = e.setValue(lastValue);
/* CowMap entrySet() returns unmodifiable view.
This may change if don't use CowMap to back DataMap.
assertEquals(ret, value);
assertEquals(e.getValue(), lastValue);
assertEquals(map9.get(e.getKey()), lastValue);
*/
}
catch (Exception ex)
{
exc = ex;
}
assertTrue(exc instanceof UnsupportedOperationException);
}
lastValue = value;
}
}
catch (IllegalAccessException exc)
{
fail("Unexpected exception", exc);
}
catch (InstantiationException exc)
{
fail("Unexpected exception", exc);
}
}
public <MapTemplate extends AbstractMapTemplate<E>, E>
void testMapBadInput(Class<MapTemplate> templateClass,
MapDataSchema schema,
Map<String, E> good,
Map<String, Object> badInput,
Map<String, Object> badOutput)
{
MapTemplate mapTemplateBad = null;
try
{
mapTemplateBad = templateClass.newInstance();
}
catch (IllegalAccessException exc)
{
fail("Unexpected exception", exc);
}
catch (InstantiationException exc)
{
fail("Unexpected exception", exc);
}
Exception exc = null;
DataMap badDataMap = new DataMap();
@SuppressWarnings("unchecked")
Map<String, E> badIn = (Map<String, E>) badInput;
// put(String key, E element)
for (Map.Entry<String, E> entry : badIn.entrySet())
{
exc = null;
E value = entry.getValue();
try
{
mapTemplateBad.put(entry.getKey(), value);
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(value == null || exc instanceof ClassCastException);
assertTrue(value != null || exc instanceof NullPointerException);
}
// putAll(Collection<E> c)
try
{
exc = null;
mapTemplateBad.putAll(badIn);
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof ClassCastException);
badDataMap.clear();
badDataMap.putAll(badOutput);
MapTemplate badWrappedMapTemplate = DataTemplateUtil.wrap(badDataMap, schema, templateClass);
// Get bad
for (String key : badOutput.keySet())
{
try
{
exc = null;
badWrappedMapTemplate.get(key);
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof TemplateOutputCastException);
}
// Set returns bad
badDataMap.clear();
badDataMap.putAll(badOutput);
assertEquals(badWrappedMapTemplate.size(), badOutput.size());
for (String key : badOutput.keySet())
{
try
{
exc = null;
badWrappedMapTemplate.put(key, good.get(good.keySet().iterator().next()));
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof TemplateOutputCastException);
}
// Remove returns bad
badDataMap.clear();
badDataMap.putAll(badOutput);
assertEquals(badWrappedMapTemplate.size(), badOutput.size());
for (String key : badOutput.keySet())
{
try
{
exc = null;
badWrappedMapTemplate.remove(key);
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof TemplateOutputCastException);
}
// entrySet returns bad
badDataMap.clear();
badDataMap.putAll(badOutput);
for (Map.Entry<String, E> entry : badWrappedMapTemplate.entrySet())
{
try
{
exc = null;
entry.getValue();
}
catch (Exception e)
{
exc = e;
}
assertTrue(exc != null);
assertTrue(exc instanceof TemplateOutputCastException);
}
}
@SuppressWarnings("unchecked")
public <MapTemplate extends AbstractMapTemplate<E>, E extends Number>
void testNumberMap(Class<MapTemplate> templateClass,
MapDataSchema schema,
Map<String, E> castTo,
Map<String, ? extends Number> castFrom)
{
try
{
// test insert non-native, converted to element type on set
MapTemplate map1 = templateClass.newInstance();
map1.putAll((Map<String, E>) castFrom);
for (String i : castFrom.keySet())
{
assertEquals(castTo.get(i), map1.get(i));
assertEquals(map1.data().get(i), castTo.get(i));
}
// test underlying is non-native, convert on get to element type on get.
DataMap dataList2 = new DataMap(castFrom);
MapTemplate map2 = DataTemplateUtil.wrap(dataList2, schema, templateClass);
for (String i : castTo.keySet())
{
assertSame(dataList2.get(i), castFrom.get(i));
assertEquals(castTo.get(i), map2.get(i));
}
// test entrySet, convert on access to element type
int lastHash = 0;
boolean first = true;
for (Map.Entry<String, E> e : map2.entrySet())
{
String key = e.getKey();
E castToValue = castTo.get(key);
assertEquals(e.getValue(), castToValue);
assertTrue(e.equals(new AbstractMap.SimpleEntry<String,E>(key, castToValue)));
assertFalse(e.equals(null));
assertFalse(e.equals(new Object()));
String eToString = e.toString();
assertEquals(eToString, key + "=" + castToValue.toString());
// hashCode
int newHash = e.hashCode();
if (!first)
{
assertTrue(lastHash != newHash);
}
first = false;
lastHash = newHash;
}
}
catch (IllegalAccessException exc)
{
fail("Unexpected exception", exc);
}
catch (InstantiationException exc)
{
fail("Unexpected exception", exc);
}
}
@Test
public void testBooleanMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"boolean\" }");
Map<String, Boolean> input = asMap("true", true, "false", false);
Map<String, Boolean> adds = asMap("thirteen", true, "seventeen", false, "nineteen", true);
Map<String, Object> badInput = asMap("integer", 99, "long", 999L, "float", 88.0f, "double", 888.0, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("integer", 99, "long", 999L, "float", 88.0f, "double", 888.0, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(BooleanMap.class, schema, input, adds);
testMapBadInput(BooleanMap.class, schema, input, badInput, badOutput);
}
@Test
public void testIntegerMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"int\" }");
Map<String, Integer> input = asMap("one", 1, "three", 3, "five", 5, "seven", 7, "eleven", 11);
Map<String, Integer> adds = asMap("thirteen", 13, "seventeen", 17, "nineteen", 19);
Map<String, Object> badInput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(IntegerMap.class, schema, input, adds);
testMapBadInput(IntegerMap.class, schema, input, badInput, badOutput);
Map<String, ? extends Number> castFrom = asMap("one", 1L, "three", 3.0f, "five", 5.0, "seven", 7, "eleven", 11);
testNumberMap(IntegerMap.class, schema, input, castFrom);
}
@Test
public void testLongMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"long\" }");
Map<String, Long> input = asMap("one", 1L, "three", 3L, "five", 5L, "seven", 7L, "eleven", 11L);
Map<String, Long> adds = asMap("thirteen", 13L, "seventeen", 17L, "nineteen", 19L);
Map<String, Object> badInput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(LongMap.class, schema, input, adds);
testMapBadInput(LongMap.class, schema, input, badInput, badOutput);
Map<String, ? extends Number> castFrom = asMap("one", 1, "three", 3.0f, "five", 5.0, "seven", 7, "eleven", 11);
testNumberMap(LongMap.class, schema, input, castFrom);
}
@Test
public void testFloatMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"float\" }");
Map<String, Float> input = asMap("one", 1.0f, "three", 3.0f, "five", 5.0f, "seven", 7.0f, "eleven", 11.0f);
Map<String, Float> adds = asMap("thirteen", 13.0f, "seventeen", 17.0f, "nineteen", 19.0f);
Map<String, Object> badInput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(FloatMap.class, schema, input, adds);
testMapBadInput(FloatMap.class, schema, input, badInput, badOutput);
Map<String, ? extends Number> castFrom = asMap("one", 1, "three", 3L, "five", 5.0, "seven", 7, "eleven", 11);
testNumberMap(FloatMap.class, schema, input, castFrom);
}
@Test
public void testDoubleMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"double\" }");
Map<String, Double> input = asMap("one", 1.0, "three", 3.0, "five", 5.0, "seven", 7.0, "eleven", 11.0);
Map<String, Double> adds = asMap("thirteen", 13.0, "seventeen", 17.0, "nineteen", 19.0);
Map<String, Object> badInput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(DoubleMap.class, schema, input, adds);
testMapBadInput(DoubleMap.class, schema, input, badInput, badOutput);
Map<String, ? extends Number> castFrom = asMap("one", 1, "three", 3L, "five", 5.0f, "seven", 7, "eleven", 11);
testNumberMap(DoubleMap.class, schema, input, castFrom);
}
@Test
public void testStringMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"string\" }");
Map<String, String> input = asMap("one", "1", "three", "3", "five", "5", "seven", "7", "eleven", "11");
Map<String, String> adds = asMap("thirteen", "13", "seventeen", "17", "nineteen", "19");
Map<String, Object> badInput = asMap("boolean", true, "integer", 99, "long", 999L, "float", 88.0f, "double", 888.0,
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "integer", 99, "long", 999L, "float", 88.0f, "double", 888.0,
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(StringMap.class, schema, input, adds);
testMapBadInput(StringMap.class, schema, input, badInput, badOutput);
}
@Test
public void testBytesMap()
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"bytes\" }");
Map<String, ByteString> input = asMap("one", ByteString.copyAvroString("1", false), "three", ByteString.copyAvroString("3", false), "five", ByteString.copyAvroString("5", false), "seven", ByteString.copyAvroString("7", false), "eleven", ByteString.copyAvroString("11", false));
Map<String, ByteString> adds = asMap("thirteen", ByteString.copyAvroString("13", false), "seventeen", ByteString.copyAvroString("17", false), "nineteen", ByteString.copyAvroString("19", false));
Map<String, Object> badInput = asMap("boolean", true, "integer", 99, "long", 999L, "float", 88.0f, "double", 888.0,
"data", "\u0100",
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "integer", 99, "long", 999L, "float", 88.0f, "double", 888.0,
"data", "\u0100",
"map", new DataMap(),
"list", new DataList());
testMap(BytesMap.class, schema, input, adds);
testMapBadInput(BytesMap.class, schema, input, badInput, badOutput);
}
public static enum Fruits
{
APPLE, ORANGE, BANANA, GRAPES, PINEAPPLE
}
public static class EnumMapTemplate extends com.linkedin.data.template.DirectMapTemplate<Fruits>
{
public static final MapDataSchema SCHEMA = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : { \"type\" : \"enum\", \"name\" : \"Fruits\", \"symbols\" : [ \"APPLE\", \"ORANGE\", \"BANANA\", \"GRAPES\", \"PINEAPPLE\" ] } }");
public EnumMapTemplate()
{
this(new DataMap());
}
public EnumMapTemplate(int capacity)
{
this(new DataMap(capacity));
}
public EnumMapTemplate(int capacity, float loadFactor)
{
this(new DataMap(capacity, loadFactor));
}
public EnumMapTemplate(Map<String, Fruits> map)
{
this(newDataMapOfSize(map.size()));
putAll(map);
}
public EnumMapTemplate(DataMap map)
{
super(map, SCHEMA, Fruits.class, String.class);
}
@Override
public EnumMapTemplate clone() throws CloneNotSupportedException
{
return (EnumMapTemplate) super.clone();
}
@Override
public EnumMapTemplate copy() throws CloneNotSupportedException
{
return (EnumMapTemplate) super.copy();
}
}
@Test
public void testEnumMap()
{
Map<String, Fruits> input = asMap("apple", Fruits.APPLE, "orange", Fruits.ORANGE, "banana", Fruits.BANANA); // must be unique
Map<String, Fruits> adds = asMap("grapes", Fruits.GRAPES, "pineapple", Fruits.PINEAPPLE);
Map<String, Object> badInput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"map", new DataMap(),
"list", new DataList());
testMap(EnumMapTemplate.class, EnumMapTemplate.SCHEMA, input, adds);
testMapBadInput(EnumMapTemplate.class, EnumMapTemplate.SCHEMA, input, badInput, badOutput);
}
public static class MapOfStringMapTemplate extends WrappingMapTemplate<StringMap>
{
public static final MapDataSchema SCHEMA = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : { \"type\" : \"map\", \"values\" : \"string\" } }");
public MapOfStringMapTemplate()
{
this(new DataMap());
}
public MapOfStringMapTemplate(int capacity)
{
this(new DataMap(capacity));
}
public MapOfStringMapTemplate(int capacity, float loadFactor)
{
this(new DataMap(capacity, loadFactor));
}
public MapOfStringMapTemplate(Map<String, StringMap> map)
{
this(newDataMapOfSize(map.size()));
putAll(map);
}
public MapOfStringMapTemplate(DataMap map)
{
super(map, SCHEMA, StringMap.class);
}
@Override
public MapOfStringMapTemplate clone() throws CloneNotSupportedException
{
return (MapOfStringMapTemplate) super.clone();
}
@Override
public MapOfStringMapTemplate copy() throws CloneNotSupportedException
{
return (MapOfStringMapTemplate) super.copy();
}
}
@Test
public void testMapOfStringMap()
{
Map<String, StringMap> input = new HashMap<String, StringMap>();
for (int i = 0; i < 5; ++i)
{
String key = "input" + i;
input.put(key, new StringMap());
input.get(key).put("subinput" + i, "subinputvalue" + i);
}
Map<String, StringMap> adds = new HashMap<String, StringMap>();
for (int i = 0; i < 5; ++i)
{
String key = "add" + i;
adds.put(key, new StringMap());
adds.get(key).put("subadd" + i, "subaddvalue" + i);
}
Map<String, Object> badInput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray(),
"record", new FooRecord());
Map<String, Object> badOutput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"list", new DataList());
testMap(MapOfStringMapTemplate.class, MapOfStringMapTemplate.SCHEMA, input, adds);
testMapBadInput(MapOfStringMapTemplate.class, MapOfStringMapTemplate.SCHEMA, input, badInput, badOutput);
}
public static class FooRecord extends RecordTemplate
{
public static final RecordDataSchema SCHEMA = (RecordDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"record\", \"name\" : \"Foo\", \"fields\" : [ { \"name\" : \"bar\", \"type\" : \"string\" } ] }");
public static final RecordDataSchema.Field FIELD_bar = SCHEMA.getField("bar");
public FooRecord()
{
super(new DataMap(), SCHEMA);
}
public FooRecord(DataMap map)
{
super(map, SCHEMA);
}
public String getBar(GetMode mode)
{
return obtainDirect(FIELD_bar, String.class, mode);
}
public void removeBar()
{
remove(FIELD_bar);
}
public void setBar(String value)
{
putDirect(FIELD_bar, String.class, value);
}
@Override
public FooRecord clone() throws CloneNotSupportedException
{
return (FooRecord) super.clone();
}
@Override
public FooRecord copy() throws CloneNotSupportedException
{
return (FooRecord) super.copy();
}
}
public static class FooRecordMap extends WrappingMapTemplate<FooRecord>
{
public static final MapDataSchema SCHEMA = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : { \"type\" : \"record\", \"name\" : \"Foo\", \"fields\" : [ { \"name\" : \"bar\", \"type\" : \"string\" } ] } }");
public FooRecordMap()
{
this(new DataMap());
}
public FooRecordMap(int capacity)
{
this(new DataMap(capacity));
}
public FooRecordMap(int capacity, float loadFactor)
{
this(new DataMap(capacity, loadFactor));
}
public FooRecordMap(Map<String, FooRecord> map)
{
this(newDataMapOfSize(map.size()));
putAll(map);
}
public FooRecordMap(DataMap map)
{
super(map, SCHEMA, FooRecord.class);
}
@Override
public FooRecordMap clone() throws CloneNotSupportedException
{
return (FooRecordMap) super.clone();
}
@Override
public FooRecordMap copy() throws CloneNotSupportedException
{
return (FooRecordMap) super.copy();
}
}
@Test
public void testFooRecordMap()
{
Map<String, FooRecord> input = new HashMap<String, FooRecord>();
for (int i = 0; i < 5; ++i)
{
String key = "input" + i;
input.put(key, new FooRecord());
input.get(key).setBar("subinputvalue" + i);
}
Map<String, FooRecord> adds = new HashMap<String, FooRecord>();
for (int i = 0; i < 5; ++i)
{
String key = "add" + i;
adds.put(key, new FooRecord());
adds.get(key).setBar("subaddvalue" + i);
}
Map<String, Object> badInput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"object", new Object(),
"null", null,
"array", new StringArray());
Map<String, Object> badOutput = asMap("boolean", true, "integer", 1, "long", 2L, "float", 3.0f, "double", 4.0, "string", "hello",
"bytes", ByteString.empty(),
"list", new DataList());
testMap(FooRecordMap.class, FooRecordMap.SCHEMA, input, adds);
testMapBadInput(FooRecordMap.class, FooRecordMap.SCHEMA, input, badInput, badOutput);
}
public <E> void dump(Set<Map.Entry<String,E>> set)
{
for (Map.Entry<String,E> entry : set)
{
Object k = entry.getKey();
Object v = entry.getValue();
out.println(k + "(" + k.getClass() + ") = " + v + " (" + v.getClass() + ") " + entry.hashCode());
}
}
protected static class PrimitiveLegacyMap<T> extends DirectMapTemplate<T>
{
public PrimitiveLegacyMap(DataMap map, MapDataSchema schema, Class<T> valuesClass)
{
super(map, schema, valuesClass);
assertSame(_dataClass, valuesClass);
}
}
protected static class EnumLegacyMap extends DirectMapTemplate<Fruits>
{
public static final MapDataSchema SCHEMA = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : { \"type\" : \"enum\", \"name\" : \"Fruits\", \"symbols\" : [ \"APPLE\", \"ORANGE\", \"BANANA\", \"GRAPES\", \"PINEAPPLE\" ] } }");
public EnumLegacyMap(DataMap map)
{
super(map, SCHEMA, Fruits.class);
assertSame(_dataClass, String.class);
}
}
@Test
public void testLegacyConstructor()
{
Map<String, Class<?>> primitiveStringToClassMap = asMap(
"int", Integer.class,
"long", Long.class,
"float", Float.class,
"double", Double.class,
"boolean", Boolean.class,
"string", String.class);
for (Map.Entry<String, Class<?>> e : primitiveStringToClassMap.entrySet())
{
MapDataSchema schema = (MapDataSchema) DataTemplateUtil.parseSchema("{ \"type\" : \"map\", \"values\" : \"" + e.getKey() + "\" }");
@SuppressWarnings("unchecked")
PrimitiveLegacyMap<?> map = new PrimitiveLegacyMap<Object>(new DataMap(), schema, (Class)e.getValue());
}
EnumLegacyMap enumMap = new EnumLegacyMap(new DataMap());
}
}