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 }