Is there a Java collection that has the same behavior as the c# abstract KeyedCollection class (that is items can be retrieved by both key and index)? I have looked around but can't find anything similar.
Thanks,
Nick
Here is a full implementation using a "KeyedItem" interface. I've included comments as best I could.
/**
* An interface that must be implemented by an item inserted into a KeyedItemHashMap.
*/
public interface KeyedItem {
/**
* Returns an ID for this item to be used in a KeyedItemHashMap.
* #return An ID for this item to be used in a KeyedItemHashMap.
*/
public String getId();
}
And then the implementation of the HashMap class...
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Hash table based implementation of the {#code Map} interface. This
* implementation provides all of the optional map operations, and permits
* {#code null} values and the {#code null} key. (The {#code HashMap}
* class is roughly equivalent to {#code Hashtable}, except that it is
* unsynchronized and permits nulls.) This class makes no guarantees as to
* the order of the map; in particular, it does not guarantee that the order
* will remain constant over time.
*
* Unlike a typical hash map, this implementation uses KeyedItem's as the
* value. A KeyedItem must implement the {#code KeyedItem} interface and
* provide a unique ID to avoid collisions within the map.
*
* For more details see the {#code HashMap} class.
*/
public final class KeyedItemHashMap implements Map<String,KeyedItem>{
private HashMap<String,KeyedItem> m_map;
/**
* Constructs an empty HashMap with the default initial capacity (16) and
* the default load factor (0.75).
*/
public KeyedItemHashMap() {
m_map = new HashMap<>();
}
/**
* Returns the number of key-value mappings in this map.
* #return The number of key-value mappings in this map.
*/
public int size() {
return m_map.size();
}
/**
* Returns {#code true} if this map contains no key-value mappings.
* #return {#code true} if this map contains no key-value mappings.
*/
public boolean isEmpty() {
return m_map.isEmpty();
}
/**
* Returns {#code true} if this map contains a mapping for the specified key.
* #param key The key whose presence in this map is to be tested.
* #return {#code true} if this map contains a mapping for the specified key.
*/
public boolean containsKey(Object key) {
return m_map.containsKey(key);
}
public boolean containsValue(Object keyedItem) {
return m_map.containsValue(keyedItem);
}
/**
* Returns the string representation of the {#code Object} argument.
*
* #param obj an {#code Object}.
* #return if the argument is {#code null}, then a string equal to
* {#code "null"}; otherwise, the value of
* {#code obj.toString()} is returned.
* #see java.lang.Object#toString()
*/
public KeyedItem get(Object obj) {
return m_map.get(String.valueOf(obj));
}
/**
* Associates the specified value with the keyedItem's ID in this map.
* If the map previously contained a mapping for the keyedItem's ID, the old
* value is replaced.
*
* #param key UNUSED here but necessary for override.
* #param keyedItem Value that implements the KeyedItem interface. The getId() function will
* be used to determine the key for the map.
* #return the previous value associated with {#code keyedItem.getId()}, or
* {#code null} if there was no mapping for {#code keyedItem.getId()}.
* (A {#code null} return can also indicate that the map
* previously associated {#code null} with {#code keyedItem.getId()}.)
*/
public KeyedItem put(String key, KeyedItem keyedItem) {
return m_map.put(keyedItem.getId(), keyedItem);
}
/**
* Associates the specified value with the keyedItem's ID in this map.
* If the map previously contained a mapping for the keyedItem's ID, the old
* value is replaced.
*
* #param keyedItem Value that implements the KeyedItem interface. The getId() function will
* be used to determine the key for the map.
* #return the previous value associated with {#code keyedItem.getId()}, or
* {#code null} if there was no mapping for {#code keyedItem.getId()}.
* (A {#code null} return can also indicate that the map
* previously associated {#code null} with {#code keyedItem.getId()}.)
*/
public KeyedItem put(KeyedItem keyedItem) {
return m_map.put(keyedItem.getId(), keyedItem);
}
/**
* Removes the mapping for the specified keyedItem's ID from this map if present.
*
* #param keyedItem KeyedItem whose mapping is to be removed from the map.
* #return the previous value associated with {#code keyedItem.getId()}, or
* {#code null} if there was no mapping for {#code keyedItem.getId()}.
* (A {#code null} return can also indicate that the map
* previously associated {#code null} with {#code keyedItem.getId()}.)
* #throws ClassCastException if the keyedItem does not implement the KeyedItem interface.
*/
public KeyedItem remove(Object keyedItem) {
return m_map.remove(((KeyedItem)keyedItem).getId());
}
/**
* Copies all of the mappings from the specified map to this map.
* These mappings will replace any mappings that this map had for
* any of the keys currently in the specified map.
*
* #param map mappings to be stored in this map
* #throws NullPointerException if the specified map is null
*/
public void putAll(Map<? extends String, ? extends KeyedItem> map) {
m_map.putAll(map);
}
/**
* Removes all of the mappings from this map.
* The map will be empty after this call returns.
*/
public void clear() {
m_map.clear();
}
/**
* Returns a {#link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own {#code remove} operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* {#code Iterator.remove}, {#code Set.remove},
* {#code removeAll}, {#code retainAll}, and {#code clear}
* operations. It does not support the {#code add} or {#code addAll}
* operations.
*
* #return a set view of the keys contained in this map
*/
public Set<String> keySet() {
return m_map.keySet();
}
/**
* Returns a {#link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress
* (except through the iterator's own {#code remove} operation),
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the {#code Iterator.remove},
* {#code Collection.remove}, {#code removeAll},
* {#code retainAll} and {#code clear} operations. It does not
* support the {#code add} or {#code addAll} operations.
*
* #return a view of the values contained in this map
*/
public Collection<KeyedItem> values() {
return m_map.values();
}
/**
* Returns a {#link Set} view of the mappings contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own {#code remove} operation, or through the
* {#code setValue} operation on a map entry returned by the
* iterator) the results of the iteration are undefined. The set
* supports element removal, which removes the corresponding
* mapping from the map, via the {#code Iterator.remove},
* {#code Set.remove}, {#code removeAll}, {#code retainAll} and
* {#code clear} operations. It does not support the
* {#code add} or {#code addAll} operations.
*
* #return a set view of the mappings contained in this map
*/
public Set<Entry<String, KeyedItem>> entrySet() {
return m_map.entrySet();
}
}
This should be flexible enough for you to just add the interface to any class you want to store.
You want to implement the map interface so that your new hashmap can be used in any capacity that a generic hashmap could be used, unfortunately this means you have a put function that takes in a key that isn't used. I've noted that in the comments.
Edit: fixed some syntax errors and included imports
I think you can develop Your own class by extending a HashMap
I think the closest thing I can think of to what you want is a treemap (http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html), however you cannot get items by index only by key.
Given an Entity class:
#Data // lombok.Data, to have getters and setters
public class Entity {
private String myIndexingProperty;
// Other fields here
}
An attempt to implement something like the KeyedCollection<T> of C# is (Java 8+):
import com.my.application.model.Entity;
import java.util.*;
import java.util.stream.Collectors;
public final class EntityMap implements Map<String, Entity> {
private TreeSet<Entity> set;
public EntityMap() {
set = new TreeSet<>();
}
#Override
public int size() {
return set.size();
}
#Override
public boolean isEmpty() {
return set.isEmpty();
}
#Override
public boolean containsKey(Object o) {
return set.stream()
.anyMatch(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()));
}
#Override
public boolean containsValue(Object o) {
return set.stream()
.anyMatch(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()));
}
#Override
public Entity get(Object o) {
return set.stream()
.filter(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()))
.findFirst()
.orElse(null);
}
#Override
public Entity put(String s, Entity entity) {
set.add(entity);
return set.stream()
.filter(e -> String.valueOf(entity.getMyIndexingProperty()).equalsIgnoreCase(e.getMyIndexingProperty()))
.findFirst()
.orElse(null);
}
#Override
public Verb remove(Object o) {
boolean removed = set.removeIf(e -> e.equals(o));
if (!removed) {
return null;
}
return get(o);
}
#Override
public void putAll(Map<? extends String, ? extends Entity> map) {
map.forEach(this::put);
}
#Override
public void clear() {
set.clear();
}
#Override
public Set<String> keySet() {
return set.stream()
.map(Entity::getMyIndexingProperty)
.collect(Collectors.toSet());
}
#Override
public Collection<Entity> values() {
return set;
}
#Override
public Set<Entry<String, Entity>> entrySet() {
return set.stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getMyIndexingProperty(), e))
.collect(Collectors.toSet());
}
}
Related
I'm trying to insert data from ArrayList to HashMap<String, Language> optimally.
Many items may have the same languge_name (code below), so I need to group items having the same language in Language class and store languages in a HashMap with the name of the language as a Key.
Item
String name;
String language_name;
Language
String language_name;
int numberItems;
LinkedList<String> Items;
I solved this as follows:
ArrayList<Item> items; // given array of items
HashMap<String, Language> languages = new HashMap<String, Language>();
items.forEach(item -> {
/** case 1: language isn't specified */
if (item.getLanguageName() == null) {
item.setLanguageName("unknown");
}
/** case 2: language already added */
if (languages.containsKey(item.getLanguageName())) {
languages.get(item.getLanguageName()).getItems().add(item.getName());
languages.get(item.getLanguageName())
.setNumberItems(languages.get(item.getLanguageName()).getNumberItems() + 1);
} else {
/** case 3: language isn't added yet */
LinkedList<String> languageItems = new LinkedList<String>();
languageItems.add(item.getName());
Language language = new Language(item.getLanguageName(), 1, languageItems);
languages.put(item.getLanguageName(), language);
}
});
Any help would be appreciated!
Assuming you're using Java 8 or later, this can be accomplished nicely with built-in stream functions.
HashMap<String, List<Items>> itemsGroupedByLanguage =
items.stream().collect(Collectors.groupingBy(Items::getLanguage));
tl;dr
It's not possible to achieve what you desire using Java (8+) inbuilt collector, but you can write your own custom collector and write code like below to collect into a map as -
Map<String, Language> languages = items.stream().collect(LanguageCollector.toLanguage());
Let's first look at Collector<T, A, R> interface
public interface Collector<T, A, R> {
/**
* A function that creates and returns a new mutable result container.
*/
Supplier<A> supplier();
/**
* A function that folds a value into a mutable result container.
*/
BiConsumer<A, T> accumulator();
/**
* A function that accepts two partial results and merges them. The
* combiner function may fold state from one argument into the other and
* return that, or may return a new result container.
*/
BinaryOperator<A> combiner();
/**
* Perform the final transformation from the intermediate accumulation type
*/
Function<A, R> finisher();
/**
* Returns a Set of Collector.Characteristics indicating
* the characteristics of this Collector. This set should be immutable.
*/
Set<Characteristics> characteristics();
}
Where T is the generic type of the items in the stream to be collected.
A is the type of the accumulator, the object on which the partial result will be accumulated during the collection process.
R is the type of the object (typically, but not always, the collection) resulting
from the collect operation
Now let's look at the custom LanguageCollector
public class LanguageCollector
implements Collector<Item, Map<String, Language>, Map<String, Language>> {
/**
* The supplier method has to return a Supplier of an empty accumulator - a parameterless
* function that when invoked creates an instance of an empty accumulator used during the
* collection process.
*/
#Override
public Supplier<Map<String, Language>> supplier() {
return HashMap::new;
}
/**
* The accumulator method returns the function that performs the reduction operation. When
* traversing the nth element in the stream, this function is applied with two arguments, the
* accumulator being the result of the reduction (after having collected the first n–1 items of
* the stream) and the nth element itself. The function returns void because the accumulator is
* modified in place, meaning that its internal state is changed by the function application to
* reflect the effect of the traversed element
*/
#Override
public BiConsumer<Map<String, Language>, Item> accumulator() {
return (map, item) -> {
if (item.getLanguageName() == null) {
item.setLanguageName("unknown");
} else if (map.containsKey(item.getLanguageName())) {
map.get(item.getLanguageName()).getItems().add(item.getName());
map.get(item.getLanguageName())
.setNumberItems(map.get(item.getLanguageName()).getNumberItems() + 1);
} else {
Language language = new Language(item.getLanguageName(), 1);
language.add(item.getName());
map.put(item.getLanguageName(), language);
}
};
}
/**
* The combiner method, return a function used by the reduction operation, defines how the
* accumulators resulting from the reduction of different subparts of the stream are combined
* when the subparts are processed in parallel
*/
#Override
public BinaryOperator<Map<String, Language>> combiner() {
return (map1, map2) -> {
map1.putAll(map2);
return map1;
};
}
/**
* The finisher() method needs to return a function which transforms the accumulator to the
* final result. In this case, the accumulator is the final result as well. Therefore it is
* possible to return the identity function
*/
#Override
public Function<Map<String, Language>, Map<String, Language>> finisher() {
return Function.identity();
}
/**
* The characteristics, returns an immutable set of Characteristics, defining the behavior of
* the collector—in particular providing hints about whether the stream can be reduced in
* parallel and which optimizations are valid when doing so
*/
#Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(
EnumSet.of(Characteristics.IDENTITY_FINISH));
}
/**
* Static method to create LanguageCollector
*/
public static LanguageCollector toLanguage() {
return new LanguageCollector();
}
}
I have modified your classes at little bit to (to follow the naming convention and more for readable accumulator operation).
Class Item
public class Item {
private String name;
private String languageName;
public Item(String name, String languageName) {
this.name = name;
this.languageName = languageName;
}
//Getter and Setter
}
Class Language
public class Language {
private String languageName;
private int numberItems;
private LinkedList<String> items;
public Language(String languageName, int numberItems) {
this.languageName = languageName;
this.numberItems = numberItems;
items = new LinkedList<>();
}
public void add(String item) {
items.add(item);
}
// Getter and Setter
public String toString() {
return "Language(languageName=" + this.getLanguageName() + ", numberItems=" + this.getNumberItems() + ", items=" + this.getItems() + ")";
}
}
Running code
public static void main(String[] args) {
List<Item> items =
Arrays.asList(
new Item("ItemA", "Java"),
new Item("ItemB", "Python"),
new Item("ItemC", "Java"),
new Item("ItemD", "Ruby"),
new Item("ItemE", "Python"));
Map<String, Language> languages = items.stream().collect(LanguageCollector.toLanguage());
System.out.println(languages);
}
prints
{Java=Language(languageName=Java, numberItems=2, items=[ItemA, ItemC]), Ruby=Language(languageName=Ruby, numberItems=1, items=[ItemD]), Python=Language(languageName=Python, numberItems=2, items=[ItemB, ItemE])}
For more information please read book 'Modern Java in Action: Lambdas, streams, functional and reactive programming' chapter 6.5 or check this link
Opting for an alternative to JPA and Spring-Data I wanted to try out JDBI for my Repository implementation with SQLite
Repository Code
/**
* SQLite implementation of Foo Repository
*/
public class FooRepository implements FooRepository {
private final DBI connection;
/**
* The constructor initialises the connection to the local SQLite file
*
* #param dataSource jdbc connection string e.g. "jdbc:sqlite::resource:db/foo.db"
* #throws IllegalArgumentException when an invalid DB file is given
*/
public FooRepository(final SQLiteDataSource dataSource) {
checkNotNull(dataSource, "dataSource required");
connection = new DBI(dataSource);
}
/**
* Returns a list of Foo objects for a website locale in the DB
* #return List
* #throws SQLException error querying
*/
#Override
public List<Foo> getFoosByWebsiteLocale(f) throws SQLException {
checkNotNull(websiteLocale, "websiteLocale required");
final String fooQuery = query...
Handle queryHandler = connection.open();
final List<Foo> fooList = queryHandler.createQuery(fooQuery)
.map(FooMapper.class);
queryHandler.close();
return fooList;
}
}
Mapper
public class FooMapper implements ResultSetMapper {
/**
* Construct a Foo object from a record in the result set
* #param index row number
* #param resultRow row
* #param ctx statementcontext
* #return Foo object
* #throws SQLException when accessing sql result set
*/
#Override
public Foo map(final int index, final ResultSet resultRow, final StatementContext ctx) throws SQLException {
return Foo.builder()
.name(resultRow.getString("foo_name"))
.type(resultRow.getString("foo_type"))
.build();
}
}
I am struggling to understand how I will create a list of Foo objects using ResultSetMapper.
The JDBI documentation also appears to be broken on this area :
http://jdbi.org/maven_site/apidocs/org/skife/jdbi/v2/tweak/ResultSetMapper.html
Help would be appreciated on how to make this work.
Your mapper only needs to map one row to one Foo object. JDBI will create the list and put the objects in the list for you.
I.e.:
final List<Foo> fooList = queryHandler.createQuery(fooQuery).map(FooMapper.class).list();
Map<Integer, Map<String, String>> mapMap = new HashMap<Integer,Map<String, String>>();
Currently asserting like this
assertThat(mapMap.size(), is(equalTo(1)));
Or
assertThat(mapMap.values(), hasSize(1));
Are there any other methods like one used with Lists.
assertThat(someListReferenceVariable, hasSize(1));
The good news
There is a matcher that does exactly what you want in the current master branch of the JavaHamcrest project.
You can call it like so:
assertThat(mapMap, aMapWithSize(1));
And the bad news
Unfortunately this matcher is not in the latest release of Hamcrest (1.3).
[Update] And finally the very good news
The aforementioned matcher is included in the newly released version 2.1.
There is none in Hamcrest 1.3, but you can very easily create your own:
public class IsMapWithSize<K, V> extends FeatureMatcher<Map<? extends K, ? extends V>, Integer> {
public IsMapWithSize(Matcher<? super Integer> sizeMatcher) {
super(sizeMatcher, "a map with size", "map size");
}
#Override
protected Integer featureValueOf(Map<? extends K, ? extends V> actual) {
return actual.size();
}
/**
* Creates a matcher for {#link java.util.Map}s that matches when the
* <code>size()</code> method returns a value that satisfies the specified
* matcher.
* <p/>
* For example:
*
* <pre>
* Map<String, Integer> map = new HashMap<>();
* map.put("key", 1);
* assertThat(map, isMapWithSize(equalTo(1)));
* </pre>
*
* #param sizeMatcher
* a matcher for the size of an examined {#link java.util.Map}
*/
#Factory
public static <K, V> Matcher<Map<? extends K, ? extends V>> isMapWithSize(Matcher<? super Integer> sizeMatcher) {
return new IsMapWithSize<K, V>(sizeMatcher);
}
/**
* Creates a matcher for {#link java.util.Map}s that matches when the
* <code>size()</code> method returns a value equal to the specified
* <code>size</code>.
* <p/>
* For example:
*
* <pre>
* Map<String, Integer> map = new HashMap<>();
* map.put("key", 1);
* assertThat(map, isMapWithSize(1));
* </pre>
*
* #param size
* the expected size of an examined {#link java.util.Map}
*/
#Factory
public static <K, V> Matcher<Map<? extends K, ? extends V>> isMapWithSize(int size) {
Matcher<? super Integer> matcher = equalTo(size);
return IsMapWithSize.<K, V> isMapWithSize(matcher);
}
}
Testing:
Map<String, Integer> map = new HashMap<>();
map.put("key", 1);
assertThat(map, isMapWithSize(1));
assertThat(map, isMapWithSize(equalTo(1)));
You can check this using not and any
import static org.hamcrest.Matchers.any;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.not;
not(hasEntry(any(Object.class), any(Object.class)))
I have a class to hold some json data as follows:
package org.swx.nursing.tools.configuration.data;
import java.util.Set;
import com.cerner.system.exception.Verifier;
import com.cerner.system.exception.VerifyException;
import com.google.common.collect.ImmutableSet;
/**
* Class representing a simple {#link JsonData#identifier},
* {#link JsonData#data} format. This class can be used to
* persist application data for example in a Configuration file.
*
* #author SW029693
* #since v1.0
*/
public class JsonData <T>{
/**
* Represents a unique identifier
*/
private String identifier;
/**
* Represents the data pertaining to this {#link JsonData#identifier}
*/
private T data;
private static final Set<String> VALID_JSON_ID_TYPES = ImmutableSet.of("CONFIG","HOTKEYS");
public JsonData(String identifier, T data) {
super();
this.identifier = identifier;
this.data = data;
}
/**
* Getter for {#link JsonData#identifier}
* #return
*/
public String getIdentifier() {
return identifier;
}
/**
* Sets the {#link JsonData#identifier} to the given value
* #param identifier
* Represents a unique {#link JsonData#identifier}
* #throws VerifyException
* If the argument is {#code null} or {#code empty}
*/
public void setIdentifier(String identifier) throws VerifyException{
Verifier.verifyNotNull(identifier, "identifier : null");
Verifier.verifyNotEmpty(identifier,"identifier : empty");
this.identifier = identifier;
}
/**
* Getter for {#link JsonData}
* #return
*/
public T getData() {
return data;
}
/**
* Sets the {#link JsonData#data} to the given value
* #param identifier
* Represents a unique {#link JsonData#data}
* #throws VerifyException
* If the argument is {#code null}
*/
public void setData(T data) {
Verifier.verifyNotNull(data, "data : null");
this.data = data;
}
#Override
public String toString() {
return "JsonData [identifier=" + identifier + ", data=" + data + "]";
}
}
I am trying to convert some data to JSON (write it to a file), read it back and cast into the above JsonData object. This is my unit test which fails:
#Test
#SuppressWarnings("unchecked")
public void testWriteContentsToFile() {
ConfigurationManager<JsonData> configurationManager = (ConfigurationManager<JsonData>)
ConfigurationManager.Factory.create();
assertNotNull(configurationManager);
configurationManager.write(getMockJsonData());
System.out.println("1="+getMockJsonData().getData().toString());
assertEquals(getMockJsonData().getData(), ((JsonData) readconfigFile()).getData());
}
The helper methods for this test are as follows:
/**
* Helper method to read the config.json file
* #return
*/
private static JsonData<ConfigurationProperty> readconfigFile() {
Reader reader = null;
JsonData<ConfigurationProperty> data = null;
Gson gson = null;
try {
reader = new FileReader("./config.json");
gson = new GsonBuilder().create();
data = gson.fromJson(reader, JsonData.class);
System.out.println("yooo="+data.getData().getRunnableContext());
} catch (FileNotFoundException e) {
e.printStackTrace();
fail("Test failed while reading the config.json file: "+e.getMessage()); }
finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
fail("Test failed while reading the config.json file: "+e.getMessage());
}
}
return data;
}
and
/**
* Helper method which creates a mock {#link JsonData} object for testing purposes
*
* #return An instance of the newly created mock {#link Jsondata} object
*/
private static JsonData<ConfigurationProperty> getMockJsonData() {
JsonData<ConfigurationProperty> data = new JsonData<ConfigurationProperty>("CONFIG",
getMockConfigurationProperty("testKey", "testContext", "APPLICATION"));
System.out.println("data ==="+data);
return data;
}
/**
* Helper method which creates a {#link ConfigurationProperty} based on the given
* non-null and non-empty arguments. This method can be used to get the
* {#link ConfigurationProperty} instance created with the desired fields when ALL
* of the 3 parameters are non-null, non-empty & valid.
*
* #param mockHotKey
* A non-null & non-empty mockHotkey parameter passed in as an argument
*
* #param mockContext
* A non-null & non-empty mockContext parameter passed in as an argument
*
* #param mockContextType
* A non-null & non-empty mockContextType parameter passed in as an argument
*
* #throws VerifierException
* If the argument validation for non-null or non-empty arguments as failed.
*
* #throws IllegalArgumentException
* The {#link ConfigurationProperty.Builder#withRunnableContextType(String type) method
* throws an {#link IllegalArgumentException} if the provided type is not supported in
* {#link ConfigurationProperty#RUNNABLE_CONTEXT_TYPE}
*
* #return An instance of newly created {#link ConfigurationProperty} object
*/
private static ConfigurationProperty getMockConfigurationProperty (
String mockHotKey, String mockContext, String mockContextType) {
Verifier.verifyNotNull(mockHotKey);
Verifier.verifyNotNull(mockContext);
Verifier.verifyNotNull(mockContextType);
Verifier.verifyNotEmpty(mockHotKey);
Verifier.verifyNotEmpty(mockContext);
Verifier.verifyNotEmpty(mockContextType);
return ConfigurationProperty.Builder
.create()
.withHotKey(mockHotKey)
.withRunnableContext(mockContext)
.withRunnableContextType(mockContextType)
.build();
}
I am getting the following error when i run the unit test to read the data from the json file into my JsonData object:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to org.swx.nursing.tools.configuration.data.ConfigurationProperty
at org.swx.nursing.tools.configuration.data.ConfigurationManagerImplTest.readconfigFile(ConfigurationManagerImplTest.java:181)
at org.swx.nursing.tools.configuration.data.ConfigurationManagerImplTest.testWriteContentsToFile(ConfigurationManagerImplTest.java:166)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
at org.jmock.integration.junit4.JMock$1.invoke(JMock.java:37)
at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:105)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:98)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:61)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:54)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:52)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
The file gets written like this:
{
"identifier": "CONFIG",
"data": {
"hotKey": "testKey",
"type": "APPLICATION",
"runnableContext": "testContext"
}
}
I am unable to read the 'data' part of the above json written into the file into the JsonData object due to the above error.
Please advise
Thanks
please see https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Generic-Types
When you call toJson(obj), Gson calls obj.getClass() to get information on the fields to serialize. Similarly, you can typically pass MyClass.class object in the fromJson(json, MyClass.class) method. This works fine if the object is a non-generic type. However, if the object is of a generic type, then the Generic type information is lost because of Java Type Erasure. Here is an example illustrating the point:
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
The above code fails to interpret value as type Bar because Gson invokes list.getClass() to get its class information, but this method returns a raw class, Foo.class. This means that Gson has no way of knowing that this is an object of type Foo, and not just plain Foo.
You can solve this problem by specifying the correct parameterized type for your generic type. You can do this by using the TypeToken class.
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
The idiom used to get fooType actually defines an anonymous local inner class containing a method getType() that returns the fully parameterized type.
I need a data structure to store different type of objects.E.g. String, Boolean and other classes.
Is using a Map<String, Object> where using the key you get the according object which assumes that you know how to cast it a good practice?
Is there a better solution?
That's a perfect use case for a PropretyHolder I wrote a while ago. You can read in length about it on my blog. I developed it with immutability in mind, feel free to adapt it to your needs.
In general I'd say if you want to profit from type safety in Java you need to know your keys. What I mean by that - it will be hardly possible to develop type safe solution where keys come from external source.
Here's a special key that knows type of its value (it's not complete please download the source for complete version):
public class PropertyKey<T> {
private final Class<T> clazz;
private final String name;
public PropertyKey(Class<T> valueType, String name) {
this.clazz = valueType;
this.name = name;
}
public boolean checkType(Object value) {
if (null == value) {
return true;
}
return this.clazz.isAssignableFrom(value.getClass());
}
... rest of the class
}
Then you develop a data structure that utilizes it:
public class PropertyHolder {
private final ImmutableMap<PropertyKey<?>, ?> storage;
/**
* Returns value for the key of the type extending-the-one-declared-in-the {#link PropertyKey}.
*
* #param key {#link PropertyKey} instance.
* #return Value of the type declared in the key.
*/
#SuppressWarnings("unchecked")
public <T extends Serializable> T get(PropertyKey<T> key) {
return (T) storage.get(key);
}
/**
* Adds key/value pair to the state and returns new
* {#link PropertyHolder} with this state.
*
* #param key {#link PropertyKey} instance.
* #param value Value of type specified in {#link PropertyKey}.
* #return New {#link PropertyHolder} with updated state.
*/
public <T> PropertyHolder put(PropertyKey<T> key, T value) {
Preconditions.checkNotNull(key, "PropertyKey cannot be null");
Preconditions.checkNotNull(value, "Value for key %s is null",
key);
Preconditions.checkArgument(key.checkType(value),
"Property \"%s\" was given "
+ "value of a wrong type \"%s\"", key, value);
// Creates ImmutableMap.Builder with new key/value pair.
return new PropertyHolder(filterOutKey(key)
.put(key, value).build());
}
/**
* Returns {#link Builder} with all the elements from the state except for the given ket.
*
* #param key The key to remove.
* #return {#link Builder} for further processing.
*/
private <T> Builder<PropertyKey<? extends Serializable>, Serializable> filterOutKey(PropertyKey<T> key) {
Builder<PropertyKey<? extends Serializable>, Serializable> builder = ImmutableMap
.<PropertyKey<? extends Serializable>, Serializable> builder();
for (Entry<PropertyKey<? extends Serializable>, Serializable> entry : this.storage.entrySet()) {
if (!entry.getKey().equals(key)) {
builder.put(entry);
}
}
return builder;
}
... rest of the class
}
I omit here a lot of unnecessary details please let me know if something is not clear.
A typesafe heterogeneous container can be used for this purpose:
import java.util.HashMap;
import java.util.Map;
public class Container {
private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>();
public <T> void putElement(Class<T> type, T instance) {
if (type == null) {
throw new NullPointerException("Type is null");
}
//container.put(type, instance); // 'v1'
container.put(type, type.cast(instance)); // 'v2' runtime type safety!
}
public <T> T getElement(Class<T> type) {
return type.cast(container.get(type));
}
public static void main(String[] args) {
Container myCont = new Container();
myCont.putElement(String.class, "aaa");
myCont.putElement(Boolean.class, true);
myCont.putElement(String[].class, new String[] {"one", "two"});
System.out.println(myCont.getElement(String.class));
System.out.println(myCont.getElement(String[].class)[1]);
}
}
Limitation: this container in its form is capable only to store one instance/object type.
In putElement() you can achieve runtime type safety by using a dynamic cast. This will hoewever add an extra overhead.
E.g: Try to pass a raw class object to the container. Note where the exception occurs:
Class raw = Class.forName("MyClass");
myCont.putElement(raw, "aaa"); //ClassCastException if using 'v2'
System.out.println(myCont.getElement(raw)); //ClassCastException if using 'v1'