1 package org.wikimedia.search.extra.superdetectnoop;
2
3 import java.util.List;
4 import java.util.Locale;
5 import java.util.Objects;
6
7 import javax.annotation.Nonnull;
8 import javax.annotation.Nullable;
9
10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11
12
13
14
15
16
17 public interface ChangeHandler<T> {
18
19
20
21 Result handle(@Nullable T oldValue, @Nullable T newValue);
22
23
24
25
26
27 interface NonnullChangeHandler<T> {
28 Result handle(@Nonnull T oldValue, @Nonnull T newValue);
29 }
30
31
32
33
34
35
36 interface Recognizer {
37 @Nullable
38 ChangeHandler<Object> build(String description);
39 }
40
41
42
43
44 interface Result {
45
46
47
48
49 boolean isCloseEnough();
50
51
52
53
54
55
56 @Nullable
57 Object newValue();
58
59
60
61
62 boolean isDocumentNooped();
63 }
64
65
66
67
68
69
70
71
72 class NullSafe<T> implements ChangeHandler<T> {
73 private final NonnullChangeHandler<T> delegate;
74
75 public NullSafe(NonnullChangeHandler<T> delegate) {
76 this.delegate = delegate;
77 }
78
79 @Override
80 public Result handle(@Nullable T oldValue, @Nullable T newValue) {
81 if (oldValue == null) {
82 return Changed.forBoolean(newValue == null, newValue);
83 }
84 if (newValue == null) {
85 return new Changed(null);
86 }
87 return delegate.handle(oldValue, newValue);
88 }
89 }
90
91
92
93
94
95 final class Equal implements ChangeHandler<Object> {
96 public static final ChangeHandler<Object> INSTANCE = new Equal();
97
98 public static class Recognizer implements ChangeHandler.Recognizer {
99 @Override
100 public ChangeHandler<Object> build(String description) {
101 if (description.equals("equals")) {
102 return INSTANCE;
103 }
104 return null;
105 }
106 }
107
108 private Equal() {
109 }
110
111 @Override
112 public Result handle(@Nullable Object oldValue, @Nullable Object newValue) {
113 return Changed.forBoolean(Objects.equals(oldValue, newValue), newValue);
114 }
115 }
116
117
118
119
120
121
122
123
124
125 class TypeSafe<T> implements NonnullChangeHandler<Object> {
126
127
128
129 static <T> ChangeHandler<Object> nullAndTypeSafe(Class<T> type, NonnullChangeHandler<T> delegate) {
130 return new ChangeHandler.NullSafe<>(new ChangeHandler.TypeSafe<>(type, delegate));
131 }
132
133 private final Class<T> type;
134 private final NonnullChangeHandler<T> delegate;
135
136 public TypeSafe(Class<T> type, NonnullChangeHandler<T> delegate) {
137 this.type = type;
138 this.delegate = delegate;
139 }
140
141 @Override
142 public Result handle(@Nonnull Object oldValue, @Nonnull Object newValue) {
143 T oldValueCast;
144 T newValueCast;
145 try {
146 oldValueCast = type.cast(oldValue);
147 newValueCast = type.cast(newValue);
148 } catch (ClassCastException e) {
149 return Equal.INSTANCE.handle(oldValue, newValue);
150 }
151 return delegate.handle(oldValueCast, newValueCast);
152 }
153 }
154
155 class TypeSafeList<T> implements NonnullChangeHandler<Object> {
156 static <T> ChangeHandler<Object> nullAndTypeSafe(Class<T> type, NonnullChangeHandler<List<T>> delegate) {
157 return new NullSafe<>(new TypeSafeList<>(type, delegate));
158 }
159
160 private final Class<T> type;
161 private final NonnullChangeHandler<List<T>> delegate;
162
163 public TypeSafeList(Class<T> type, NonnullChangeHandler<List<T>> delegate) {
164 this.type = type;
165 this.delegate = delegate;
166 }
167
168 @Override
169 public Result handle(@Nonnull Object oldList, @Nonnull Object newList) {
170 return delegate.handle(toTypedList(oldList), toTypedList(newList));
171 }
172
173 @SuppressWarnings("unchecked")
174 @SuppressFBWarnings("PDP_POORLY_DEFINED_PARAMETER")
175 private List<T> toTypedList(Object value) {
176 List<T> list;
177 try {
178 list = (List<T>)value;
179 } catch (ClassCastException e) {
180 throw new IllegalArgumentException(String.format(Locale.ROOT,
181 "Expected a list, but recieved (%s)", value.getClass().getName()), e);
182 }
183 if (!list.stream().allMatch(x -> type.isAssignableFrom(x.getClass()))) {
184 throw new IllegalArgumentException(String.format(Locale.ROOT,
185 "List elements not assignable to expected type (%s)", type.getName()));
186 }
187 return list;
188 }
189 }
190
191
192
193
194
195 final class CloseEnough implements Result {
196 public static final Result INSTANCE = new CloseEnough();
197
198 private CloseEnough() {
199
200 }
201
202 @Override
203 public boolean isCloseEnough() {
204 return true;
205 }
206
207 @Override
208 public Object newValue() {
209 return null;
210 }
211
212 @Override
213 public boolean isDocumentNooped() {
214 return false;
215 }
216 }
217
218
219
220
221
222 final class NoopDocument implements Result {
223 public static final Result INSTANCE = new NoopDocument();
224
225 public static Result forBoolean(boolean noop, Object newValue) {
226 if (noop) {
227 return INSTANCE;
228 }
229 return new Changed(newValue);
230 }
231
232 private NoopDocument() {
233
234 }
235
236 @Override
237 public boolean isCloseEnough() {
238 return false;
239 }
240
241 @Override
242 public Object newValue() {
243 return null;
244 }
245
246 @Override
247 public boolean isDocumentNooped() {
248 return true;
249 }
250 }
251
252
253
254
255 class Changed implements Result {
256 public static Result forBoolean(boolean closeEnough, @Nullable Object newValue) {
257 if (closeEnough) {
258 return CloseEnough.INSTANCE;
259 }
260 return new Changed(newValue);
261 }
262
263 @Nullable private final Object newValue;
264
265 public Changed(@Nullable Object newValue) {
266 this.newValue = newValue;
267 }
268
269 @Override
270 public boolean isCloseEnough() {
271 return false;
272 }
273
274 @Override
275 public Object newValue() {
276 return newValue;
277 }
278
279 @Override
280 public boolean isDocumentNooped() {
281 return false;
282 }
283 }
284 }