highsource / jaxb2-basics

Useful plugins and tools for JAXB2.
BSD 2-Clause "Simplified" License
109 stars 54 forks source link

simpleequals enhancement on Object comparison #73

Closed rviado closed 6 years ago

rviado commented 8 years ago

I stumbled upon a use case wherein I needed to use the "isEqual" instead of the "equals" method on ZonedDateTime objects.

I ended up enhancing the code to make simpleequals configurable by just adding a notSoSimpleEquals.properties file in META-INF/jaxb2/plugin folder.

This file is used to configure a strategy based on the object type.

Example:

java.time.ZonedDateTime=method:isEqual my.own.HelloWorld=adapter:my.special.EqualsAdapter

The first line above will instruct simpleequals plugin to use the method "isEqual" on object of type ZoneDateTime

The second line will instruct simpleequals plugin to use an adapter on HelloWorld objects. The adapter must implement a public "isEqual" static method that accepts two objects that will be compared for equality. The method must return a boolean value depending on the result of the comparison.

The enhancement is so simple and yet gives so much flexibility that it may be warranted to be included in the main distribution.

Code below (Change is isolated only to EqualsCodeGenerationImplementor.java in onObject method)

@Override public void onObject(EqualsArguments arguments, JBlock block, boolean isAlwaysSet) { String type = arguments.leftValue().type().fullName(); JInvocation condition = arguments.leftValue().invoke("equals").arg(arguments.rightValue());

    try {
        InputStream strategyConfigFile = this.getClass().getClassLoader().getResourceAsStream("META-INF/jaxb2/plugin/notSoSimpleEquals.properties");
        if (strategyConfigFile != null) {
            Properties conf = new Properties();
            conf.load(strategyConfigFile);
            if (conf.containsKey(type)) {
                String[] strategy = conf.getProperty(type).split(":");
                if (strategy.length > 1) {
                    if (strategy[0].equals("method")) {
                        String method = strategy[1];
                        condition = arguments.leftValue().invoke(method).arg(arguments.rightValue());
                        System.out.println("Using " + method  + " method on " + type + " for equals comparison");
                    } else if (strategy[0].equals("adapter")) {
                        String adapter = strategy[1];
                        condition = getCodeModel().ref(Class.forName(adapter)).staticInvoke("isEqual").arg(arguments.leftValue()).arg(arguments.rightValue());
                    }
                }
            }

        }
    } catch (Exception e) {
        condition = arguments.leftValue().invoke("equals").arg(arguments.rightValue());
    }

    returnFalseIfNotEqualsCondition(
            arguments,
            block,
            isAlwaysSet,
            condition.not());
}
highsource commented 8 years ago

Interesting idea, but JAXB2 Basics provide "normal" equals and hashCode plugins which cover your use case.

These plugins generate equals and hashCode methods which accept locators (irrelevant here) as well as a strategy. The strategy implements comparison (or hash code generation). There are default strategies but you can also provide your own.

This allows you to implement and supply the desired comparison or hash code generation strategy in the runtime. For instance if you want to use isEqual for ZonedDateTime, you can capture this in your custom equality strategy. You can also configure equals or hashCode plugins to use your strategy class by default.

This is the kind of code you get generated:

    public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy2 strategy) {
        if ((object == null)||(this.getClass()!= object.getClass())) {
            return false;
        }
        if (this == object) {
            return true;
        }
        final IssueGH26Type that = ((IssueGH26Type) object);
        {
            String lhsA;
            lhsA = this.getA();
            String rhsA;
            rhsA = that.getA();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "a", lhsA), LocatorUtils.property(thatLocator, "a", rhsA), lhsA, rhsA, this.isSetA(), that.isSetA())) {
                return false;
            }
        }
        {
            List<Serializable> lhsBOrC;
            lhsBOrC = (this.isSetBOrC()?this.getBOrC():null);
            List<Serializable> rhsBOrC;
            rhsBOrC = (that.isSetBOrC()?that.getBOrC():null);
            if (!strategy.equals(LocatorUtils.property(thisLocator, "bOrC", lhsBOrC), LocatorUtils.property(thatLocator, "bOrC", rhsBOrC), lhsBOrC, rhsBOrC, this.isSetBOrC(), that.isSetBOrC())) {
                return false;
            }
        }
        {
            List<JAXBElement<String>> lhsDOrE;
            lhsDOrE = (this.isSetDOrE()?this.getDOrE():null);
            List<JAXBElement<String>> rhsDOrE;
            rhsDOrE = (that.isSetDOrE()?that.getDOrE():null);
            if (!strategy.equals(LocatorUtils.property(thisLocator, "dOrE", lhsDOrE), LocatorUtils.property(thatLocator, "dOrE", rhsDOrE), lhsDOrE, rhsDOrE, this.isSetDOrE(), that.isSetDOrE())) {
                return false;
            }
        }
        {
            String lhsZ;
            lhsZ = this.getZ();
            String rhsZ;
            rhsZ = that.getZ();
            if (!strategy.equals(LocatorUtils.property(thisLocator, "z", lhsZ), LocatorUtils.property(thatLocator, "z", rhsZ), lhsZ, rhsZ, this.isSetZ(), that.isSetZ())) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(Object object) {
        final EqualsStrategy2 strategy = new IssueJIIB42EqualsStrategy();
        return equals(null, null, object, strategy);
    }

You can see that this code does not contain actual comparison, it calles the provided strategy instance.