Generics and concrete implementations of strategy components - java

I ran into a little snag with the concrete implementation of strategy components using generic types.
Wondering if anyone can point me in the right direction with an example?
Here is what I am working towards, but I get caught up when I declare the decode method as it expcects a List when I create the ArrayList... Not a surprise.
public class CsvFormat<T,T1> implements FormatStrategy<T,T1> {
public CsvFormat(boolean header) {
setHeader(header);
#Override
public final T decode(T1 csvData) {
csvData = new ArrayList(); //ERROR****
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}
Here is what I had to start with The only way I can think of so far to achieve the above without error is to overload the the decode methods based on what object they are passed..
public class CsvFormat implements FormatStrategy<
List<Map<String, String>>, List<String>> {
public CsvFormat(boolean header) {
setHeader(header);
}
#Override
public final List<Map<String, String>> decode(List<String> csvData) {
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}

Actually the example you "started with" seems exactly right. You have written a decode method that requires a List<String> as input, so it stands to reason that you would be implementing the FormatStrategy interface with that specific type as T1 and the same goes for the output type T.
Why would you do icky runtime inspection of the input and loads of unsafe casting when you can actually follow the pattern and create a new class for each specific concrete type you care about?

The code you have written for decoding the data will always return a List<Map<String, String>> and can only work with a List<String> as input, so there is no reason for the CsvFormat class to have type parameters. So what you started with seems correct, why aren't you satisfied with it?

Related

Guava Hashmap comparison

I'm having some trouble using Guava's Maps.difference
Right now, using this code to compare two HashMaps from two different jsons:
//Create maps from the given jsons
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> map1 = gson.fromJson(jsonObject1, type);
Map<String, Object> map2 = gson.fromJson(jsonObject2, type);
//Flatten the maps
Map<String, Object> leftFlatMap = FlatMap.flatten(map1);
Map<String, Object> rightFlatMap = FlatMap.flatten(map2);
//Check differences between both maps
MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);
Everything works fine, and compares (almost) all the elements correctly.
Problem is when one of the elements inside the HashMap is an array of maps and the elements are the same but in a different order. Like this:
FIRST JSON:
{ "body":[
{
"primitive":"VALUE",
"jsonArray":[
{
"element":83284180
},
{
"anotherElement":20832841804
}
]
}
]
}
SECOND JSON:
{
"body":[
{
"primitive":"VALUE",
"jsonArray":[
{
"anotherElement":20832841804
},
{
"element":83284180
}
]
}
]
}
As you can see, element and anotherElement values are the same but as they appear in a different order inside the array, difference shows an error.
Is there any possibility to sort the array before? or any other solution?
Thanks in advance!!
One of possible solutions may be sorting the inner sub array so that it would affect the deserialized maps (however, I think making maps out of JSON objects in this case might be a not very good idea due to deserialization costs and strategies that do not necessarily represent the original JSON object).
Assuming jsonObject1 and jsonObject2 are JsonElement implementations, you can sort its descendants.
#UtilityClass
public final class JsonElements {
public static List<JsonElement> asListView(final JsonArray jsonArray) {
return new JsonArrayListView(jsonArray);
}
public static void sort(final JsonArray jsonArray, final Comparator<? super JsonElement> comparator) {
Collections.sort(asListView(jsonArray), comparator);
}
#AllArgsConstructor(access = AccessLevel.PRIVATE)
private static final class JsonArrayListView
extends AbstractList<JsonElement> {
private final JsonArray jsonArray;
#Override
public JsonElement get(final int index) {
return jsonArray.get(index);
}
#Override
public int size() {
return jsonArray.size();
}
#Override
#SuppressWarnings("MethodDoesntCallSuperMethod")
public JsonElement set(final int index, final JsonElement element) {
return jsonArray.set(index, element);
}
}
}
public final class JsonElementsTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.create();
private static final Type stringToObjectMapType = new TypeToken<Map<String, Object>>() {}.getType();
#Test
public void testSort()
throws IOException {
final JsonElement jsonElement1 = ... read the 1st JSON document ...;
final JsonElement jsonElement2 = ... read the 2nd JSON document ...;
final JsonArray jsonSubArray1 = getSubArray(jsonElement1);
final JsonArray jsonSubArray2 = getSubArray(jsonElement2);
Assertions.assertNotEquals(jsonSubArray1, jsonSubArray2);
JsonElements.sort(jsonSubArray1, JsonElementsTest::compare);
JsonElements.sort(jsonSubArray2, JsonElementsTest::compare);
final Map<String, Object> map1 = gson.fromJson(jsonElement1, stringToObjectMapType);
final Map<String, Object> map2 = gson.fromJson(jsonElement2, stringToObjectMapType);
Assertions.assertEquals(map1, map2);
}
private static JsonArray getSubArray(final JsonElement jsonElement) {
return jsonElement.getAsJsonObject()
.get("body")
.getAsJsonArray()
.get(0)
.getAsJsonObject()
.get("jsonArray")
.getAsJsonArray();
}
private static int compare(final JsonElement jsonElement1, final JsonElement jsonElement2)
throws IllegalArgumentException {
final JsonObject jsonObject1 = jsonElement1.getAsJsonObject();
final int size1 = jsonObject1.size();
if ( size1 != 1 ) {
throw new IllegalArgumentException("Size-1 must equal 1, but was " + size1);
}
final JsonObject jsonObject2 = jsonElement2.getAsJsonObject();
final int size2 = jsonObject2.size();
if ( size2 != 1 ) {
throw new IllegalArgumentException("Size-2 must equal 2, but was " + size2);
}
// TODO optimize somehow
final String key1 = jsonObject1.keySet().iterator().next();
final String key2 = jsonObject2.keySet().iterator().next();
return key1.compareTo(key2);
}
}
Also consider sorting the descendants recursively if necessary.
Note that you also might have a mapping for the given JSON documents, but I don't think it's your case, but if it is, then you might want to apply #JsonAdapter to apply a special ordering deserializer (however I still don't think it's a good idea too). Or else, it is also possible to create a map view for the given JsonObjects so that it might produce recursive reordering views.

How to store and access 2 different types of List in a HashMap

Im trying to store and access 2 different types of List in a HashMap, and access them when the method that return the HashMap is it called, but Im getting this error:
The constructor ArrayList<JournalArticle>(Object) is undefined
The method looks like this:
public static HashMap<String, Object> getJournalArticles(long groupId) throws NumberFormatException, SystemException{
List<JournalArticle> journalArticles = JournalArticleLocalServiceUtil.getStructureArticles(groupId);
List<String> allJournalArticleIds = new ArrayList<String>();
for (JournalArticle journalArticle : journalArticles) {
allJournalArticleIds.add(journalArticle.getArticleId());
}
HashMap<String, Object> mapArticles = new HashMap<String,Object>();
mapArticles.put("journalArticles", journalArticles);
mapArticles.put("allJournalArticleIds", allJournalArticleIds);
return mapArticles;
}
And when I call the method and try to store their respective values into a new List I get the error commented before:
HashMap<String, Object> mapArticles = JournalArticleUtil.getJournalArticles(scopeGroupId);
List<JournalArticle> allArticles = new ArrayList<JournalArticle>(mapArticles.get("journalArticles"));
List<String> allJournalArticleIds = new ArrayList<String>(mapArticles.get("allJournalArticleIds"));
What´s wrong and how to solve?
I would use a class written to hold this information (you may find it quicker to use something like Pair<L, R>):
class ArticleHolder {
private List<JournalArticle> journalArticles;
private List<String> allJournalArticleIds;
public ArticleHolder(List<JournalArticle> journalArticles,
List<String> allJournalArticleIds) {
this.journalArticles = journalArticles;
this.allJournalArticleIds = allJournalArticleIds;
}
//getters + setters
}
And change your methods:
public static ArticleHolder getJournalArticles(long groupId)
throws NumberFormatException, SystemException {
List<JournalArticle> journalArticles =
JournalArticleLocalServiceUtil.getStructureArticles(groupId);
List<String> allJournalArticleIds = new ArrayList<String>();
for (JournalArticle journalArticle : journalArticles) {
allJournalArticleIds.add(journalArticle.getArticleId());
}
return new ArticleHolder(journalArticles, allJournalArticleIds);
}
Beside that: your call to new ArrayList<JournalArticle>(...) shows that you intended to create new array list instances (assuming code could compile). There should be no need to do that, even if your map values were typed as Collection objects.
IMHO The quick solution is change the type of mapArticles to this HashMap<String, List<?>> and then:
List<JournalArticle> allArticles = new ArrayList<JournalArticle>((Collection<JournalArticle>)mapArticles.get("journalArticles"));
List<String> allJournalArticleIds = new ArrayList<String>((Collection<String>)mapArticles.get("allJournalArticleIds"));
Because the ArrayList constructor only supports these options:
new ArrayList<T>();
new ArrayList<T>(int capacity);
new ArrayList<T>(Collection<? extends T> collection);
And Object isn't a collection at compiling time.

Java streams: Add to map but avoid mutation

I often find myself in a situation where I need to create a Map of objects from a Set or List.
The key is usually some String or Enum or the like, and the value is some new object with data lumped together.
The usual way of doing this, for my part, is by first creating the Map<String, SomeKeyValueObject> and then iterating over the Set or List I get in and mutate my newly created map.
Like the following example:
class Example {
Map<String, GroupedDataObject> groupData(final List<SomeData> list){
final Map<String, GroupedDataObject> map = new HashMap<>();
for(final SomeData data : list){
final String key = data.valueToGroupBy();
map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data()));
}
return map;
}
}
class SomeData {
private final String valueToGroupBy;
private final Object data;
private final String displayName;
public SomeData(final String valueToGroupBy, final String displayName, final Object data) {
this.valueToGroupBy = valueToGroupBy;
this.data = data;
this.displayName = displayName;
}
public String valueToGroupBy() {
return valueToGroupBy;
}
public Object data() {
return data;
}
public String displayName() {
return displayName;
}
}
class GroupedDataObject{
private final String key;
private final List<Object> datas;
private GroupedDataObject(final String key, final List<Object> list) {
this.key = key;
this.datas = list;
}
public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) {
final List<Object> list = new ArrayList<>();
if(groupedDataObject != null){
list.addAll(groupedDataObject.datas());
}
list.add(data);
return new GroupedDataObject(key, list);
}
public String key() {
return key;
}
public List<Object> datas() {
return datas;
}
}
This feels very unclean. We create a map, and then mutate it over and over.
I've taken a liking to java 8s use of Streams and creating non-mutating data structures (or rather, you don't see the mutation). So is there a way to turn this grouping of data into something that uses a declarative approach rather than the imperative way?
I tried to implement the suggestion in https://stackoverflow.com/a/34453814/3478016 but I seem to be stumbling. Using the approach in the answer (the suggestion of using Collectors.groupingBy and Collectors.mapping) I'm able to get the data sorted into a map. But I can't group the "datas" into one and the same object.
Is there some way to do it in a declarative way, or am I stuck with the imperative?
You can use Collectors.toMap with a merge function instead of Collectors.groupingBy.
Map<String, GroupedDataObject> map =
list.stream()
.collect(Collectors.toMap(SomeData::valueToGroupBy,
d -> {
List<Object> l = new ArrayList<>();
l.add(d.data());
return new GroupedDataObject(d.valueToGroupBy(), l);
},
(g1,g2) -> {
g1.datas().addAll(g2.datas());
return g1;
}));
The GroupedDataObject constructor must be made accessible in order for this to work.
If you avoid the GroupedDataObject and simply want a map with a key and a list you can use Collectors.groupingBy that you have been looking into.
Collectors.groupingBy will allow you to do this:
List<SomeObject> list = getSomeList();
Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod));
This will require SomeKey to have proper implementations of equals and hashValue
Sometimes streams are not the way to go. I believe this is one of those times.
A little refactoring using merge() gives you:
Map<String, MyTuple> groupData(final List<SomeData> list) {
Map<String, MyTuple> map = new HashMap<>();
list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()),
(a, b) -> {a.addAll(b.getDatas()); return a;});
Assuming a reasonable class to hold your stuff:
class MyTuple {
String displayName;
List<Object> datas = new ArrayList<>();
// getters plus constructor that takes 1 data and adds it to list
}

