Search code examples
javalistcollectionscomparisondelta

How to Compare a List of Objects and Their Properties, then Update the Original List in Java


I have an object that has a list as its property, e.g.

public class A {
  private String aName;
  private List<B> bList;
}

public class B {
  private String bName;
}

Lets assume that we have two lists of A's:

List<A> existingList = new ArrayList<A>();
// populate it with a list of A's which in turn, each has its own list of B's

List<A> newList = new ArrayList<A>();
// populate it with possibly some new A's and/or, an existing A which its property B has new items

With that in mind, I would like to know the fastest way to compare these two list of A's, and add the delta of these two lists to the existingList.

Note that we are also comparing a list of B's for A's in both lists, so if there is a matching A's, but there is a delta of their B's, we should be able to add it to existingList's A's bList.

Equally, if we detect an item has been deleted from newList, we should remove that from existingList.

Note, this is not simply about comparing two objects, but rather finding the delta deep in the object graph and updating or adding new and/or existing parts. Here is the sample example:

package collection.delta.model;

import java.util.List;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;

package collection.delta.model;

import java.util.List;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

public class A {
    private String aName;

    private List<B> bList;

    public String getaName() {
        return aName;
    }

    public void setaName(String aName) {
        this.aName = aName;
    }

    public List<B> getbList() {
        return bList;
    }

    public void setbList(List<B> bList) {
        this.bList = bList;
    }

    public A(String name, List<B> bList) {
        this.aName = name;
        this.bList = bList;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }

        if (o == this) {
            return true;
        }

        if (!(o instanceof A)) {
            return false;
        }

        A b = (A) o;
        return StringUtils.isNotBlank(aName) && StringUtils.equalsAnyIgnoreCase(b.getaName(), aName)
                && CollectionUtils.disjunction(this.getbList(), b.getbList()).isEmpty();
    }

    @Override
    public int hashCode() {
        if (StringUtils.isBlank(aName)) {
            return 31;
        }

        return Objects.hashCode(aName);
    }   

package collection.delta.model;

import java.util.Objects;

import org.apache.commons.lang3.StringUtils;

public class B {
    private String bName;

    public String getbName() {
        return bName;
    }

    public void setbName(String bName) {
        this.bName = bName;
    }

    public B(String name) {
        this.bName = name;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }

        if (o == this) {
            return true;
        }

        if (!(o instanceof B)) {
            return false;
        }

        B b = (B) o;
        return StringUtils.isNotBlank(bName) && StringUtils.equalsAnyIgnoreCase(b.getbName(), bName);
    }

    @Override
    public int hashCode() {
        if (StringUtils.isBlank(bName)) {
            return 31;
        }

        return Objects.hashCode(bName);
    }
}

package collection.delta;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import collection.delta.model.A;
import collection.delta.model.B;

public class App {
    public static void main( String[] args ) {

        List<A> originalA = new ArrayList<A>();
        List<A> newA = new ArrayList<A>();

        List<B> bListOriginalA1 = new ArrayList<B>();
        bListOriginalA1.add(new B("originalA1_B1"));
        bListOriginalA1.add(new B("originalA1_B2"));
        bListOriginalA1.add(new B("originalA1_B3"));
        bListOriginalA1.add(new B("originalA1_B4"));

        A originalA1 = new A("originalA1", bListOriginalA1);

        List<B> bListOriginalA2 = new ArrayList<B>();
        bListOriginalA2.add(new B("originalA2_B1"));
        bListOriginalA2.add(new B("originalA2_B2"));
        bListOriginalA2.add(new B("originalA2_B3"));
        bListOriginalA2.add(new B("originalA2_B4"));

        A originalA2 = new A("originalA2", bListOriginalA2);

        List<B> bListOriginalA3 = new ArrayList<B>();
        bListOriginalA3.add(new B("originalA3_B1"));
        bListOriginalA3.add(new B("originalA3_B2"));
        bListOriginalA3.add(new B("originalA3_B3"));
        bListOriginalA3.add(new B("originalA3_B4"));

        A originalA3 = new A("originalA3", bListOriginalA3);

        originalA.add(originalA1);
        originalA.add(originalA2);
        originalA.add(originalA3);


        List<B> bListNewA1 = new ArrayList<B>();
        bListNewA1.add(new B("originalA1_B1"));
        bListNewA1.add(new B("originalA1_B2"));
        bListNewA1.add(new B("originalA1_B3"));
        bListNewA1.add(new B("originalA1_B4"));

        A newA1 = new A("originalA1", bListNewA1);

        List<B> bListNewA2 = new ArrayList<B>();
        bListNewA2.add(new B("originalA2_B1"));
        bListNewA2.add(new B("originalA2_B2"));
        bListNewA2.add(new B("originalA2_B3"));
        bListNewA2.add(new B("originalA2_B4"));

        A newA2 = new A("originalA2", bListNewA2);

        List<B> bListNewA3 = new ArrayList<B>();
        bListNewA3.add(new B("originalA3_B1"));
        bListNewA3.add(new B("originalA3_B2"));
        bListNewA3.add(new B("originalA3_B5"));
        bListNewA3.add(new B("originalA3_B4"));

        A newA3 = new A("originalA3", bListNewA3);

        List<B> bListNewA4 = new ArrayList<B>();
        bListNewA4.add(new B("A4_B1"));
        bListNewA4.add(new B("A4_B2"));
        bListNewA4.add(new B("A4_B3"));
        bListNewA4.add(new B("A4_B4"));

        A newA4 = new A("originalA4", bListNewA4);

        newA.add(newA1);
        newA.add(newA2);
        newA.add(newA3);
        newA.add(newA4);

        List<A> result = CollectionUtils.disjunction(originalA, newA).stream().collect(Collectors.toList());
        for (A a : newA) {
            for (A aResult : result) {
                if (StringUtils.equalsAnyIgnoreCase(a.getaName(), aResult.getaName())) {
                    originalA.add(aResult);
                }
            }
        }

        System.out.println("");
    }

}

