1 package org.pojomatic;
2
3 import java.util.Arrays;
4 import java.util.List;
5
6 import org.pojomatic.annotations.OverridesEquals;
7 import org.pojomatic.annotations.PojomaticPolicy;
8 import org.pojomatic.annotations.Property;
9 import org.pojomatic.annotations.SkipArrayCheck;
10 import org.pojomatic.annotations.SubclassCannotOverrideEquals;
11 import org.pojomatic.diff.Differences;
12 import org.pojomatic.formatter.DefaultEnhancedPojoFormatter;
13 import org.pojomatic.formatter.DefaultEnhancedPropertyFormatter;
14 import org.pojomatic.formatter.EnhancedPojoFormatter;
15 import org.pojomatic.formatter.EnhancedPropertyFormatter;
16
17 /**
18 * A provider of the three standard {@code Object} methods,
19 * {@link Object#equals(Object)}, {@link Object#hashCode()} and {@link Object#toString()}, as
20 * well as a useful method to aid in debugging, {@link #doDiff(Object, Object)}.
21 *
22 * <h3>Treatment of arrays</h3>
23 * Normally, when encountering an array,Pojomatic looks into the array, examining each of the array elements. Moreover,
24 * if any of these elements are themselves arrays, then it recursively looks into them. However, there is one exception
25 * to this rule. If a property of type {@code Object} is annotated with {@link SkipArrayCheck @SkipArrayCheck}, then
26 * Pojomatic will not check to see if it's value might be of array type. Typically, the reason for annotating a property
27 * with {@code @SkipArrayCheck} would be to gain a slight performance advantage by avoiding a call to
28 * {@link Object#getClass()}.{@link Class#isArray() isArray()}.
29 *
30 * @param <T> the class this {@code Pojomator} is generated for.
31 */
32 public interface Pojomator<T> {
33
34 /**
35 * Compute the hashCode for a given instance of {@code T}.
36 * This is done by computing the hashCode of each property which has a {@link PojomaticPolicy} of
37 * {@link PojomaticPolicy#HASHCODE_EQUALS HASHCODE_EQUALS} or {@link PojomaticPolicy#ALL ALL}
38 * (using 0 when the property is null), and combining them in a fashion similar to that of
39 * {@link List#hashCode()}.
40 *
41 * @param instance the instance to compute the hashCode for - must not be {@code null}
42 * @return the hashCode of {@code instance}
43 * @throws NullPointerException if {@code instance} is {@code null}
44 * @see Object#hashCode()
45 */
46 int doHashCode(T instance);
47
48 /**
49 * Compute the {@code toString} representation for a given instance of {@code T}.
50 * <p>
51 * The format used depends on the
52 * {@link EnhancedPojoFormatter} used for the POJO, and the {@link EnhancedPropertyFormatter} of each property.
53 * <p>
54 * For example, suppose a class {@code Person} has properties {@code String name} and
55 * {@code int age} which are included in its {@code String} representation.
56 * No {@code EnhancedPojoFormatter} or {@code EnhancedPropertyFormatter} are specified, so the defaults are used.
57 * In particular, instances of {@code DefaultEnhancedPropertyFormatter} will be created for
58 * {@code name} and {@code age} (referred to here as {@code nameFormatter} and
59 * {@code ageFormatter}, respectively). Let {@code nameProperty} and
60 * {@code ageProperty} refer to the instances of {@link PropertyElement} referring to the
61 * properties {@code name} and {@code age} respectively.
62 * <p>
63 * For a non-null {@code Person} instance, the {@code String} representation will be created by
64 * creating an instance of {@code DefaultEnhancedPojoFormatter} for the {@code Person} class (referred to
65 * here as {@code personFormatter}), a {@link StringBuilder} (referred to here as builder), and then invoking the
66 * following methods in order:
67 * <ul>
68 * <li>{@link DefaultEnhancedPojoFormatter#appendToStringPrefix(StringBuilder, Class) personFormatter.appendToStringPrefix(builder, Person.class)}</li>
69 * <li>{@link DefaultEnhancedPojoFormatter#appendPropertyPrefix(StringBuilder, PropertyElement) personFormatter.appendPropertyPrefix(builder, nameProperty)}</li>
70 * <li>{@link DefaultEnhancedPropertyFormatter#appendFormatted(StringBuilder, Object) nameFormatter.appendFormatted(builder, name)}</li>
71 * <li>{@link DefaultEnhancedPojoFormatter#appendPropertySuffix(StringBuilder, PropertyElement) personFormatter.appendPropertySuffix(builder, nameProperty)}</li>
72 * <li>{@link DefaultEnhancedPojoFormatter#appendPropertyPrefix(StringBuilder, PropertyElement) personFormatter.appendPropertyPrefix(builder, ageProperty)}</li>
73 * <li>{@link DefaultEnhancedPropertyFormatter#appendFormatted(StringBuilder, int) ageFormatter.appendFormatted(age)}</li>
74 * <li>{@link DefaultEnhancedPojoFormatter#appendPropertySuffix(StringBuilder, PropertyElement) personFormatter.appendPropertySuffix(builder, ageProperty)}</li>
75 * <li>{@link DefaultEnhancedPojoFormatter#appendToStringSuffix(StringBuilder, Class) personFormatter.appendToStringSuffix(builder, Person.class)}</li>
76 * <li>builder.toString()</li>
77 * </ul>
78 *
79 * @param instance the instance to compute the {@code toString} representation for - must not be {@code null}
80 * @return the {@code toString} representation of {@code instance}
81 * @throws NullPointerException if {@code instance} is {@code null}
82 * @see Object#toString()
83 * @see Property#name()
84 */
85 String doToString(T instance);
86
87 /**
88 * Compute whether {@code instance} and {@code other} are equal to each other in the sense of
89 * {@code Object}'s {@link Object#equals(Object) equals} method. For two instances to be
90 * considered equal, the first requirement is that their classes must be compatible for equality,
91 * as described in the documentation for {@link #isCompatibleForEquality(Class)}.
92 * <p>
93 * More precisely, if {@code other} is null, this method returns {@code false}. Otherwise, if
94 * {@link #isCompatibleForEquality(Class) isCompatibleForEquals(other.getClass())} would return
95 * false, then this method will return false. Otherwise, this method will return true provided
96 * that each property of {@code instance} which has a {@code PojomaticPolicy} other than
97 * {@code TO_STRING} or {@code NONE} is equal to the corresponding property of {@code other} in
98 * the following sense:
99 * <ul>
100 * <li>Both are {@code null}, or</li>
101 * <li>Both are reference-equals (==) to each other, or</li>
102 * <li>Both are primitive of the same type, and equal to each other, or</li>
103 * <li>The property {@code p} in {@code instance} is an object not of array type, and {@code
104 * instanceP.equals(otherP)} returns true.</li>
105 * <li>The declared type of the property {@code p} is {@link Object}, the property is annotated with
106 * {@link SkipArrayCheck @SkipArrayCheck}, and {@code instanceP.equals(otherP)} returns true.</li>
107 * <li>The declared type of the property is either an array type, or is of type {@link Object}, and the property is
108 * not annotated with {@link SkipArrayCheck @SkipArrayCheck}, and:
109 * <ul>
110 * <li>{@code instanceP.getClass().equals(otherP.getClass())},</li>
111 * <li>{@code instanceP.length == otherP.length}, and</li>
112 * <li>Recursively, each element of {@code instanceP} is equal to the corresponding element of {@code otherP}.</li>
113 * </ul></li>
114 * </ul>
115 * <p>
116 * Note that Pojomator's treatment of multi-dimensional arrays can differ from
117 * {@link Arrays#deepEquals(Object[], Object[])}, in that the latter only looks at array length and element-wise
118 * equality, but not array type. For example, {@code Arrays.deepEquals()} would consider {@code new Integer[0]} and
119 * {@code new boolean[0][][])} to be equal, where as Pojomatic does not. Version 1.0 of Pojomatic simply delegated to
120 * {@code Arrays.deepEquals()}, and hence would have considered those two arrays to be equal.
121 * @param instance the instance to test against - must not be {@code null}
122 * @param other the instance to test
123 * @return {@code true} if {@code instance} should be considered equal to {@code other}, and
124 * {@code false} otherwise.
125 * @throws NullPointerException if {@code instance} is {@code null}
126 * @see Object#equals(Object)
127 */
128 boolean doEquals(T instance, Object other);
129
130 /**
131 * Compute whether {@code otherClass} is compatible for equality with {@code T}.
132 * Classes {@code A} and {@code B} are compatible for equality if
133 * they share a common superclass {@code C}, and for every class {@code D} which
134 * is a proper subclass of {@code C} and a superclass of {@code A} or {@code B} (including
135 * the classes {@code A} and {@code B} themselves), the following hold:
136 * <ul>
137 * <li>{@code D} has not added additional properties for inclusion in the {@code equals} calculation, and</li>
138 * <li>{@code D} has not been annotated with {@link OverridesEquals}</li>
139 * </ul>
140 * If {@code T} is an interface or is annotated with {@link SubclassCannotOverrideEquals},
141 * then all subclasses of {@code T} are automatically assumed by {@code T}'s {@code Pojomator}
142 * to be compatible for equals with each other and with {@code T}. Note that in this case.
143 * to add an {@link OverridesEquals} annotation or additional
144 * properties for inclusion in {@code equals} to a subclass of {@code T} will
145 * result in a violation of the contract for {@link Object#equals(Object)}.
146 * @param otherClass the class to check for compatibility for equality with {@code T}
147 * @return {@code true} if {@code otherClass} is compatible for equality with {@code T}, and
148 * {@code false} otherwise.
149 */
150 boolean isCompatibleForEquality(Class<?> otherClass);
151
152 /**
153 * Compute the differences between {@code instance} and {@code other} among the properties
154 * examined by {@link #doEquals(Object, Object)}. Assuming that {@code instance} and {@code other}
155 * are both non-null and have types which are compatible for equals, it is guaranteed that invoking
156 * {@link Differences#areEqual()} on the returned object will return true iff
157 * {@code instance.equals(other)}.
158 *
159 * @param instance the instance to diff against
160 * @param other the instance to diff
161 * @return the differences between {@code instance} and {@code other}
162 * among the properties examined by {@link #doEquals(Object, Object)}.
163 * @throws NullPointerException if {@code instance} or {@code other} is null
164 * (this behavior may change in future releases).
165 * @throws IllegalArgumentException the type of {@code instance} or of {@code other} is not a
166 * class which is compatible for equality with {@code T}
167 * (this behavior may change in future releases).
168 * @see #doEquals(Object, Object)
169 */
170 Differences doDiff(T instance, T other);
171
172 /**
173 * Return a simple String representation of this Pojomator. This is meant to aid in debugging
174 * which properties are being used for which purposes. The contents and format of this
175 * representation are subject to change.
176 *
177 * @return a simple String representation of this Pojomator.
178 */
179 @Override
180 public String toString();
181 }