Search code examples
javaobjectcomparejavers

Comparing objects that implement the same interface with javers


I'm using javers (https://javers.org/) and trying to compare two objects that implement the same interface. As an example I set up a test with apples and oranges. Apples to Apples works fine, but not Apples to Oranges. It seems to run the compare but doesn't pick up the new values.

This works fine if I have a Fruit super class and apples and oranges that extend it but no luck with interfaces. Anybody out there familiar with this API that could point me in the right direction would be great.

Here's the model code:

@TypeName("FruitIface")
@Entity
public interface FruitIface {

public final static String SIZE_SMALL = "Small";
public final static String SIZE_LARGE = "Large";
public final static String CONDITION_RIPE = "Ripe";
public final static String CONDITION_NOT_RIPE = "Not Ripe";

public String getSize();
public String getCondition();
public Double getWeight();
public Date getSellBy();

}



@TypeName("FruitIface")
@Entity
public class Apple implements FruitIface {


private String size;
private String condition;
private Double weight;
private Date sellBy;
@Id
private String compareId;

public Apple (String id,String size, String condition, Double weight, Date sellBy){
    this.compareId = id;
    this.size = size;
    this.condition = condition;
    this.weight = weight;
    this.sellBy = sellBy;
}
    ....

}


@TypeName("FruitIface")
@Entity
public class Orange implements FruitIface {

@PropertyName("size")
private String size;
private String condition;
private Double weight;
private Date sellBy;
@Id
private String compareId;


public Orange (String id, String size, String condition, Double weight, Date sellBy){
    this.compareId = id;
    this.size = size;
    this.condition = condition;
    this.weight = weight;
    this.sellBy = sellBy;
}
....
}

Test cases:

@Test public void testCompareAppleToAppleIface() {
    Apple appleOne = new Apple("01",FruitIface.SIZE_LARGE,FruitIface.CONDITION_RIPE, 0.3, new Date());
    Apple appleTwo = new Apple("01",FruitIface.SIZE_SMALL,FruitIface.CONDITION_RIPE, 0.2, new Date());

    LOGGER.info("\n\nComparing apples to apples...");
    Diff diff = javers.compare(appleOne, appleTwo);
    LOGGER.info("diff: " + diff);

}


@Test public void testCompareAppleAndOrangeIface() {
    Apple apple = new Apple("01",FruitIface.SIZE_LARGE,FruitIface.CONDITION_RIPE, 0.3, new Date()); //(String size, String condition, Double weight, Date sellBy)
    Orange orange = new Orange("01",FruitIface.SIZE_SMALL,FruitIface.CONDITION_RIPE, 0.2, new Date());


    LOGGER.info("\n\nComparing apples and oranges IFace...");
    Diff diff = javers.compare(apple, orange);
    LOGGER.info("diff: " + diff);
}

Results:

Comparing apples to apples...
[main] INFO xxx.compare.test.JaversCompareTestIface - diff: Diff:
1. ValueChange{globalId:'FruitIface/01', property:'size', oldVal:'Large', newVal:'Small'}
2. ValueChange{globalId:'FruitIface/01', property:'weight', oldVal:'0.3', newVal:'0.2'}


Comparing apples and oranges IFace...
[main] INFO xxx.compare.test.JaversCompareTestIface - diff: Diff:
1. ValueChange{globalId:'FruitIface/01', property:'size', oldVal:'Large', newVal:''}
2. ValueChange{globalId:'FruitIface/01', property:'condition', oldVal:'Ripe', newVal:''}
3. ValueChange{globalId:'FruitIface/01', property:'weight', oldVal:'0.3', newVal:''}
4. ValueChange{globalId:'FruitIface/01', property:'sellBy', oldVal:'Thu Aug 10 12:14:45 PDT 2017', newVal:''}
5. ValueChange{globalId:'FruitIface/01', property:'compareId', oldVal:'01', newVal:''}

Solution

  • Using the same @TypeName for multiple classes is a misuse for the same reasons like using the same class name for multiple Java classes.

    JaVers should throw an exception in this case, instead you are getting somehow random results because JaVers type system gets confused.

    In the current version of JaVers, you can't compare Apples with Oranges even if both of them implement Fruit interface. JaVers matches objects with the same GlobalId and compares them. When you have Apple with id 1 and Orange with id 1, their GlobalIds are Apple#1 and Orange#1 so they won't be matches as two versions of the same object.