I got a List in java. I get values from a SQL query.
public void ReloadPages() throws Exception {
try (Connection conn = Framework.GetDatabaseManager().GetBone().getConnection()) {
try (ResultSet set = conn.createStatement().executeQuery("SELECT * FROM habbo_shop_pages")) {
while (set.next()) {
int Id = set.getInt(1);
Pages.put(Id, new CatalogPage(set));
}
}
}
System.out.println("Loaded " + Pages.size() + " Catalog Page(s).");
}
Then I store it all. In another function, I want to retrieve certain pages from a parentid.
public LinkedList<CatalogPage> getSubPages(int parentId) {
LinkedList<CatalogPage> pages = new LinkedList<>();
for (CatalogPage page : this.Pages.values()) {
if (page.getParentId() != parentId) {
continue;
}
pages.add(page);
}
return pages;
}
How do I order the list? Now id 4 is above in the shop and 1 at the bottom, but I want it ordered by id. ORDER BY in query doesn't work.
Have your class implement Comparable and provide said sort ordering in compareTo method. And to sort, simply use Collections#sort, although be aware that it's an inline sort.
You want to sort the list in reverse order then try this:
Collections.sort(list,Collections.reverseOrder());
Like mre writes, you can let the elements in the list implement the Comparable interface. Alternatively the Collections#sort(List, Comparator) method, which allows you to sort on different keys, can be used. This can for example be used handle sorting on multiple fields of the elements in the list, e.g. implement a Comparator to sort on date and a different Comparator for sorting on id.
For more info look at the javadoc for the Comparator interface.
Example
Code:
package com.stackoverflow.questions;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
public class Question15586200 {
public static void main(String[] args) {
// Add test data
LinkedList<CatalogPage> catalogs = new LinkedList<CatalogPage>();
catalogs.add(new CatalogPage("foo 4", 4));
catalogs.add(new CatalogPage("bar 1", 1));
catalogs.add(new CatalogPage("foobar 2", 2));
catalogs.add(new CatalogPage("barfoo 3", 3));
// Sort by id
Collections.sort(catalogs, new Comparator<CatalogPage>() {
#Override
public int compare(CatalogPage o1, CatalogPage o2) {
return Integer.compare(o1.getId(), o2.getId());
}
});
// Print result
for (CatalogPage page : catalogs) {
System.err.println(page);
}
}
public static class CatalogPage {
private String name;
private int id;
public CatalogPage(String name, int id) {
this.name = name;
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
#Override
public String toString() {
return "CatalogPage [name=" + name + ", id=" + id + "]";
}
}
}
Ouput:
CatalogPage [name=bar 1, id=1]
CatalogPage [name=foobar 2, id=2]
CatalogPage [name=barfoo 3, id=3]
CatalogPage [name=foo 4, id=4]
You can do it using Lambda expressions, added in Java version 8.
Comparator<ProductColor> byProductColorName = (color1, color2) -> Integer.compare(
color1.getId(), color2.getId());
myProductColorList.stream().sorted(byProductColorName)
.forEach(e -> System.out.println(e));
Also i found this post very usefull.
List maintains insertion order. If you want to order in by ID, then use TreeSet and write a external Comparator, by implementing a compare() method of Comparator interface and pass it to the TreeSet constructor. Values will be sorted and ordered by ID.
Related
I want to create a Swingtable with a dynamic Layout, regarding the class data that is set as sources.
To be more specific:
I have a class with multiple attributes. When creating the table, I want to do it, so that the table looks: "which public getFunction with returntyp String are available" and use the attributes behind this functions as Columnnames and later also as source for the rows.
That is working at the moment.
My problem now is:
How can I ensure a specific order of my Columns with this approach?
for example i have a column "ID","callsign","categorie".
I want to display them in this order.
No metter how i order the methodes in sourceCode, the columns are allways in the same order ("ID","categorie","callsign").
java.lang.reflect.Method methodes[] = null;
methodes = classObjectOfT.getMethods();
List<String> tempList=new ArrayList<String>();
for (java.lang.reflect.Method m: methodes)
{
if (m.getReturnType().equals(String.class)&&m.getName().startsWith("get"))
{
tempList.add(m.getName().substring(3));
}
}
columnNames=(String[]) tempList.toArray(new String[tempList.size()]);
above is the code i use for retriving the columnames.
A workaround would be to name the attributes/getMethodes "ID_00","Callsign_01","Categorie_02" and do the ordering by using the last 2 chars of the String, but that would be rather ugly and i'm searching for a cleaner solution.
I would suggest creating an annotation which you would use to define the order and even more meta data for your table columns, for example a label:
#Retention(RetentionPolicy.RUNTIME)
#interface TableColumn {
String label();
int order();
}
Then retrieve them like this:
public Set<Method> findTableColumsGetters(Class<TestTableData> clazz) {
Set<Method> methods = new TreeSet<>(new Comparator<Method>() {
#Override
public int compare(Method o1, Method o2) {
return Integer.valueOf(o1.getAnnotation(TableColumn.class).order())
.compareTo(o2.getAnnotation(TableColumn.class).order());
}
});
for(Method method : clazz.getMethods()) {
if(method.isAnnotationPresent(TableColumn.class)) {
methods.add(method);
}
}
return methods;
}
Here is some testings:
Test table data
static class TestTableData {
private String id, callsign, categorie;
#TableColumn(label = "Caterogy", order = 3)
public String getCategorie() {
return categorie;
}
public void setCategorie(String categorie) {
this.categorie = categorie;
}
#TableColumn(label = "Call sign", order = 2)
public String getCallsign() {
return callsign;
}
public void setCallsign(String callsign) {
this.callsign = callsign;
}
#TableColumn(label = "ID", order = 1)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Test:
#Test
public void findTableColumsGetters() {
Set<Method> getters = findTableColumsGetters(TestTableData.class);
for(Method getter : getters) {
TableColumn annotation = getter.getAnnotation(TableColumn.class);
System.out.printf("%d %s (%s)%n", annotation.order(), annotation.label(), getter.getName());
}
}
Output:
1 ID (getId)
2 Call sign (getCallsign)
3 Caterogy (getCategorie)
I would suggest though that you don't retrieve the annotation reach time you need info from it, instead create a Metadata class for your methods where you put everyting including the method itself while you are performing the search.
I have to add User identified by his id into set and in runtime all users form that set have to be sorted by this id.
I've created TreeSet added some User objects and tried to iterate through it.
Here is my attempt:
//irrelevant code removed
TreeSet<User> userSet = new TreeSet<User>();
userSet.add(new User(2));
userSet.add(new User(1));
userSet.add(new User(3));
Iterator<User> iterator = userSet.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
I wrote class User where is one of the fields id and constructor have id as parameter.
public class User {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
}
When i run this code I get ClassCastException.
Here is the stacktrace:
Exception in thread "main" java.lang.ClassCastException: OrderedUsers$User cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at java.util.TreeSet.add(TreeSet.java:255)
at OrderedUsers.<init>(OrderedUsers.java:9)
at Main.main(Main.java:6)
What I am doing wrong?
You are on the right way when you decided to use TreeSet because with TreeSet you can get ordered output. But...
Note that if you use TreeSet, because of TreeSet is sorted you have to implement Comparable.
When you implement Comparable you will get what you expected.
I suggest that you perform changes like this:
public class User implements Comparable<User> {
private int id;
// irrelevant code removed
public User(int id) {
this.id = id;
}
// irrelevant code removed
public String toString() {
return id + "";
}
#Override
public int compareTo(User u) {
return id - u.id;
}
}
Either pass a custom Comparator to TreeSet constructor or implement Comparable in your model class
TreeSet maintains sorted order and it needs to know how Users can be compared
Here's the statement at TreeMap.java 1188:
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
So if comparator is null, then it will try to cast the set member to a (Comparable). If your class doesn't implement Comparable, this will cause the ClassCastException you saw. comparator is non-null only if you call the TreeMap constructor that provides a Comparator (or if you copy it from another SortedMap that already has a comparator).
TreeSet internally stores the object by performing comparasions with the existing ones similar to Binary Search Tree (actually its a Red-Black tree). So you need to implement interface Comparable in User or provide a custom Comparator to the TreeSet.
If you dont want the user objects to be stored in a sorted order I would recommend using ArrayList.
Method 1 :
public class User implements Comparable<User>
{
public int compare(User u)
{
if( u == null)
return 1;
return id - u.id;
}
}
Method 2 :
public class CompareUsers implements Comparator<User>
{
public int compareTo(User a, User b)
{
if(a == null)
return -1;
if(b == null)
return 1;
return a.id - b.id;
}
}
// Create an instance of this comparator class and pass to the TreeSet
// during initialization.
TreeSet<User> userSet = new TreeSet<User>(new CompareUsers());
How do I create an ArrayList with integer and string input types? If I create one as:
List<Integer> sections = new ArrayList <Integer>();
that will be an Integer type ArrayList.
If I create one as:
List<String> sections = new ArrayList <String>();
that will be of String type.
How can I create an ArrayList which can take both integer and string input types?
Thank you.
You can make it like :
List<Object> sections = new ArrayList <Object>();
(Recommended) Another possible solution would be to make a custom model class with two parameters one Integer and other String. Then using an ArrayList of that object.
(1)
ArrayList<Object> list = new ArrayList <>();`
list.add("ddd");
list.add(2);
list.add(11122.33);
System.out.println(list);
(2)
ArrayList arraylist = new ArrayList();
arraylist.add(5);
arraylist.add("saman");
arraylist.add(4.3);
System.out.println(arraylist);
You can use Object for storing any type of value for e.g. int, float, String, class objects, or any other java objects, since it is the root of all the class. For e.g.
Declaring a class
class Person {
public int personId;
public String personName;
public int getPersonId() {
return personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}}
main function code, which creates the new person object, int, float, and string type, and then is added to the List, and iterated using for loop. Each object is identified, and then the value is printed.
Person p = new Person();
p.setPersonId(1);
p.setPersonName("Tom");
List<Object> lstObject = new ArrayList<Object>();
lstObject.add(1232);
lstObject.add("String");
lstObject.add(122.212f);
lstObject.add(p);
for (Object obj : lstObject) {
if (obj.getClass() == String.class) {
System.out.println("I found a string :- " + obj);
}
if (obj.getClass() == Integer.class) {
System.out.println("I found an int :- " + obj);
}
if (obj.getClass() == Float.class) {
System.out.println("I found a float :- " + obj);
}
if (obj.getClass() == Person.class) {
Person person = (Person) obj;
System.out.println("I found a person object");
System.out.println("Person Id :- " + person.getPersonId());
System.out.println("Person Name :- " + person.getPersonName());
}
}
You can find more information on the object class on this link Object in java
List<Object> list = new ArrayList<>();
list.add(1);
list.add("1");
As the return type of ArrayList is object, you can add any type of data to ArrayList but it is not a good practice to use ArrayList because there is unnecessary boxing and unboxing.
You could create a List<Object>, but you really don't want to do this. Mixed lists that abstract to Object are not very useful and are a potential source of bugs. In fact the fact that your code requires such a construct gives your code a bad code smell and suggests that its design may be off. Consider redesigning your program so you aren't forced to collect oranges with orangutans.
Instead -- do what G V recommends and I was about to recommend, create a custom class that holds both int and String and create an ArrayList of it. 1+ to his answer!
Create your own class which stores the string and integer, and then make a list of these objects.
class Stuff {
private String label;
private Integer value;
// Constructor
public void Stuff(String label, Integer value) {
if (label == null || value == null) {
throw NullPointerException();
}
this.label = label;
this.value = value;
}
// getters
public String getLabel() {
return this.label;
}
public Integer getValue() {
return this.value;
}
}
Then in your code:
private List<Stuff> items = new ArrayList<Stuff>();
items.add(new Stuff(label, value));
for (Stuff item: items) {
doSomething(item.getLabel()); // returns String
doSomething(item.getValue()); // returns Integer
}
It depends on the use case. Can you, please, describe it more?
If you want to be able to add both at one time, than you can do the which is nicely described by #Sanket Parikh. Put Integer and String into a new class and use that.
If you want to add the list either a String or an int, but only one of these at a time, then sure it is the List<Object>
which looks good but only for first sight! This is not a good pattern. You'll have to check what type of object you have each time you get an object from your list. Also This type of list can contain any other types as well.. So no, not a nice solution. Although maybe for a beginner it can be used. If you choose this, i would recommend to check what is "instanceof" in Java.
I would strongly advise to reconsider your needs and think about maybe your real nead is to encapsulate Integers to a List<Integer> and Strings to a separate List<String>
Can i tell you a metaphor for what you want to do now? I would say you want to make a List wich can contain coffee beans and coffee shops. These to type of objects are totally different! Why are these put onto the same shelf? :)
Or do you have maybe data which can be a word or a number? Yepp! This would make sense, both of them is data! Then try to use one object for that which contains the data as String and if needed, can be translated to integer value.
public class MyDataObj {
String info;
boolean isNumeric;
public MyDataObj(String info){
setInfo(info);
}
public MyDataObj(Integer info){
setInfo(info);
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
this.isNumeric = false;
}
public void setInfo(Integer info) {
this.info = Integer.toString(info);
this.isNumeric = true;
}
public boolean isNumeric() {
return isNumeric;
}
}
This way you can use List<MyDataObj> for your needs. Again, this depends on your needs! :)
Some edition: What about using inharitance? This is better then then List<Object> solution, because you can not have other types in the list then Strings or Integers:
Interface:
public interface IMyDataObj {
public String getInfo();
}
For String:
public class MyStringDataObj implements IMyDataObj {
final String info;
public MyStringDataObj(String info){
this.info = info;
}
#Override
public String getInfo() {
return info;
}
}
For Integer:
public class MyIntegerDataObj implements IMyDataObj {
final Integer info;
public MyIntegerDataObj(Integer info) {
this.info = info;
}
#Override
public String getInfo() {
return Integer.toString(info);
}
}
Finally the list will be: List<IMyDataObj>
You don't know the type is Integer or String then you no need Generic. Go With old style.
List list= new ArrayList ();
list.add(1);
list.add("myname");
for(Object o = list){
}
You can always create an ArrayList of Objects. But it will not be very useful to you. Suppose you have created the Arraylist like this:
List<Object> myList = new ArrayList<Object>();
and add objects to this list like this:
myList.add(new Integer("5"));
myList.add("object");
myList.add(new Object());
You won't face any problem while adding and retrieving the object but it won't be very useful.
You have to remember at what location each type of object is it in order to use it. In this case after retrieving, all you can do is calling the methods of Object on them.
You can just add objects of diffefent "Types" to an instance of ArrayList. No need create an ArrayList. Have a look at the below example,
You will get below output:
Beginning....
Contents of array: [String, 1]
Size of the list: 2
This is not an Integer String
This is an Integer 1
package com.viswa.examples.programs;
import java.util.ArrayList;
import java.util.Arrays;
public class VarArrayListDemo {
#SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
System.out.println(" Beginning....");
ArrayList varTypeArray = new ArrayList();
varTypeArray.add("String");
varTypeArray.add(1); //Stored as Integer
System.out.println(" Contents of array: " + varTypeArray + "\n Size of the list: " + varTypeArray.size());
Arrays.stream(varTypeArray.toArray()).forEach(VarArrayListDemo::checkType);
}
private static <T> void checkType(T t) {
if (Integer.class.isInstance(t)) {
System.out.println(" This is an Integer " + t);
} else {
System.out.println(" This is not an Integer" + t);
}
}
}
Just use Entry (as in java.util.Map.Entry) as the list type, and populate it using (java.util.AbstractMap’s) SimpleImmutableEntry:
List<Entry<Integer, String>> sections = new ArrayList<>();
sections.add(new SimpleImmutableEntry<>(anInteger, orString)):
For me this method works perfectly fine in jdk 16
import java.util.ArrayList;
public class Array {
public static void main(String[] args) {
ArrayList arrayList= new ArrayList();
arrayList.add("alien");
arrayList.add(1);
arrayList.add(0,'b');
System.out.println(arrayList);
System.out.println((arrayList.get(0)) instanceof Integer);
}
}
Output
[b, alien, 1]
false
User Defined Class Array List Example
import java.util.*;
public class UserDefinedClassInArrayList {
public static void main(String[] args) {
//Creating user defined class objects
Student s1=new Student(1,"AAA",13);
Student s2=new Student(2,"BBB",14);
Student s3=new Student(3,"CCC",15);
ArrayList<Student> al=new ArrayList<Student>();
al.add(s1);
al.add(s2);
al.add(s3);
Iterator itr=al.iterator();
//traverse elements of ArrayList object
while(itr.hasNext()){
Student st=(Student)itr.next();
System.out.println(st.rollno+" "+st.name+" "+st.age);
}
}
}
class Student{
int rollno;
String name;
int age;
Student(int rollno,String name,int age){
this.rollno=rollno;
this.name=name;
this.age=age;
}
}
Program Output:
1 AAA 13
2 BBB 14
3 CCC 15
I have created a Vector object to store data in Table object as Vector<Table>. Vector<Table> contains components as below.
[Vector<Record> records, String tableName, String keyColumnName, int recordCount, int columnCount]
I need to sort tableName in above Vector to my own order and return Vector<Table> with sorted tableNames for other processes.
I have wrote method as below.
private Vector<Table> orderTables(Vector<Table> loadTables) {
List<String> tableNames = new ArrayList<String>();
for (Table table : loadTables) {
String tblName = table.getTableName();
tableNames.add(tblName);
}
Collections.sort(tableNames, new MyComparable());
return null;
}
But I have no idea about how to write Comparator to this. My own sort order is stored in .properties file. I can read it and get value. But I have no idea about how to compare it.
How could I do it?
Before clarification
You need to write a Comparator for Table objects that delegates to the tableName's comparator:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return one.getTableName().compareTo(two.getTableName());
}
}
Note that this will consider Tables that have the same name to be equal. This can mess things up if you put these tables in a HashMap or HashSet. To avoid this, you can detect this case and return one.hashCode() - two.hashCode() if the table names are the same.
Guava's ComparisonChain is a convenient way to write such multi-stage comparisons:
new Comparator<Table>() {
#Override public int compare(Table one, Table two) {
return ComparisonChain.start()
.compare(one.getTableName(), two.getTableName())
.compare(one.hashCode(), two.hashCode())
.result();
}
}
After clarification
Okay, the question is to impose a predefined sorting order rather than sorting the Tables by name. In that case, you need to make a Comparator that is aware of the ordering defined in the .properties file.
One way to achieve this is to initialize a mapping of table names to sorting order indices, and refer that mapping during the comparison. Given the property value:
SORT_ORDER = SALES,SALE_PRODUCTS,EXPENSES,EXPENSES_ITEMS
The mapping should look like:
{
SALES: 0,
SALE_PRODUCTS: 1,
EXPENSES: 2,
EXPENSES_ITEMS: 3
}
Here's what the comparator would look like:
private static class PredefinedOrderComparator implements Comparator<Table> {
public PredefinedOrderComparator() {
// Initialize orderIndex here
}
private final Map<String, Integer> orderIndex;
#Override public int compare(Table one, Table two) {
return orderIndex.get(one.getTableName()) - orderIndex.get(two.getTableName());
}
}
To populate orderIndex from the property value, you need to:
Get the comma-separated list using getProperty() as you mentioned
Split that value on comma (I recommend using Guava's Splitter, but String.split or others will work too)
Initialize a new HashMap<String, Integer> and an int index = 0
Iterate through the split tokens, map the current token to index and increment index
Note the implicit assumption that none of the table names have a comma in it.
public class MyComparable implements Comparator<Table>{
#Override
public int compare(Table table1, Table table2) {
return (table1.getTableName().compareTo(table2.getTableName());
}
}
make sure that you have overridden the hashcode and equals in Table class to achieve this.
I wrote you a very simple example on how to work with a Comparator. If you create a class called Main, copy paste below contents in it, compile and run it, you can see what's going on.
A comparator just needs to implement an interface. For this it needs to implement one method (public int compare(T arg0, T arg1). There you specify how a collection will get sorted; in this case according to the alfabet.
I hope this helps you.
import java.util.*;
public class Main {
public static void main(String[] args) {
System.out.println("Start\n");
List<Item> items = new ArrayList<Item>();
for(String s : new String[]{"mzeaez", "xcxv", "hjkhk", "azasq", "iopiop"}) {
items.add(createItem(s));
}
System.out.println("Items before sort:");
System.out.println(Item.toString(items));
Collections.sort(items, new ItemComparator());
System.out.println("Items after sort:");
System.out.println(Item.toString(items));
System.out.println("End");
}
private static Item createItem(String s) {
Item item = new Item();
item.setS(s);
return item;
}
}
class Item {
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
#Override
public String toString() {
return "Item: " + s;
}
public static String toString(Collection<Item> items) {
String s = "";
for(Item item : items) {
s += item + "\n";
}
return s;
}
}
class ItemComparator implements Comparator<Item> {
#Override
public int compare(Item item1, Item item2) {
return item1.getS().compareTo(item2.getS());
}
}
Is there such a collection implemention can let us know what objects are newly added, modified or deleted comparing to a specific point?
I want to use such a collection to hold objects loaded from database, and bind it to user interface ,so user can add new object to it, delete items in it or modify some ones. when user click a save button , I need to persist changes to database, so I need to know the changed objects.
Here is my own solution, but I am not sure about whether it is very bad, please give me some advice.
interface :
import java.util.Collection;
/**
* #author ggfan#amarsoft
*
*/
public interface DataObjectsMonitor {
/**
* take a snapshot for comparing
*/
public void snapshot();
/**
*
* #return Objects that are modified comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getmodifiedObjects();
/**
*
* #return Objects that are deleted comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getDeletedObjects();
/**
*
* #return Objects that are added comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getAddedObjects();
}
Model Class must be extended from such a abstract class :
public abstract class DataObject {
public abstract int dataHashCode();
}
implemention class :
public class DataObjectListMonitor<T extends DataObject> extends ArrayList<T> implements DataObjectsMonitor {
private static final long serialVersionUID = 1L;
private Map<T, Integer> oldVersion = new HashMap<T, Integer>();
public void snapshot() {
oldVersion.clear();
for(T t : this){
oldVersion.put(t, new Integer(t.dataHashCode()));
}
}
public Collection<T> getmodifiedObjects() {
ArrayList<T> modified = new ArrayList<T>();
for(T t : oldVersion.keySet()){
if(this.contains(t) && t.dataHashCode() != oldVersion.get(t)){
modified.add(t);
}
}
return modified;
}
public Collection<T> getDeletedObjects() {
ArrayList<T> deleted = new ArrayList<T>();
for(T t : oldVersion.keySet()){
if(!this.contains(t)){
deleted.add(t);
}
}
return deleted;
}
public Collection<T> getAddedObjects() {
ArrayList<T> added = new ArrayList<T>();
for(T t : this){
if(!oldVersion.keySet().contains(t)){
added.add(t);
}
}
return added;
}
}
test :
public class Model extends DataObject {
private String id;
private String name;
public String toString() {
return "Model [id=" + id + ", name=" + name + "]";
}
public Model(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int dataHashCode() {
int dataHashCode = 0;
if(id != null){
dataHashCode += id.hashCode();
}
if(name != null){
dataHashCode += name.hashCode();
}
return dataHashCode;
}
public static void main(String[] args){
DataObjectListMonitor<Model> data = new DataObjectListMonitor<Model>();
Model m1 = new Model("m1", "model 1");
Model m2 = new Model("m2", "model 2");
Model m3 = new Model("m3", "model 3");
Model m4 = new Model("m4", "model 4");
Model m5 = new Model("m5", "model 5");
data.add(m1);
data.add(m2);
data.add(m3);
data.add(m4);
data.add(m5);
data.snapshot();
Model m6 = new Model("m6", "model 6");
data.add(m6);
m3.setName("model 3 changed");
m3.setName("model 3");
data.remove(m5);
m1.setName("model 1 chaned");
for(Model m : data.getAddedObjects()){
System.out.println("added : " + m);
}
for(Model m : data.getDeletedObjects()){
System.out.println("deleted : " + m);
}
for(Model m : data.getmodifiedObjects()){
System.out.println("modified : " + m);
}
}
}
output :
added : Model [id=m6, name=model 6]
deleted : Model [id=m5, name=model 5]
modified : Model [id=m1, name=model 1 chaned]
edit: using hashCode is totally wrong, but maybe we can use MD5 or CRC32.
You can implement a custom collection that can easily track and log add, replace and delete operations but tracking and logging modifications on collection items is quite impossible..
The general idea: implement the List interface, add a delegate (a real list) and delegate all method calls to the internal list (or set, map, queue, ...):
public class LoggingList implements List {
private List delegate = new ArrayList();
private List<ChangeEvent> history = new ArrayList<ChangeEvent>();
// ...
#Override
public boolean add(Object item) {
boolean success = delegate.add(item);
if (success) {
history.add(new ChangeEvent(Type.ADD, item));
}
return success;
}
}
But - the list can't track if someone uses a reference to item to modify its state after it has been added.
One way is to create your own collection implementing the Map interface and add a dirty flag to it. So that when you add or remove elements from the collection make the isDirty flag true and do the operation according to it.
if the existing object is changed then you need to have some different logic.
Tracking modifications means there has to be some ID that you can attach to each item, which also means your better choice is a Map instead of a Collection.
One example to do this is to subclass HashMap and intercept the put (for add/modify operations) and remove (for delete operations).
This is the general idea -- bottom line is you are most likely on your own with the implementation, unless somebody else can recommend a third-party API for this:
Map<K, V> items = new HashMap<K, V>() {
public V put(K key, V value) {
if (containsKey(key))
modified.put(key, value);
else
created.put(key, value);
return super.put(key, value);
}
public V remove(Object key) {
if (containsKey(key))
deleted.put((K) key, get(key));
return super.remove(key);
}
};
In my opinion you should not track changes in the collection but in the changed object itself. So have a changed flag as an instance field inside your object and set it to true when something changed and set it to false after you've written it to the database.
The problem in tracking inside the collection is that it is hard to keep track of modifications to objects already in the collection. Then you need something like a wrapper around each and every object that "informs" your collection when changes to that object happen. Too much in my opinion when this can be solved by tracking inside the objects itself.
This objects' states can afterwards be used to filter the collection or whatever...