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 toString
equals
, 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.