Object.equals(Object), Object.hashCode() and
Object.toString() by
annotating properties on a Plain Old Java Object, or POJO.
There are two steps to "pojomate" a POJO class: annotating, and implementing
the three methods.
Pojomatic behavior is controlled by annotations; these can be on a property-by-property basis, a
class-wide basis, or a mix of the two. Class wide behavior can be controlled by the
AutoProperty and
PojoFormat, while property-specific behavior can be
controlled by the Property and
PropertyFormat annotations. A recommended
practice is to provide an AutoProperty annotation at the class level, and then override
the behavior it specifies on a per-property basis as needed. This minimizes both the number of
annotations needed, as well as the number of additional steps needed when adding new properties to
a class.
The annotations OverridesEquals and
SubclassCannotOverrideEquals can be used to control the behavior
of equals for inheritance hierarchies. If instances of a child class cannot possibly be
equal to instances of the parent class which are not themselves instance of the child class,
annotating the child class with OverridesEquals can inform Pojomatic of this (although
if any additional properties have been added for inclusion in the equals method by the child
class, Pojomatic will infer this automatically). Conversely, one can declare via the
SubclassCannotOverrideEquals annotation that no additional properties will be added by
child classes.
By default, when Pojomatic sees a property of type object, it will inspect the value to see if it is an array,
and if so, treat it as such. If the SkipArrayCheck annotation is present on the
property declaration, then this skip is checked, providing a small performance gain.
equals, hashCode and toStringequals, hashCode and toString, simply annotate the class for pojomation (see below),
and delegate to the static methods in
Pojomatic:
@Override public int hashCode() {
return Pojomatic.hashCode(this);
}
@Override public String toString() {
return Pojomatic.toString(this);
}
@Override public boolean equals(Object o) {
return Pojomatic.equals(this, o);
}
Note that the above methods on Pojomatic each in turn call Pojomatic.pojomator
For classes that might have equals, hashCode or toString called in performance-critical sections, one option is to
assign the result of Pojomatic.pojomator to a static variable, and use that:
private final static Pojomator<Manual> POJOMATOR = Pojomatic.pojomator(Manual.class);
@Override public boolean equals(Object other) {
return POJOMATOR.doEquals(this, other);
}
@Override public int hashCode() {
return POJOMATOR.doHashCode(this);
}
@Override public String toString() {
return POJOMATOR.doToString(this);
}
equals, hashCode and toString for interfacesequals, hashCode and toString for an interface, first annotate the interface for
pojomation (see below) and define a static constant POJOMATOR in the interface:
import org.pojomatic.annotations.AutoProperty;
import org.pojomatic.Pojomator;
import org.pojomatic.Pojomatic;
@AutoProperty
public interface Interface {
static Pojomator<Interface> POJOMATOR = Pojomatic.pojomator(Interface.class);
...
}
POJOMATOR in the implementing classes:
public class Implementation implements Interface {
@Override public int hashCode() {
return POJOMATOR.doHashCode(this);
}
@Override public boolean equals(Object other) {
return POJOMATOR.doEquals(this, other);
}
@Override public String toString() {
return POJOMATOR.doToString(this);
}
...
}
AutoProperty annotation:
@AutoProperty //all fields are included by default
public class Common {
...
}
autoDetect=AutoDetectPolicy.METHOD to the
AutoProperty annotation.
By default, all properties are used in the implementations of the equals, hashCode
and toString methods; this can be changed via the
policy parameter to
AutoProperty.
Additionally, one can override this choice on a per-property basis by use of the
Property
annotation. For example, if you have a class with a mutable field which you do not wish to include
in the hashCode calculation, you can accomplish this via:
@AutoProperty
public class Employee {
private final String firstName;
private final String lastName;
@Property(policy=PojomaticPolicy.EQUALS_TO_STRING)
private String securityLevel;
...
}
toString implementation provided by Pojomatic defaults to using
DefaultEnhancedPojoFormatter; you can specify your
own formatting by providing an alternate implementation of
EnhancedPojoFormatter and using the
PojoFormat annotation. In addition to controlling the
overall format of toString, the formatting of individual properties can be controlled by
a PropertyFormat annotation referencing an
implementation of EnhancedPropertyFormatter. An easy way to
implement EnhancedPropertyFormatter is to extend
DefaultPropertyFormatter. For example, to format a byte array
as an IP address, one could have:
public class IpAddressFormatter extends DefaultEnhancedPropertyFormatter {
@Override
public void appendFormatted(StringBuilder builder, byte[] array) {
if (array == null) {
super.appendFormatted(builder, array);
}
else {
boolean first = true;
for (byte b: array) {
if (first) {
first = false;
}
else {
builder.append('.');
}
builder.append(((int) b) & 0xff);
}
}
}
}
@AutoProperty
public class Customer {
private final String firstName;
private final String lastName;
@PropertyFormat(IpAddressFormatter.class)
private final byte[] ipAddress;
public Customer(String firstName, String lastName, byte[] ipAddress) {
this.firstName = firstName;
this.lastName = lastName;
this.ipAddress = ipAddress;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public byte[] getIpAddress() { return ipAddress; }
@Override public int hashCode() {
return Pojomatic.hashCode(this);
}
@Override public String toString() {
return Pojomatic.toString(this);
}
@Override public boolean equals(Object o) {
return Pojomatic.equals(this, o);
}
}
Security Manager is present, Pojomatic needs the following permissions
grant codebase "file:/path/to/pojomatic.jar" {
Note that in fact only one of the two RuntimePermissions above is needed; if running under Java 7 or 8,
"accessDeclaredMembers" is needed, while if running under Java 9 or later, "defineClass" is needed.
permission FilePermission "/path/to/jar/with/pojos.jar", "read"
permission FilePermission "/path/to/dir/with/pojos/classes/-", "read"
permission RuntimePermission "accessDeclaredMembers"
permission RuntimePermission "defineClass"
permission ReflectPermission "suppressAccessChecks"
};
| Module | Description |
|---|---|
| org.pojomatic |
Copyright © 2008–2018. All rights reserved.