Solution

  • This works:

    package collection.delta;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.function.Predicate;
    import java.util.stream.Collectors;
    
    import collection.delta.model.A;
    import collection.delta.model.B;
    
    public class App {
        public static void main( String[] args ) {
    
            List<A> originalA = new ArrayList<A>();
            List<A> newA = new ArrayList<A>();
    
            List<B> bListOriginalA1 = new ArrayList<B>();
            bListOriginalA1.add(new B("originalA1_B1"));
            bListOriginalA1.add(new B("originalA1_B2"));
            bListOriginalA1.add(new B("originalA1_B3"));
            bListOriginalA1.add(new B("originalA1_B4"));
    
            A originalA1 = new A("originalA1", bListOriginalA1);
    
            List<B> bListOriginalA2 = new ArrayList<B>();
            bListOriginalA2.add(new B("originalA2_B1"));
            bListOriginalA2.add(new B("originalA2_B2"));
            bListOriginalA2.add(new B("originalA2_B3"));
            bListOriginalA2.add(new B("originalA2_B4"));
    
            A originalA2 = new A("originalA2", bListOriginalA2);
    
            List<B> bListOriginalA3 = new ArrayList<B>();
            bListOriginalA3.add(new B("originalA3_B1"));
            bListOriginalA3.add(new B("originalA3_B2"));
            bListOriginalA3.add(new B("originalA3_B3"));
            bListOriginalA3.add(new B("originalA3_B4"));
    
            A originalA3 = new A("originalA3", bListOriginalA3);
    
            originalA.add(originalA1);
            originalA.add(originalA2);
            originalA.add(originalA3);
    
    
            List<B> bListNewA1 = new ArrayList<B>();
            bListNewA1.add(new B("originalA1_B1"));
            bListNewA1.add(new B("originalA1_B2"));
            bListNewA1.add(new B("originalA1_B3"));
            bListNewA1.add(new B("originalA1_B4"));
    
            A newA1 = new A("originalA1", bListNewA1);
    
            List<B> bListNewA2 = new ArrayList<B>();
            bListNewA2.add(new B("originalA2_B1"));
            bListNewA2.add(new B("originalA2_B3"));
            bListNewA2.add(new B("originalA2_B4"));
            bListNewA2.add(new B("originalA2_B2"));
    
            A newA2 = new A("originalA2", bListNewA2);
    
            List<B> bListNewA3 = new ArrayList<B>();
            bListNewA3.add(new B("originalA3_B1"));
            bListNewA3.add(new B("originalA3_B2"));
            bListNewA3.add(new B("originalA3_B5"));
            bListNewA3.add(new B("originalA3_B4"));
    
            A newA3 = new A("originalA3", bListNewA3);
    
            List<B> bListNewA4 = new ArrayList<B>();
            bListNewA4.add(new B("A4_B1"));
            bListNewA4.add(new B("A4_B2"));
            bListNewA4.add(new B("A4_B3"));
            bListNewA4.add(new B("A4_B4"));
    
            A newA4 = new A("originalA4", bListNewA4);
    
            newA.add(newA1);
            newA.add(newA2);
            newA.add(newA3);
            newA.add(newA4);
    
            List<A> result = newA.stream()
                    .filter(not(new HashSet<A>(originalA)::contains))
                    .collect(Collectors.toList());
    
            A tempA = null;
            B tempB = null;
            List<B> bList = null;
            for (A a : result) {
                if (!containsName(originalA, a.getaName())) {
                    originalA.add(a);
                } else {
                    tempA = getAIfPresent(originalA, a.getaName());
    
                    if (tempA != null) {
    
                        bList = a.getbList().stream()
                                .filter(not(new HashSet<B>(tempA.getbList())::contains))
                                .collect(Collectors.toList());
    
                        if (bList != null) {
                            tempA.getbList().addAll(bList);
                        }
                    }
                }
            }
    
            System.out.println("");
        }
    
        public static <T> Predicate<T> not(Predicate<T> predicate) {
            return predicate.negate();
        }
    
        public static boolean containsName(final List<A> list, final String name){
            return list.stream().map(A::getaName).filter(name::equals).findFirst().isPresent();
        }
    
        public static A getAIfPresent(final List<A> list, final String name) {
            return list.stream().filter(x -> x.getaName().equalsIgnoreCase(name)).findFirst().orElse(null);
        }
    
    }