This is my first question in Stackoverflow.I have come to find a issue with one of the problem suggested and give to me by my colleague to do some research on it.
My question is
i have a class
Class Function{
String func;
String funcname;
boolean log;
}
i have created some objects:
obj1 : ("a" ,"b",true)- //these values come from either DB or UI
obj2 : ("c" ,"x",true)
obj3 : ("a" ,"z",true)
i have a list:
List<function> flist;
now i want to have that list in the map and want to put in inside the map
Map<String, List<function>> funcMap
and then display this following output:
a:[obj1 obj3]
b:[obj2]
if i have the list but how to go about and find the above output as desired
Try this,
add all the objects in the flist.
initialize the map
Map<String, List<Function>> funcMap = new HashMap<String, List<Function>>();
going to add the object to the relevant key based on the func value the object will add to the value list.
for (Function functionValue : flist)
{
List<Function> functionList = funcMap.get(functionValue.getFunc());
if (functionList != null && !functionList.isEmpty())
{
functionList.add(functionValue);
}
else
{
functionList = new ArrayList<Function>();
functionList.add(functionValue);
funcMap.put(functionValue.getFunc(), functionList);
}
}
Atlast print the funcMap
for (Map.Entry< String, List<Function>> entry : funcMap.entrySet())
{
System.out.println("Key : " + entry.getKey() + "Values : "+entry.getValue());
}
Hmm.. I think it's a case of parsing your list in a nested loop kind of way. Here is the pseudo-code:
public void listToMap(List<Function> list)
{
Map<String, List<Function>> map := new Map
for every function in the list.
{
if(is the current function's func value does not exist in the map)
{
func := current functions func value
List matchingFunctions := new list of Functions.
for(every function in the list.)
{
// Every Function with the same key get's added to a list.
if(function has the same func value as func)
{
add to matchingFunctions.
}
}
// That list and key get put into the HashMap.
map.put(func, matchingFunctions).
}
}
}
A Note on your code design
Java convention states that you should wrap your member objects up in getters and setters, and that those members should be private.
what about:
public class FuncTest {
public static void main(String[] args) {
new FuncTest().start();
}
private void start() {
List<Function> flist = new ArrayList<Function>();
flist.add(new Function("a", "b", true));
flist.add(new Function("c", "x", true));
flist.add(new Function("a", "z", true));
Map<String, List<Function>> funcMap = new HashMap<String, List<Function>>();
for (Function func : flist) {
this.add(func.func, func, funcMap);
this.add(func.funcname, func, funcMap);
}
}
private void add(String field, Function func, Map<String, List<Function>> funcMap) {
List<Function> subList = funcMap.get(field);
if (subList == null) {
subList = new ArrayList<Function>();
funcMap.put(field, subList);
}
subList.add(func);
}
}
Note
As already mentioned by Chris you should think about your code design. Use getters and setters ..
public class Stackoverflow {
public static void main(String[] args) {
Function obj1 = new Function("a" ,"b",true);
Function obj2 = new Function("c" ,"x",true);
Function obj3 = new Function("a" ,"z",true);
List<Function> functionsList1 = new ArrayList<Function>();
functionsList1.add(obj1);
functionsList1.add(obj3);
List<Function> functionsList2 = new ArrayList<Function>();
functionsList2.add(obj2);
Map<String, List<Function>> funcMap = new LinkedHashMap<String, List<Function>>();
funcMap.put("a", functionsList1);
funcMap.put("b", functionsList2);
Set<Entry<String,List<Function>>> entrySet = funcMap.entrySet();
for (Entry<String, List<Function>> entry : entrySet) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
class Function {
String func;
String funcname;
boolean log;
public Function(String func, String funcname, boolean log) {
super();
this.func = func;
this.funcname = funcname;
this.log = log;
}
#Override
public String toString() {
return "Function [func=" + func + ", funcname=" + funcname + ", log="
+ log + "]";
}
}
Write your own map.
Pass the list to map, let map will decide what portion of list to keep as value.
I have added put method here, like the same, have to Override other methods.
class MyHashMap<K,V> extends HashMap<K,V>{
#SuppressWarnings("unchecked")
public V put(K k, V v) {
String key = (String)k;
List<Function> list = (List<Function>) v;
List<Function> list2 = new ArrayList<Function>();
for (Function function : list) {
if(key.equalsIgnoreCase(function.func)){
list2.add(function);
}
}
return (V) list2;
};
#Override
public boolean equals(Object o) {
// Your own code
return true;
}
// other methods goes here..
}
Related
I have an ArrayList that contains the below elements of type class: ListClass
public class ListClass {
String requestPath; //parse
List<String> paramsMandatory; //name, query
List<String> paramsOptional; //company
boolean needBody; //true
String mimeType; //String, json
public String getRequestPath() {
return requestPath;
}
public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
}
public List<String> getParamsMandatory() {
return paramsMandatory;
}
public void setParamsMandatory(List<String> paramsMandatory) {
this.paramsMandatory = paramsMandatory;
}
public List<String> getParamsOptional() {
return paramsOptional;
}
public void setParamsOptional(List<String> paramsOptional) {
this.paramsOptional = paramsOptional;
}
public boolean isNeedBody() {
return needBody;
}
public void setNeedBody(boolean needBody) {
this.needBody = needBody;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}
Setting the properties of it in another class: PropSetter
public class PropSetter {
List<String> mp = new ArrayList<String>();
List<String> op = new ArrayList<String>();
public void setParameters() {
mp.add("force");
mp.add("name");
op.add("company");
op.add("location");
ListClass lc = new ListClass();
lc.setRequestPath("/parse");
lc.setParamsMandatory(mp);
lc.setParamsOptional(op);
lc.setNeedBody(false);
lc.setMimeType("String");
System.out.println("Set the props for ListClass");
}
}
I am trying to return an ArrayList that is of Type: ListClass in the below way:
List<ListClass> cl = new ArrayList<ListClass>();
public void setCL() {
PropSetter ps = new PropSetter();
ps.setParameters();
ListClass lcl = new ListClass();
cl.add(lcl);
}
public List<ListClass> getPropList() {
return cl;
}
The method getPropList returns the List of type: ListClass. How do I access elements in it ?
If it is a particular datatype, I could've used an Iterator or a foreach loop. But this is of a class type: ListClass which I dont understand how to access the elements, particularly the lists: paramsMandatory & paramsOptional inside.
I tried to display the elements like:
CreateList cl = new CreateList();
cl.setCL();
List<ListClass> ll = cl.getPropList();
System.out.println("Size of Arraylist<ListClass>:" + ll.size());
for (ListClass l: ll) {
System.out.println("DS: " + l);
}
In my main class, I tried to see if I can print a parameter inside as below.
for (ListClass l: ll) {
System.out.println("DS: " + l.getRequestPath);
}
which gives me a compilation error: getRequestPath cannot be resolved or is not a field
I tried to print the size of array and it shows the right value:
System.out.println("Size of Arraylist<ListClass>:" + ll.size());
Size of Arraylist<ListClass>: 1
Could anyone let me know how can I access the elements coming from : getPropList
Look at the compiler error you are getting, namely:
getRequestPath cannot be resolved or is not a field
In other words, the compiler thinks that getRequestPath is a member of class ListClass - because you forgot to add parentheses so as to indicate to the compiler that getRequestPath is a method. Hence you need to change that line of your code as follows:
System.out.println("DS: " + l.getRequestPath());
I have two java classes:
public class MyClass1 {
private String userId;
private String userName;
private List<CustomList1> customList1;
// getters and setters
// inner CustomList1 class
}
public class MyClass2 {
private String userId;
private List<CustomList2> customList2;
// getters and setters
// inner CustomList2 class
}
Now, I have have lists of these classes:
List<MyClass1> classOneList;
List<MyClass2> classTwoList;
In both classOneList and classTwoList lists, object should be sorted with userId ascending. userId in both lists should have same values. What I want to check is that:
Has both lists same size? If not, thow error exception about.
Has every next element from both list the same userId? If not, throw another exception.
Step 1. I have done with simply if statement.
By prototype, step 2. should look like this:
for (el1, el2 : classOneList, classTwoList) {
el1.getUserId().isEqualTo(el2.getUserId());
}
Try the below code for your problem.
public class Testing {
public static void main(String[] args) {
Map<String, List<String>> map1 = new LinkedHashMap<String, List<String>>();
List<String> m1l1 = new LinkedList<String>();
m1l1.add("One");
m1l1.add("Two");
m1l1.add("Three");
m1l1.add("Four");
map1.put("1", m1l1);
List<String> m1l2 = new LinkedList<String>();
m1l2.add("One");
m1l2.add("Two");
m1l2.add("Three");
m1l2.add("Four");
map1.put("2", m1l2);
// Add more element into the map1 by creating more list.
Map<String, List<String>> map2 = new LinkedHashMap<String, List<String>>();
List<String> m2l1 = new LinkedList<String>();
m2l1.add("One");
m2l1.add("Two");
m2l1.add("Three");
m2l1.add("Four");
map2.put("1", m2l1);
// Add more element into the map2 by creating more list.
for (Entry<String, List<String>> entry : map1.entrySet()) {
if (map2.containsKey(entry.getKey())) {
if (entry.getValue().size() == map2.get(entry.getKey()).size()) {
} else {
System.out.println("UserId are same but list are different for userid: " + entry.getKey());
}
}
else {
System.out.println("Userid '"+entry.getKey()+"' exists in map1 but is not found in map2");
}
}
}
}
Hope this may help you.
if(classOneList.size() != classTwoList.size()){
throw new ErrorException();
}else{
classOneList = classOneList.stream().sorted(Comparator.comparing(MyClass1::getUserId)).collect(Collectors.toList());
classTwoList = classTwoList.stream().sorted(Comparator.comparing(MyClass2::getUserId)).collect(Collectors.toList());
for (int i = 0; i < classOneList.size(); i++){
if(!classOneList.get(i).getUserId().equals(classTwoList.get(i).getUserId())){
throw new AnotherErrorException();
}
}
}
I have a below class and would like to transform the list of data objects into the pivot table format with java.
public class Data {
private String consultedOn;
private String consultedBy;
// Getters
// Setters
}
List<Data> reports = new ArrayList<Data>();
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("04/12/2018","Mr.Jhon"));
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("05/12/2018","Mr.Jhon"));
reports.add(new Data("06/12/2018","Mr.Bob"));
reports.add(new Data("06/12/2018","Mr.Jhon"));
reports.add(new Data("07/12/2018","Mr.Bob"));
I would like to transform the above list into the below table format with java within a collection.
consultedOn Mr.Bob Mr.Jhon
---------------------------------------
04/12/2018 2 1
05/12/2018 0 1
06/12/2018 1 1
07/12/2018 1 0
Note that the consultedOn field is not restricted to two values, this field may contain any data so that the collection should be dynamic.
I tried using Java8 streams with below code.
class DataMap {
private String consultedOn;
private String consultedBy;
public DataMap(String consultedOn) {
super();
this.consultedOn = consultedOn;
}
public DataMap(String consultedOn, String consultedBy) {
super();
this.consultedOn = consultedOn;
this.consultedBy = consultedBy;
}
public String getConsultedOn() {
return consultedOn;
}
public void setConsultedOn(String consultedOn) {
this.consultedOn = consultedOn;
}
public String getConsultedBy() {
return consultedBy;
}
public void setConsultedBy(String consultedBy) {
this.consultedBy = consultedBy;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((consultedOn == null) ? 0 : consultedOn.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof DataMap ))
return false;
DataMap other = (DataMap )obj;
if (consultedOn == null) {
if (other.consultedOn != null)
return false;
} else if (!consultedOn.equals(other.consultedOn))
return false;
return true;
}
}
Map<DataMap, List<DataReport>> map = reports.stream()
.collect(Collectors.groupingBy(x -> new DataMap(x.getConsultedOn(), x.getConsultedBy())));
But the map is not giving intend results as per my expectations.
I'm not sure how to go-ahead with this kind of data, any help will be appreciated.
Here's a complete answer, using the technique I explained in the comment, i.e. design a class Row representing what you want to generate for each row, i.e. a consultedOn string, and a number of consultations for each person.
public class Pivot {
private static final class Data {
private final String consultedOn;
private final String consultedBy;
public Data(String consultedOn, String consultedBy) {
this.consultedOn = consultedOn;
this.consultedBy = consultedBy;
}
public String getConsultedOn() {
return consultedOn;
}
public String getConsultedBy() {
return consultedBy;
}
}
private static final class Row {
private final String consultedOn;
private final Map<String, Integer> consultationsByPerson = new HashMap<>();
public Row(String consultedOn) {
this.consultedOn = consultedOn;
}
public void addPerson(String person) {
consultationsByPerson.merge(person, 1, Integer::sum);
}
public int getConsultationsFor(String person) {
return consultationsByPerson.getOrDefault(person, 0);
}
public String getConsultedOn() {
return consultedOn;
}
}
private static class PivotReport {
private final Map<String, Row> rowsByConsultedOn = new HashMap<>();
private SortedSet<String> persons = new TreeSet<>();
private PivotReport() {}
private void addData(Data d) {
rowsByConsultedOn.computeIfAbsent(d.getConsultedOn(), Row::new).addPerson(d.getConsultedBy());
persons.add(d.consultedBy);
}
public static PivotReport create(List<Data> list) {
PivotReport report = new PivotReport();
list.forEach(report::addData);
return report;
}
public String toString() {
String headers = "Consulted on\t" + String.join("\t", persons);
String rows = rowsByConsultedOn.values()
.stream()
.sorted(Comparator.comparing(Row::getConsultedOn))
.map(this::rowToString)
.collect(Collectors.joining("\n"));
return headers + "\n" + rows;
}
private String rowToString(Row row) {
return row.getConsultedOn() + "\t" +
persons.stream()
.map(person -> Integer.toString(row.getConsultationsFor(person)))
.collect(Collectors.joining("\t"));
}
}
public static void main(String[] args) {
List<Data> list = createListOfData();
PivotReport report = PivotReport.create(list);
System.out.println(report);
}
private static List<Data> createListOfData() {
List<Data> reports = new ArrayList<Data>();
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("04/12/2018","Mr.Jhon"));
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("05/12/2018","Mr.Jhon"));
reports.add(new Data("06/12/2018","Mr.Bob"));
reports.add(new Data("06/12/2018","Mr.Jhon"));
reports.add(new Data("07/12/2018","Mr.Bob"));
reports.add(new Data("07/12/2018","Mr.Smith"));
return reports;
}
}
Note that since you're using String instead of LocalDate for the consultedOn field, the dates will be sorted lexicographically instead of being sorted chronologically. You should use the appropriate type: LocalDate.
You are probably looking to use Collectors.groupingBy to group the List<DataMap> by consultedOn and further grouping it by consultedBy attribute and their count as :
Map<String, Map<String, Long>> finalMapping = reports.stream()
.collect(Collectors.groupingBy(DataMap::getConsultedOn,
Collectors.groupingBy(DataMap::getConsultedBy,Collectors.counting())));
This would provide you as an output:
{05/12/2018={Mr.Jhon=1}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}
Further, if you require all the corresponding consultedBy values to be accounted in, you can create a Set of those from the initial List<DataMap> as :
Set<String> consultedBys = reports.stream()
.map(DataMap::getConsultedBy)
.collect(Collectors.toSet());
using which you can modify your existing map obtained to contain 0 count as well in the following manner:
finalMapping.forEach((k, v) -> consultedBys.forEach(c -> v.putIfAbsent(c, 0L)));
This would now provide you as the output:
{05/12/2018={Mr.Jhon=1, Mr.Bob=0}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Jhon=0, Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}
The other would be like this:
Map<Pair<String, String>, Integer> map = reports
.stream()
.collect(toMap(data -> new Pair<>(data.getConsultedOn(),
data.getConsultedBy()), data -> 1, Integer::sum));
Map<String, DataMap> result= new HashMap<>();
-
class DataMap {
private String consultedOn;
private Map<String, Integer> map;
}
-
Set<String> persons = new HashSet<>();
persons = reports.stream().map(Data::getConsultedBy).collect(Collectors.toSet());
-
for (Map.Entry<Pair<String, String>, Integer> entry : map.entrySet()) {
Map<String, Integer> val = new HashMap<>();
for (String person : persons) {
if (!person.equals(entry.getKey().getValue()))
val.put(person, 0);
else
val.put(entry.getKey().getValue(), entry.getValue());
}
result.put(entry.getKey().getKey(), new DataMap(entry.getKey().getKey(), val));
}
and final result:
List<DataMap> finalResult = new ArrayList<>(result.values());
Instead of using a separate data structure, you can use a Map of key as consultedOn (date or String) and have the value as a list of (String or your own defined POJO with overridden equals() method.Here in I have used a map like Map<String, List<String>>
All you need is the two methods:
one to set report (addDataToReport) : for each consultedOn (key), create a list of doctors consulted . See comments for map.merge usage
and one to display the data in a report manner (printReport). We are using "%10s" to give proper formatting. Instead of println, format doesn't implicitly append a new line character
Moreover to get the report's column we need to have a set (unique value list), doctors.add(consultedBy); will serve us for this purpose . Java will take care of keeping the doctors' value unique.
public class Application {
Set<String> doctors = new LinkedHashSet<>();
private void addDataToReport(Map<String, List<String>> reportMap, String consultedOn, String consultedBy) {
doctors.add(consultedBy); // set the doctors Set
reportMap.merge(consultedOn, Arrays.asList(consultedBy)// if key = consultedOn is not there add , a new list
, (v1, v2) -> Stream.concat(v1.stream(), v2.stream()).collect(Collectors.toList()));//else merge previous and new values , here concatenate two lists
}
private void printReport(Map<String, List<String>> reportMap) {
/*Set Headers*/
String formatting = "%10s";//give a block of 10 characters for each string to print
System.out.format(formatting, "consultedOn");
doctors.forEach(t -> System.out.format(formatting, t));// print data on console without an implicit new line
System.out.println("\n---------------------------------------");
/*Set row values*/
for (Map.Entry<String, List<String>> entry : reportMap.entrySet()) {
Map<String, Integer> map = new LinkedHashMap<>();
doctors.forEach(t -> map.put(t, 0)); // initialise each doctor count on a day to 0
entry.getValue().forEach(t -> map.put(t, map.get(t) + 1));
System.out.format(formatting, entry.getKey());
map.values().forEach(t -> System.out.format(formatting, t));
System.out.println();
}
}
public static void main(String[] args) {
Application application = new Application();
Map<String, List<String>> reportMap = new LinkedHashMap<>();
String MR_JHON = "Mr.Jhon";
String MR_BOB = "Mr.Bob ";
application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
application.addDataToReport(reportMap, "04/12/2018", MR_JHON);
application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
application.addDataToReport(reportMap, "05/12/2018", MR_JHON);
application.addDataToReport(reportMap, "06/12/2018", MR_BOB);
application.addDataToReport(reportMap, "06/12/2018", MR_JHON);
application.addDataToReport(reportMap, "07/12/2018", MR_BOB);
application.printReport(reportMap);
}
}
Result
consultedOn Mr.Bob Mr.Jhon
---------------------------------------
04/12/2018 2 1
05/12/2018 0 1
06/12/2018 1 1
07/12/2018 1 0
Okay, might not be the right title, but here's what I'm thinking. I am looking for a way to loop through a set of variables. For example, I'm thinking in my game there would be a series of things you could unlock, and when you entered "unlocked" or something it would show you what you had unlocked and only what you had unlocked. So it would show only the Booleans that were false.
In Java, there is no easy way to get a variable based on its name. What you can do, however, is store all your variables in a HashMap. This will accomplish what you want.
HashMap<String, Boolean> vars = new HashMap<>();
vars.put("test1", true); // to make a new variable
vars.get("test1"); // to get a variable's value
If you want to store any variable type, change the statement that creates the list of variables.
HashMap<String, Object> vars = new HashMap<>();
To find only the booleans that are false, you can simply iterate over the HashMap, like so:
for(Entry<String, Boolean> entry : vars.entrySet()) {
String key = entry.getKey();
boolean value = entry.getValue();
if(!value) {
System.out.println(key + " was false");
}
}
There is a comprehensive tutorial here, if you want to learn more about maps in Java.
You could have a method that returns a collection of keys that have a false values. Depending on your requirements (if it's a homework assignment or something) there are a couple of ways you could do it. If it is required to use only primitives, you could do something like
public List<String> getUnlockedItems(Map<String, Boolean> map) {
// map validation (not null, has entries, etc)
final List<String> unlockedEntries = new ArrayList<>();
for (Map.Entry<String, Boolean> entry : map.entries()) {
if (entry.getValue() == true) {// if it has been unlocked
unlockedEntries.add(entry.getKey());
}
}
return unlockedEntries;
}
public static void main(String[] args) {
Map<String, Boolean> unlockables = new HashMap<>();
unlockables.put("Fire Punch", false);
unlockables.put("Ice Punch", false);
unlockables.put("Mega Punch", false);
unlockables.put("Thunder Punch", false);
// Player unlocks Fire Punch
unlockables.put("Fire Punch", true);
// Get list of unlockables they have unlocked
List<String> unlockedItems = getUnlockedItems(unlockables); // returns ["Fire Punch"]
}
If you're able to define your own types, it may be better in terms of maintainability and readability to do something like
public class Unlockable {
public Unlockable(String name) {
// name validation
this.name = name;
}
public void unlock() {
unlocked = true;
}
public boolean isUnlocked() {
return unlocked;
}
private String name;
private boolean unlocked;
}
And then something like
public List<Unlockable> getUnlockedItems(List<Unlockable> unlockables) {
List<Unlockable> unlockedItems = new ArrayList<>();
for (Unlockable unlockable : unlockables) {
if (unlockable.isUnlocked()) {
unlockedItems.add(unlockable);
}
}
return unlockedItems;
}
public static void main(String[] args) {
Unlockable firePunch = new Unlockable("Fire Punch");
Unlockable icePunch = new Unlockable("Ice Punch");
Unlockable megaPunch = new Unlockable("Mega Punch");
Unlockable thunderPunch = new Unlockable("Thunder Punch");
List<Unlockable> unlockables = new ArrayList<>();
unlockables.add(firePunch);
unlockables.add(icePunch);
unlockables.add(megaPunch);
unlockables.add(thunderPunch);
// Player unlocks Fire Punch
firePunch.unlock();
// Get list of unlockables they have unlocked
List<Unlockable> unlockedItems = getUnlockedItems(unlockables); // returns ["Fire Punch"]
}
Just use a map:
Map<String, Boolean> m = new HashMap<String, Boolean>();
m.put("test1", false);
m.put("test2", true);
Usage:
System.out.println("is test1 " + m.get("test1"));
Is there a version of BeanUtils.describe(customer) that recursively calls the describe() method on the complex attributes of 'customer'.
class Customer {
String id;
Address address;
}
Here, I would like the describe method to retrieve the contents of the address attribute as well.
Currently, all I have can see the name of the class as follows:
{id=123, address=com.test.entities.Address#2a340e}
Funny, I would like the describe method to retrieve the contents of nested attributes as well, I don't understand why it doesn't. I went ahead and rolled my own, though. Here it is, you can just call:
Map<String,String> beanMap = BeanUtils.recursiveDescribe(customer);
A couple of caveats.
I'm wasn't sure how commons BeanUtils formatted attributes in collections, so i went with "attribute[index]".
I'm wasn't sure how it formatted attributes in maps, so i went with "attribute[key]".
For name collisions the precedence is this: First properties are loaded from the fields of super classes, then the class, then from the getter methods.
I haven't analyzed the performance of this method. If you have objects with large collections of objects that also contain collections, you might have some issues.
This is alpha code, not garunteed to be bug free.
I am assuming that you have the latest version of commons beanutils
Also, fyi, this is roughly taken from a project I've been working on called, affectionately, java in jails so you could just download it and then run:
Map<String, String[]> beanMap = new SimpleMapper().toMap(customer);
Though, you'll notice that it returns a String[], instead of a String, which may not work for your needs. Anyway, the below code should work, so have at it!
public class BeanUtils {
public static Map<String, String> recursiveDescribe(Object object) {
Set cache = new HashSet();
return recursiveDescribe(object, null, cache);
}
private static Map<String, String> recursiveDescribe(Object object, String prefix, Set cache) {
if (object == null || cache.contains(object)) return Collections.EMPTY_MAP;
cache.add(object);
prefix = (prefix != null) ? prefix + "." : "";
Map<String, String> beanMap = new TreeMap<String, String>();
Map<String, Object> properties = getProperties(object);
for (String property : properties.keySet()) {
Object value = properties.get(property);
try {
if (value == null) {
//ignore nulls
} else if (Collection.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertAll((Collection) value, prefix + property, cache));
} else if (value.getClass().isArray()) {
beanMap.putAll(convertAll(Arrays.asList((Object[]) value), prefix + property, cache));
} else if (Map.class.isAssignableFrom(value.getClass())) {
beanMap.putAll(convertMap((Map) value, prefix + property, cache));
} else {
beanMap.putAll(convertObject(value, prefix + property, cache));
}
} catch (Exception e) {
e.printStackTrace();
}
}
return beanMap;
}
private static Map<String, Object> getProperties(Object object) {
Map<String, Object> propertyMap = getFields(object);
//getters take precedence in case of any name collisions
propertyMap.putAll(getGetterMethods(object));
return propertyMap;
}
private static Map<String, Object> getGetterMethods(Object object) {
Map<String, Object> result = new HashMap<String, Object>();
BeanInfo info;
try {
info = Introspector.getBeanInfo(object.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
Method reader = pd.getReadMethod();
if (reader != null) {
String name = pd.getName();
if (!"class".equals(name)) {
try {
Object value = reader.invoke(object);
result.put(name, value);
} catch (Exception e) {
//you can choose to do something here
}
}
}
}
} catch (IntrospectionException e) {
//you can choose to do something here
} finally {
return result;
}
}
private static Map<String, Object> getFields(Object object) {
return getFields(object, object.getClass());
}
private static Map<String, Object> getFields(Object object, Class<?> classType) {
Map<String, Object> result = new HashMap<String, Object>();
Class superClass = classType.getSuperclass();
if (superClass != null) result.putAll(getFields(object, superClass));
//get public fields only
Field[] fields = classType.getFields();
for (Field field : fields) {
try {
result.put(field.getName(), field.get(object));
} catch (IllegalAccessException e) {
//you can choose to do something here
}
}
return result;
}
private static Map<String, String> convertAll(Collection<Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
Object[] valArray = values.toArray();
for (int i = 0; i < valArray.length; i++) {
Object value = valArray[i];
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + i + "]", cache));
}
return valuesMap;
}
private static Map<String, String> convertMap(Map<Object, Object> values, String key, Set cache) {
Map<String, String> valuesMap = new HashMap<String, String>();
for (Object thisKey : values.keySet()) {
Object value = values.get(thisKey);
if (value != null) valuesMap.putAll(convertObject(value, key + "[" + thisKey + "]", cache));
}
return valuesMap;
}
private static ConvertUtilsBean converter = BeanUtilsBean.getInstance().getConvertUtils();
private static Map<String, String> convertObject(Object value, String key, Set cache) {
//if this type has a registered converted, then get the string and return
if (converter.lookup(value.getClass()) != null) {
String stringValue = converter.convert(value);
Map<String, String> valueMap = new HashMap<String, String>();
valueMap.put(key, stringValue);
return valueMap;
} else {
//otherwise, treat it as a nested bean that needs to be described itself
return recursiveDescribe(value, key, cache);
}
}
}
The challenge (or show stopper) is problem that we have to deal with an object graph instead of a simple tree. A graph may contain cycles and that requires to develop some custom rules or requirements for the stop criteria inside the recursive algorithm.
Have a look at a dead simple bean (a tree structure, getters are assumed but not shown):
public class Node {
private Node parent;
private Node left;
private Node right;
}
and initialize it like this:
root
/ \
A B
Now call a describe on root. A non-recursive call would result in
{parent=null, left=A, right=B}
A recursive call instead would do a
1: describe(root) =>
2: {parent=describe(null), left=describe(A), right=describe(B)} =>
3: {parent=null,
{A.parent=describe(root), A.left=describe(null), A.right= describe(null)}
{B.parent=describe(root), B.left=describe(null), B.right= describe(null)}}
and run into a StackOverflowError because describe is called with objects root, A and B over and over again.
One solution for a custom implementation could be to remember all objects that have been described so far (record those instances in a set, stop if set.contains(bean) return true) and store some kind of link in your result object.
You can simple use from the same commom-beanutils:
Map<String, Object> result = PropertyUtils.describe(obj);
Return the entire set of properties for which the specified bean provides a read method.