1 package uk.org.lidalia.lang;
2
3 import java.lang.reflect.Field;
4 import java.security.PrivilegedAction;
5 import java.util.Set;
6 import java.util.concurrent.ExecutionException;
7
8 import com.google.common.base.Function;
9 import com.google.common.base.Joiner;
10 import com.google.common.base.Optional;
11 import com.google.common.base.Predicate;
12 import com.google.common.cache.CacheBuilder;
13 import com.google.common.cache.CacheLoader;
14 import com.google.common.cache.LoadingCache;
15 import com.google.common.collect.FluentIterable;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.Sets;
18
19 import static com.google.common.base.Optional.fromNullable;
20 import static java.security.AccessController.doPrivileged;
21 import static java.util.Arrays.asList;
22 import static uk.org.lidalia.lang.Classes.inSameClassHierarchy;
23 import static uk.org.lidalia.lang.Exceptions.throwUnchecked;
24
25
26
27
28
29
30 public abstract class RichObject {
31
32 private static final int PRIME = 37;
33 private static final int INITIAL_HASHCODE_VALUE = 17;
34
35 private static final LoadingCache<Class<?>, FluentIterable<FieldFacade>> IDENTITY_FIELDS =
36 CacheBuilder.newBuilder().weakKeys().softValues().build(new IdentityFieldLoader());
37 private static final Joiner FIELD_JOINER = Joiner.on(",");
38 private static final Function<Object, Integer> toHashCode = new Function<Object, Integer>() {
39 @Override
40 public Integer apply(final Object fieldValue) {
41 return fieldValue.hashCode();
42 }
43 };
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 @Override public final boolean equals(final Object other) {
63
64 if (other == this) {
65 return true;
66 }
67 if (other == null) {
68 return false;
69 }
70
71
72 if (!(other instanceof RichObject) || !inSameClassHierarchy(getClass(), other.getClass())) {
73 return false;
74 }
75
76 final RichObject that = (RichObject) other;
77
78
79
80 final FluentIterable<FieldFacade> fieldsOfThis = fields();
81 return fieldsOfThis.toSet().equals(that.fields().toSet())
82 && fieldsOfThis.allMatch(hasEqualValueIn(that));
83 }
84
85 private FluentIterable<FieldFacade> fields() {
86 try {
87 return IDENTITY_FIELDS.get(getClass());
88 } catch (ExecutionException e) {
89 return throwUnchecked(e.getCause(), null);
90 }
91 }
92
93 private Predicate<FieldFacade> hasEqualValueIn(final RichObject other) {
94 return new Predicate<FieldFacade>() {
95 @Override
96 public boolean apply(final FieldFacade field) {
97 return valueOf(field).equals(other.valueOf(field));
98 }
99 };
100 }
101
102
103
104
105
106
107
108 @Override public int hashCode() {
109 int result = INITIAL_HASHCODE_VALUE;
110 for (final FieldFacade field : fields()) {
111 final int toAdd = valueOf(field).transform(toHashCode).or(0);
112 result = PRIME * result + toAdd;
113 }
114 return result;
115 }
116
117
118
119
120
121
122
123 @Override public String toString() {
124 final Iterable<String> fieldsAsStrings = fields().transform(toStringValueOfField());
125 return getClass().getSimpleName()+"["+FIELD_JOINER.join(fieldsAsStrings)+"]";
126 }
127
128 private Function<FieldFacade, String> toStringValueOfField() {
129 return new Function<FieldFacade, String>() {
130 @Override
131 public String apply(final FieldFacade field) {
132 return field.getName() + "=" + valueOf(field).or("absent");
133 }
134 };
135 }
136
137 private Optional<Object> valueOf(final FieldFacade field) {
138 return field.valueOn(this);
139 }
140
141 private static class IdentityFieldLoader extends CacheLoader<Class<?>, FluentIterable<FieldFacade>> {
142
143 @Override
144 public FluentIterable<FieldFacade> load(final Class<?> key) {
145 return FluentIterable.from(doLoad(key));
146 }
147
148 private static final Predicate<FieldFacade> onlyIdentityFields = new Predicate<FieldFacade>() {
149 @Override
150 public boolean apply(final FieldFacade field) {
151 return field.isIdentityField();
152 }
153 };
154
155 private static final Function<Field, FieldFacade> toFieldFacade = new Function<Field, FieldFacade>() {
156 @Override
157 public FieldFacade apply(final Field field) {
158 return new FieldFacade(field);
159 }
160 };
161
162 private static final Function<Class<?>, Set<FieldFacade>> toFieldSet = new Function<Class<?>, Set<FieldFacade>>() {
163 @Override
164 public Set<FieldFacade> apply(final Class<?> input) {
165 return doLoad(input);
166 }
167 };
168
169 private static Set<FieldFacade> doLoad(final Class<?> key) {
170 final ImmutableSet<FieldFacade> localIdentityFieldSet = FluentIterable.from(asList(key.getDeclaredFields()))
171 .transform(toFieldFacade)
172 .filter(onlyIdentityFields)
173 .toSet();
174 final Optional<? extends Class<?>> superClass = fromNullable(key.getSuperclass());
175 final Set<FieldFacade> superIdentityFieldSet = superClass.transform(toFieldSet).or(ImmutableSet.<FieldFacade>of());
176 return Sets.union(localIdentityFieldSet, superIdentityFieldSet);
177 }
178 }
179
180 private static class FieldFacade extends WrappedValue {
181 private final Field field;
182
183 FieldFacade(final Field field) {
184 super(field);
185 this.field = field;
186 }
187
188 public Optional<Object> valueOn(final Object target) {
189 try {
190 if (!field.isAccessible()) {
191 makeAccessible();
192 }
193 return fromNullable(field.get(target));
194 } catch (IllegalAccessException e) {
195 throw new IllegalStateException(field+" was not accessible; all fields should be accessible", e);
196 }
197 }
198
199 public String getName() {
200 return field.getName();
201 }
202
203 public boolean isIdentityField() {
204 return field.isAnnotationPresent(Identity.class);
205 }
206
207 private void makeAccessible() {
208 doPrivileged(new PrivilegedAction<Void>() {
209 @Override
210 public Void run() {
211 field.setAccessible(true);
212 return null;
213 }
214 });
215 }
216 }
217 }