View Javadoc
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 }