Java Map toString() where Map Value is Interface - java

I have many types of maps in my method. I've tried to make a mapToString() method to handle them, but some of my maps have interfaces as their key value.
public <T> String interfaceToString(Class<T> clazz, Object instance) {
log.info(clazz + "");
StringBuilder toString = new StringBuilder("[ " + clazz.getSimpleName() + " - ");
List<Method> methods = Arrays.asList(clazz.getDeclaredMethods());
methods.forEach(method -> {
try {
toString.append(method.getName() + ": " + method.invoke(instance) + ", ");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
toString.replace(toString.length() - 2, toString.length() - 1, "");
toString.append("]");
return toString.toString();
}
public <T> String mapToString(Map<T, ?> map) {
StringBuilder toString = new StringBuilder("[ ");
for (T key : map.keySet()) {
Object value = map.get(key);
if (value.getClass().isInterface()) {
toString.append(interfaceToString(value.getClass(), value));
} else {
toString.append("[" + key + ", " + value + "] ");
}
}
toString.append(" ]");
return toString.toString();
}
It works for regular maps, but it just prints the object representation for map's whose key values are interfaces. I'm working with many interfaces, because I am using Spring JPA Projections to pull back partial parts of my database tables.
When I was debugging, when I logged value.getClass() in the mapToString, it's class was a Proxy. Which is why the isInterface() check failed when appending to the StringBuilder.
Is there a better way to do this?
EDIT
Here is my interface. It's a Spring JPA repository projection.
public interface OrderGetOpenReceievablesAndHistoryView {
Integer getNo();
String getBillchCode();
String getBilltlCode();
}
Here is my map:
Map<Integer, OrderGetOpenReceievablesAndHistoryView> carrierOrders = new HashMap<>();
I am assigning the map's key as an Integer and it's value as OrderGetOpenReceievablesAndHistoryView.
carrierOrders = orderRepository.findOpenRecievablesHistoryViewByNoIn(carrierOrderNos).stream().collect(Collectors.toMap(OrderGetOpenReceievablesAndHistoryView::getNo, Function.identity()));
I want to print the OrderGetOpenReceievablesAndHistoryView values as a normal String from the interfaceToString() method.
EDIT2
So, what I really want is a method to print out a map whose values are an interface. If I do this, it prints it out fine:
for (Integer key : carrierOrders.keySet()) {
OrderGetOpenReceievablesAndHistoryView value = carrierOrders.get(key);
log.info("{} : {}, {}, {}", key, value.getBillchCode(), value.getBilltlCode(), value.getNo());
}
Since I'm working with potentially hundreds of maps whose values could be anything, I don't want to have to write this loop everytime I want to print them.
Here is an output from the regular for each loop:
104432581 : TAXZ443237, HJMU499371, 104432581
Here is an output from the mapToString method():
[104406075, org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap#750bef00]

Every object is an instance of a concrete class, so value.getClass().isInterface() will never evaluate to true. When you want a special treatment for an interface type known at compile time, i.e. the map’s value type as declared in the generic type, you have to pass it as an argument to the method.
E.g.
public static <V> String mapToString(Map<?,V> map, Class<? super V> valueType) {
StringBuilder toString = new StringBuilder("[ ");
for(Map.Entry<?,V> entry: map.entrySet()) {
toString.append(entry.getKey()).append(": ");
if(!valueType.isInterface()) toString.append(entry.getValue());
else appendInterface(toString, valueType, entry.getValue());
toString.append(", ");
}
if(toString.length() > 2) toString.setLength(toString.length() - 2);
return toString.append(" ]").toString();
}
private static <V> void appendInterface(
StringBuilder toString, Class<V> valueType, V value) {
toString.append("[ ").append(valueType.getSimpleName()).append(" - ");
for(Method m: valueType.getDeclaredMethods()) {
if(!Modifier.isStatic(m.getModifiers()) && m.getParameterCount() == 0) {
Object o;
try {
o = m.invoke(value);
} catch(ReflectiveOperationException e) {
e.printStackTrace();
continue;
}
toString.append(m.getName()).append(": ").append(o).append(", ");
}
}
toString.replace(toString.length() - 2, toString.length(), "]");
}
Which you can invoke like
Map<Integer, OrderGetOpenReceievablesAndHistoryView> carrierOrders = new HashMap<>();
String s = mapToString(carrierOrders, OrderGetOpenReceievablesAndHistoryView.class);

Related

Java stream groupingby nested string

I have a list of string, and I want to be able to group them hierarchically.
Example of the list:
var list = new String[]{"caso.id",
"caso.unidadeDoCaso.id",
"caso.etiqueta",
"caso.sigiloso",
"caso.idPecaSegredoJustica",
"caso.numeroAno",
"caso.numero",
"caso.competencia.id",
"caso.competencia.ativo",
"caso.competencia.nome",
"caso.responsavel.id",
"caso.responsavel.dadosPessoais.nome",
"caso.escrivao.id",
"caso.escrivao.dadosPessoais.nome"};
I want to group them in Maps.
Like:
caso->
id
sigiloso,
...
unidadeDoCaso->
id
competencia->
id
ativo
...
responsavel->
id
dadosPessoais->
nome
...
...
...
I was able to group just one level. I was wondering if there's a way to do it recursively.
In spite of my suggestion I decided to provide this. There are two recursive routines.
one to fill the map.
the other to print it.
String[] array = {
"caso.id","caso.unidadeDoCaso.id","caso.etiqueta",
"caso.sigiloso","caso.idPecaSegredoJustica","caso.numeroAno",
"caso.numero","caso.competencia.id","caso.competencia.ativo",
"caso.competencia.nome","caso.responsavel.id",
"caso.responsavel.dadosPessoais.nome","caso.escrivao.id",
"caso.escrivao.dadosPessoais.nome"
};
Create the map
Then iterated across the data, splitting on the dot.
then call fill with the map, just split nodes, and the starting node index.
Map<String, Object> map = new HashMap<>();
for (String s : array) {
String[] nodes = s.split("\\.");
fill(map, nodes, 0);
}
print(map, "");
prints
caso
unidadeDoCaso
id
etiqueta
idPecaSegredoJustica
escrivao
id
dadosPessoais
nome
sigiloso
numero
id
numeroAno
responsavel
id
dadosPessoais
nome
competencia
ativo
nome
id
The fill method continues until the supplied nodes are all processed.
first the map is checked to see if the node exists or not(equal to null)
if not present, a new map is constructed and added to the supplied map. Then the method is called to process the next node.
otherwise, the method is called to add the current node to the map after the one that exists and continue processing the nodes.
public static void fill(Map<String, Object> map, String[] nodes, int i) {
if (i >= nodes.length) {
return;
}
String node = nodes[i];
#SuppressWarnings("unchecked")
Map<String, Object> obj = (Map<String, Object>)(node);
if (obj == null) {
Map<String, Object> m = new HashMap<>();
map.put(node, m);
fill(m, nodes, i + 1);
} else {
fill( obj, nodes, i + 1);
}
}
This prints the map elements and indents each subsequent nested map level on a separate line.
#SuppressWarnings("unchecked")
public static void print(Map<String, Object> map, String indent) {
for (String key : map.keySet()) {
if (key != null) {
System.out.println(indent + key);
print((Map<String, Object>) map.get(key), indent + " ");
}
}
}
Here is how you could do this using a Map<String, Map> and mutable reduction using the collect method that takes a supplier, accumulator, and combiner. The API is not the most pleasant to use, as WJS pointed out.
It requires unchecked casts because you can't represent these recursive structures of unknown depth using generics.
class Scratch {
public static void main(String[] args) {
var list = new String[]{"caso.id",
"caso.unidadeDoCaso.id",
"caso.etiqueta",
"caso.sigiloso",
"caso.idPecaSegredoJustica",
"caso.numeroAno",
"caso.numero",
"caso.competencia.id",
"caso.competencia.ativo",
"caso.competencia.nome",
"caso.responsavel.id",
"caso.responsavel.dadosPessoais.nome",
"caso.escrivao.id",
"caso.escrivao.dadosPessoais.nome"};
Map<String, Map> result = Arrays.stream(list).collect(HashMap::new, Scratch::mapRecursively, HashMap::putAll);
System.out.println(result);
// {caso={unidadeDoCaso={id=null}, etiqueta=null, idPecaSegredoJustica=null, escrivao={id=null, dadosPessoais={nome=null}}, sigiloso=null, numero=null, id=null, numeroAno=null, responsavel={id=null, dadosPessoais={nome=null}}, competencia={ativo=null, nome=null, id=null}}}
System.out.println(result.get("caso").keySet());
// [unidadeDoCaso, etiqueta, idPecaSegredoJustica, escrivao, sigiloso, numero, id, numeroAno, responsavel, competencia]
}
#SuppressWarnings("unchecked")
private static void mapRecursively(HashMap<String, Map> map, String s) {
// first recursion: s = caso.competencia.id
// second recursion: s = id
int dot = s.indexOf('.');
// Base case 1
if (dot == -1) {
map.put(s, null);
return;
}
String key = s.substring(0, dot); // caso
String value = s.substring(dot + 1); // competencia.id
boolean isFirstTimeToComeAcrossWord = !map.containsKey(key);
if (isFirstTimeToComeAcrossWord) {
map.put(key, new HashMap<>());
}
// Base case 2
int dot2 = value.indexOf('.');
if (dot2 == -1) {
map.get(key).put(value, null);
return;
}
String newKey = value.substring(0, dot2); // competencia
String leftover = value.substring(dot2 + 1); // id
boolean isFirstTimeWeComeAcrossNestedWord = !map.get(key).containsKey(newKey);
// Recursive cases
if (isFirstTimeWeComeAcrossNestedWord) {
var newMap = new HashMap<String, Map>();
map.get(key).put(newKey, newMap);
mapRecursively(newMap, leftover);
} else {
mapRecursively((HashMap<String, Map>) map.get(key).get(newKey), leftover);
}
}
}

Converting delimited string/text into a map object

I have been trying to figure out a solution for a while now, but I can't seem to get any suitable answer after thinking through/surfing the net for a solution. Hope the community can help me out!
I have some string and wishes to convert them into a nested map object, example below.
Fruits.Apple.Red
Fruits.Apple.Green
Fruits.Orange.Yellow
Fruits.Watermelon.Yellow
Fruits.Watermelon.Red
I would like to convert the above example into something like this.
{ Fruits:{
Apple:{
Red: null,
Green: null
},
Orange:{
Yellow: null
},
Watermelon:{enter code here
Yellow: null,
Red: null
}
}
}
Pardon me if you find this example to be a bad one. There is a reason why the value for the last child is null, I am trying to reproduce the problem I am facing.
A recursive function addToMap may be implemented to find the key before the dot . as a delimiter, creating a map if necessary using Map::computeIfAbsent, or adding the key with null value to the top-level map:
#SuppressWarnings({"rawtypes", "unchecked"})
static void addToMap(String s, Map<String, Map> upperLevel) {
int dotPos = s.indexOf(".");
if (dotPos < 0) {
upperLevel.put(s, null);
} else {
String key = s.substring(0, dotPos);
addToMap(s.substring(dotPos + 1), upperLevel.computeIfAbsent(key, k -> new HashMap<>()));
}
}
Then the test may look as follows:
String[] arr = {
"Fruits.Cherry",
"Fruits.Apple.Red",
"Fruits.Apple.Green",
"Fruits.Orange.Yellow",
"Fruits.Watermelon.Yellow",
"Fruits.Watermelon.Red",
};
Map<String, Map> result = new HashMap<>();
for (String t : arr) {
addToMap(t, result);
}
System.out.println(result);
Output:
{Fruits={Apple={Red=null, Green=null}, Cherry=null, Watermelon={Red=null, Yellow=null}, Orange={Yellow=null}}}
If an insertion order is important, LinkedHashMap should be created instead of HashMap or an overridden version with Supplier<Map> may be used:
Supplier<Map> mapSupplier = LinkedHashMap::new;
Map<String, Map> result = mapSupplier.get();
for (String t : arr) {
addToMap(t, result, mapSupplier);
}
System.out.println(result);
#SuppressWarnings({"rawtypes", "unchecked"})
static void addToMap(String s, Map<String, Map> upperLevel, Supplier<Map> mapSupplier) {
int dotPos = s.indexOf(".");
if (dotPos < 0) {
upperLevel.put(s, null);
} else {
String key = s.substring(0, dotPos);
addToMap(s.substring(dotPos + 1), upperLevel.computeIfAbsent(key, k -> mapSupplier.get()), mapSupplier);
}
}
Output (order changed):
{Fruits={Cherry=null, Apple={Red=null, Green=null}, Orange={Yellow=null}, Watermelon={Yellow=null, Red=null}}}

Get array elements from HashMap in Java

I have a method that puts value in HashMap of type HashMap<String, Object[]> & returns the same HashMap.
Code for putting value in HashMap:
doc = Jsoup.connect(url).get();
for( org.jsoup.nodes.Element element : doc.getAllElements() )
{
for( Attribute attribute : element.attributes() )
{
String option_ID=element.tagName()+"_"+attribute.getKey()+"_"+attribute.getValue();
String HTMLText=element.text();
int HTMLTextSize=HTMLText.length();
if(!HTMLText.isEmpty())
data.put("Test"+i,new Object[{"Test"+i,option_ID,HTMLText,HTMLTextSize});//adding value in HashMap.
i++;
}
}
I tried iterating as below, which I think is not the correct way :
HashMap<String, Object[]>set=HTMLDocument.createHTMLSet("URL of website");
Iterator it = set.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue());
}
As I am getting output as :
Test79 = [Ljava.lang.Object;#14e1a0f
Test378 = [Ljava.lang.Object;#1a5f880
How should I iterate over this HashMap to get Object[] values such as option_ID, HTMLText?
Since each object has toString() method, the default displays the class name representation, then adding # sign and then the hashcode, that's why you're getting the output
[Ljava.lang.Object;#14e1a0f
that means the array contains a class or interface.
One solution would be looping on the array and print each part (or using Arrays.toString method), but I highly recommend you wrapping this to your own class and override the toString method.
The following code might help. Its always better to create a bean class consisting of the necessary information to be stored in an array of objects.
package stack.overflow;
import java.util.HashMap;
import java.util.Map;
public class RetrieveMap {
public static void main(String[] args) {
Person p = new Person();
p.setName("John");
p.setEmpNo("1223");
p.setAge("34");
Person p1 = new Person();
p1.setName("Paul");
p1.setEmpNo("1224");
p1.setAge("35");
Person[] arr = new Person[2];
arr[0] = p ;
arr[1] = p1;
HashMap<String,Person[]> map = new HashMap<String,Person[]>();
map.put("a1", arr);
for(Map.Entry<String, Person[]> entry : map.entrySet()) {
System.out.println("Key:" +entry.getKey());
System.out.println("Value:" +entry.getValue());
for(int i=0;i<entry.getValue().length;i++) {
System.out.println("------------------");
System.out.println("Array:"+i);
Person r1 = (Person)entry.getValue()[i];
System.out.println("Name:" +r1.getName());
System.out.println("Age:" + r1.getAge());
System.out.println("Emp no:" + r1.getEmpNo());
System.out.println("------------------");
}
}
}
}
package stack.overflow;
public class Person {
String name;
String age;
String empNo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
}
The short answer is your code is behaving exactly correctly; when you call .toString() on an Object[] (which happens implicitly with System.out.println()) you get that odd [<TYPE>#<IDENTIFIER> string. To print the contents of an array, use Arrays.toString().
There are a number of things we can clean up with this code, though.
Avoid mixing generics and arrays (Effective Java Item 25); arrays lack the type safety generics provide, and there's rarely a good reason to use them in modern generic code. A better type signature would be HashMap<String, List<Object>>. This is effectively identical, but in practice much easier to work with.
Don't use arrays to store different types. You appear to be storing a "Test" string, a identifier string, the element's text, and the text's length as fields in an array. This is what objects are for. Define an object with those four fields, and pass them into the constructor. Even better, since everything but i is computable from the element, just pass the element into the constructor and compute the information you need (HTML string, length, etc.) in the constructor or even in the class' getters.
Don't use raw types (Effective Java Item 23) for Iterators and Map.Entrys. Your IDE can warn you when you use raw types so you avoid this common programming error. In your code you should use Iterator<Entry<String, Object[]>> and Entry<String, Object[]>
Don't use Iterator to loop over a Map's elements, use a for-each loop:
for (Entry<String, ...> e : map.entrySet()) {
...
}
Don't call a Map variable a set; they're different things. Similarly a Map.Entry is not a pair - it specifically represents a key-value relationship.
Here's a cleaned-up version of your code, assuming a Container object exists that takes an Element and extracts the data you need.
doc = Jsoup.connect(url).get();
for (org.jsoup.nodes.Element element : doc.getAllElements()) {
for (Attribute attribute : element.attributes()) {
Container c = new Container(i++, attribute);
data.put(c.getKey(), c);
}
}
And:
HashMap<String, Container> map = HTMLDocument.createHTMLMap("URL of website");
for (Entry<String, Container> e : map.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
The value is array of Object. Try following instead
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
System.out.println(pair.getKey() + " = " + pair.getValue()[0].toString());
}

Java HashMap, get(key) method doesnt work

I'm trying to create a PhoneBook class that uses a HashMap in Java. When I add an entry using the put() method in addContact(), it works fine, but when I try to retrieve values in the searchContact() method, none are returned. I'm not getting null values; HashMap definitely contains the key(s) I am searching for, but the values associated with the key(s) are not being returned. Thank you in advance.
Here is my code:
public class PhoneBookContactsImpl {
private Map<String, List<String>> contactMap = new HashMap<String, List<String>>();
public void addContact(String name, List<String> list) {
contactMap.put(name, list);
//its working fine here
System.out.println(contactMap.get(name));
}
public Map<String, List<String>> getContactMap() {
Set set = contactMap.entrySet();
Iterator i = contactMap.entrySet().iterator();
while (i.hasNext()) {
Map.Entry me = (Map.Entry) i.next();
System.out.println(me.getKey() + " : ");
List<String> nos = (List<String>) me.getValue();
System.out.println("Nos = " + nos + " n ");
System.out.println(nos.size());
}
return contactMap;
}
public List<String> searchContact(String name) throws NoDataFoundException {
if (contactMap.isEmpty()) {
System.out.println("Empty PhoneBook");
throw new NoDataFoundException();
} else {
if (contactMap.containsValue(name))
return contactMap.get(name);
//it doesnt retrieve valur from here
else {
System.out.println("No Entry for Specified Entry");
throw new NoDataFoundException();
}
}
}
}
your if statement is checking if the phonebook has name as a value, so your get is never reached.
Try this:
if (contactMap.containsKey(name))
return contactMap.get(name);
As the other answer points out you should be checking containsKey because name is a key, not a value. But why not make the whole thing much easier:
public List<String> searchContact(String name) throws NoDataFoundException {
List<String> result = contactMap.get(name);
if (result == null) {
// empty map, or no matching value or value is null
throw new NoDataFoundException();
}
}
You are doing:
if (contactMap.containsValue(name))
return contactMap.get(name);
and you need to do:
if (contactMap.containsKey(name))
return contactMap.get(name);

How to Print treemap in reverse order

In my assignment we are read from a file the text:
To be, or not to be: that is the question:
Whether 'tis nobler in the mind to suffer
then count the times each has occured. I've been able to print this map unsorted, then I was able to make a TreeMap and print it in natural order (which is shown below). I don't know how to print in reverse order. I know a way to use a comparator, but I'm a little rusty so I've done what I can. Furthermore, I don't know how to set the comparator up to sort the Treemap into reverse order.
Here's my method to print Unsorted and Naturally sorted:
private static void sortPrintFrequencies(Map<String,Integer> vocabulary, PrintStream output {
Iterator iterator = vocabulary.keySet().iterator();
System.out.println("Unsorted");
while (iterator.hasNext()) {
String key = iterator.next().toString();
String value = vocabulary.get(key).toString();
String times = "times.";
String appears = "appears";
System.out.printf("%35s", key + " " + appears + " " + value + " "+ times);
System.out.println();
}
System.out.println("========================================");
System.out.println("SORTED NATURALLY BY KEY");
TreeMap newVocabulary = new TreeMap(vocabulary);
Iterator iterator2 = newVocabulary.keySet().iterator();
while (iterator2.hasNext()) {
String key = iterator2.next().toString();
String value = newVocabulary.get(key).toString();
String times = "times.";
String appears = "appears";
System.out.printf("%35s", key + " " + appears + " " + value + " "+ times);
System.out.println();
}
TreeMap revVocabulary = new TreeMap(new RevCmpKey());
System.out.println("========================================");
}
Here's my comparator:
import java.util.*;
public class RevCmpKey implements Comparator<String> {
public int compare(String e1, String e2) {
//compareTo in String classs
if(e1.compareTo(e2) <1)return -1;
if(e1.compareTo(e2) >1)return 1;
return 0;
}
}
What about copying your Map into a new one naturally reverse ordered?
new TreeMap<String,Integer>(Collections.reverseOrder())
Short Answer:
Use descendingKeySet or descendingMap.
Long Answer:
Solution 1:
As Oliver correctly mentioned, you can copy the map into a new TreeMap to reach your goal.
However, when using descendingKeySet, you won't need to create a new TreeMap:
treeMap.descendingKeySet()
Here's an example:
private static void printReverseTreeMap(TreeMap<String,Integer> treeMap){
for(String key : treeMap.descendingKeySet()){
System.out.println("value of " + key + " is " + treeMap.get(key));
}
}
Solution 2:
You can also create a new Map in reverse order using descendingMap as well as Collections.reverseOrder():
NavigableMap<String, Integer> reveresedTreeMap = treeMap.descendingMap();
Note that descendingMap returns NavigableMap.
Since String is already comparable, the inverse Comparator is trivial:
public class RevCmpKey implements Comparator<String> {
public int compare(String e1, String e2) {
return - e1.compareTo(e2);
}
}
The other problem is that you are not specifying the values for the Generics; When you construct the TreeMap, you should use
TreeMap<String, Integer> revVocabulary = new TreeMap<String, Integer>(new RevCmpKey());
Then you just call putAll and that is enough
Here you can also prepare a ReverseComparator and use for any class, used in Ordered-Collection :
class ReverseComparator implements Comparator<Comparable<Object>> {
#Override
public int compare(Comparable<Object> o1, Comparable<Object> o2) {
return o2.compareTo( o1 );
}
}
As usually we compare o1 with o2, but for reverse compare o2 with o1
Just try below
private TreeMap<BigInteger, List<TicketingDocumentServiceCouponHistory>> getCpnHistoryMap(
List<TicketingDocumentHistory> tktHistoryList,List<TicketingDocumentServiceCouponTicket> couponList){
TreeMap<BigInteger, List<TicketingDocumentServiceCouponHistory>> cpnHistoryMap = new TreeMap<>(Collections.reverseOrder());
cpnHistoryMap.put(BigInteger.valueOf(Integer.MAX_VALUE), getOcCpnHistoryList(couponList));
tktHistoryList
.stream()
.filter(history -> history.getCode().equals(RVL))
.forEach(history -> cpnHistoryMap.put(history.getSequence(), getCpnHistoryList(cpnHistoryMap, history)));
TreeMap<BigInteger, List<TicketingDocumentServiceCouponHistory>> cpnHistMapInOrder = new TreeMap<>();
cpnHistMapInOrder.putAll(cpnHistoryMap);
return cpnHistMapInOrder;
}

Categories

Resources