Java Hashmap to JavaFX Treeview and Back?

I have a nested HashMap which looks like this:
Map<String,Object> myMap = new HashMap<String,Object>();
This myMap is nested, Like:
String key => String val
String key => String val
String key => Map<String,Object> val
And then that value may contain another similar Map<String,Object>.
I do not expect it to be nested more than 3 levels.
The leafs or last values are always String.
Now I'm trying to make a way to edit this HashMap in a JavaFX GUI.
From what I have learnt so far,
the best way seems like making a editable JavaFX TreeView
and somehow translating the Map to a TreeView and back.
So far I'm thinking
TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
treeMap.putAll(myMap);
And then somehow translating that TreeMap to a JavaFX TreeView.
But I can not figure out how to proceed.
Another headache is that after user edits I need to translate it all back to a HashMap such as the original myMap. Although sorting / sequence is not required.
Create a suitable class to represent the map entry. Since you need to modify the Map, you need to store key and Map in that class.
If you use TextFieldTreeCell as cell type, the StringConverter can be used to modify the source data structure on a edit:
private static TreeItem<MapItem> createTree(Map<String, Object> map) {
TreeItem<MapItem> result = new TreeItem<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
result.getChildren().add(createTree(map, entry));
}
return result;
}
private static TreeItem<MapItem> createTree(Map<String, Object> map, Map.Entry<String, Object> entry) {
MapItem mi = new MapItem(map, entry.getKey());
TreeItem<MapItem> result = new TreeItem<>(mi);
Object value = entry.getValue();
if (value instanceof Map) {
Map<String, Object> vMap = (Map<String, Object>)value;
// recursive creation of subtrees for map entries
for (Map.Entry<String, Object> e : vMap.entrySet()) {
result.getChildren().add(createTree(vMap, e));
}
} else {
result.getChildren().add(new TreeItem<>(new MapItem(null, value.toString())));
}
return result;
}
private static class MapItem {
private final Map<String, Object> map;
private final String value;
public MapItem(Map<String, Object> map, String value) {
this.map = map;
this.value = value;
}
}
private static class Converter extends StringConverter<MapItem> {
private final TreeCell<MapItem> cell;
public Converter(TreeCell<MapItem> cell) {
this.cell = cell;
}
#Override
public String toString(MapItem object) {
return object == null ? null : object.value;
}
#Override
public MapItem fromString(String string) {
MapItem mi = cell.getItem();
if (mi != null) {
TreeItem<MapItem> item = cell.getTreeItem();
if (item.isLeaf()) {
MapItem parentItem = item.getParent().getValue();
// modify value in parent map
parentItem.map.put(parentItem.value, string);
mi = new MapItem(mi.map, string);
} else if (!mi.map.containsKey(string)) {
// change key of mapping, if there is no mapping for the new key
mi.map.put(string, mi.map.remove(mi.value));
mi = new MapItem(mi.map, string);
}
}
return mi;
}
}
#Override
public void start(Stage primaryStage) {
Map<String, Object> map = new HashMap<>();
map.put("a", "b");
Map<String, Object> inner = new HashMap<>();
map.put("c", inner);
inner.put("d", "e");
Map<String, Object> inner2 = new HashMap<>();
inner.put("f", inner2);
inner2.put("g", "h");
inner2.put("i", "j");
TreeView<MapItem> treeView = new TreeView<>(createTree(map));
treeView.setEditable(true);
treeView.setShowRoot(false);
treeView.setCellFactory(t -> {
TextFieldTreeCell<MapItem> cell = new TextFieldTreeCell<>();
cell.setConverter(new Converter(cell));
return cell;
});
Button btn = new Button("Print Map");
btn.setOnAction((ActionEvent event) -> {
System.out.println(map);
});
VBox root = new VBox(10, btn, treeView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
There is not build-in method that would convert:
either Map directly to TreeView
or TreeMap to TreeView
Moreover, If you see the structure of Map<?,?>, we can see that it is a combination of key, value pair whereas TreeView<?> consists of only value, like that of Collection interface. So It's not possible to insert key, value pair in TreeView. However you can insert only values from Map.
The best you can do is define a new data structure like this:
public class YourCustomDataStructure extends TreeItem<String> {
...
/*
Now you can define methods that will convert `List`s directly to `YourCustomDataStructure`.
You can even define method to convert `Map` values to `YourCustomDataStructure`.
*/
public boolean addAll(Map map) {
//your implementation to convert map to TreeItem
}
public boolean addAll(List list) {
//your implementation to convert list to TreeItem
}
}
Now convert your list or map to YourCustomDataStructure using addAll() method from previous step.
List<Object> list = getListFromSomewhere();
YourCustomDataStructure<String> customList = new YourCustomDataStructure<String>("customList Node");
customList.getChildren().addAll(list);
Now since YourCustomDataStructure extends TreeItem so it's object's can be directly passed to TreeView's constructor and they will be automatically converted to TreeView.
TreeView<String> treeView = new TreeView<String>(customList);
P.S.: I know defining new data structure and all the methods will require lots of efforts at initial level, but once those methods are defined, then it will become too easy to convert TreeView to Map and vice versa.

How can I cast Generics in Java?

I have this code:
FVDTO.setStatus("fail");
List<String[]> invalidFields = new ArrayList<String[]>();
Iterator<ConstraintViolation<HazardSubmission>> iterator = cv.iterator();
while(iterator.hasNext()) {
ConstraintViolation<HazardSubmission> i = iterator.next();
String property = i.getPropertyPath().toString();
String message = i.getMessage();
invalidFields.add(new String[] { property, message });
}
FVDTO.setInvalidFields(invalidFields);
return new JsonResolution(FVDTO);
I've taken some out to keep things DRY so I can then use it with other classes, i.e HazardSubmission is one class, and there will be others. The below code shows my attempt, obviously manually casting <HazardSubmission> here won't work it needs to be like o.getClass();
public static List<String[]> GetInvalidProperties(Set<ConstraintViolation<Object>> cv, Object o) {
List<String[]> invalidFields = new ArrayList<String[]>();
Iterator<ConstraintViolation<HazardSubmission>> iterator = cv.iterator();
while(iterator.hasNext()) {
ConstraintViolation<HazardSubmission> i = iterator.next();
String property = i.getPropertyPath().toString();
String message = i.getMessage();
invalidFields.add(new String[] { property, message });
}
}
The second code block fails because I don't really know what I'm doing, I want to pass in the cv for param 1, with a general object type, then somehow pass in the type as a second paramter.
Could you someone please explain how to do this?
I think you might be looking for a generic method
public static <T> List<String[]> GetInvalidProperties(Set<ConstraintViolation<T>> cv){
Iterator<ConstraintViolation<T>> iterator = cv.iterator();
while(iterator.hasNext()) {
ConstraintViolation<T> i = iterator.next();
String property = i.getPropertyPath().toString();
String message = i.getMessage();
invalidFields.add(new String[] { property, message });
}
}
If all T extends a given class or interface you could even say
public static <T extends MyClassOrInterface> List<String[]> GetInvalidProperties(Set<ConstraintViolation<T>> cv){
//...
}
cv.iterator() will return you an Iterator<ConstraintViolation<Object>>, and you need Iterator<ConstraintViolation<HazardSubmission>>. This is done because cv is defined as Set<ConstraintViolation<Object>>. If you want a generic way for this, you can change
Set<ConstraintViolation<Object>> cv
to
Set<ConstraintViolation<? extends Object>> cv
in that way your code will compile.

Categories

Resources