1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sourceforge.domian.specification;
17
18
19 import java.lang.reflect.AccessibleObject;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.security.PrivilegedAction;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.apache.commons.lang.Validate;
33 import org.apache.commons.lang.builder.EqualsBuilder;
34 import org.apache.commons.lang.builder.HashCodeBuilder;
35
36 import static java.lang.Boolean.FALSE;
37 import static java.lang.Boolean.TRUE;
38 import static java.security.AccessController.doPrivileged;
39 import static net.sourceforge.domian.specification.ParameterizedSpecification.createParameterizedSpecification;
40 import static net.sourceforge.domian.util.ReflectionUtils.canCastAtLeastOneWay;
41 import static net.sourceforge.domian.util.ReflectionUtils.canCastFrom_To;
42 import static net.sourceforge.domian.util.ReflectionUtils.getAllAccessibleObjectsFrom;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 abstract class AbstractCompositeSpecification<T> extends AbstractSpecification<T> implements CompositeSpecification<T> {
59
60
61 protected Set<Specification<? super T>> specifications = new HashSet<Specification<? super T>>();
62
63
64 AbstractCompositeSpecification(final Class<T> type) {
65 this.type = type;
66 }
67
68
69
70 protected AbstractCompositeSpecification finalizeCreation() {
71 this.specifications = Collections.unmodifiableSet(this.specifications);
72 return this;
73 }
74
75
76 @Override
77 public <F> CompositeSpecification<T> where(final String accessibleObjectName, final Specification<F> accessibleObjectSpecification) {
78 if (!this.specifications.isEmpty()) {
79 throw new UnsupportedOperationException("The \"where\" clause can only be invoked once in specification expressions");
80 }
81 return and(createParameterizedSpecification(this.type, accessibleObjectName, accessibleObjectSpecification));
82 }
83
84
85 @Override
86 public <F> CompositeSpecification<T> and(String accessibleObjectName, Specification<F> accessibleObjectSpecification) {
87 if (this.specifications.isEmpty()) {
88 throw new UnsupportedOperationException("The \"where\" clause must be invoked before \"and\"/\"or\" in parameterized specification expressions");
89 }
90 return and(createParameterizedSpecification(this.type, accessibleObjectName, accessibleObjectSpecification));
91 }
92
93
94 @Override
95 public <F> CompositeSpecification<T> or(String accessibleObjectName, Specification<F> accessibleObjectSpecification) {
96 if (this.specifications.isEmpty()) {
97 throw new UnsupportedOperationException("The \"where\" clause must be invoked before \"and\"/\"or\" in parameterized specification expressions");
98 }
99 return or(createParameterizedSpecification(this.type, accessibleObjectName, accessibleObjectSpecification));
100 }
101
102
103 @Override
104 public CompositeSpecification<T> and(final Specification<? super T> otherSpecification) {
105 Validate.notNull(otherSpecification, "Specification parameter cannot be null");
106 if (!canCastAtLeastOneWay(this.getType(), otherSpecification.getType())) {
107 throw new IllegalArgumentException("Cannot create conjunction of a Specification<" + this.getType().getName() + "> and a Specification<" + otherSpecification.getType().getName() + ">");
108 }
109 if (otherSpecification.equals(this)) {
110 return this;
111 }
112 if (this.isDisjointWith(otherSpecification)) {
113 throw new IllegalArgumentException("Cannot create a conjunction out of two disjoint specifications");
114 }
115 if (canCastFrom_To(this.getType(), otherSpecification.getType())) {
116
117
118
119 return wrapWithNewSpecification(new ConjunctionSpecification<T>(this.type), otherSpecification);
120 } else {
121 return wrapWithNewSpecification(new ConjunctionSpecification<T>((Class<T>) otherSpecification.getType()), otherSpecification);
122 }
123 }
124
125
126 @Override
127 public CompositeSpecification<T> or(final Specification<? super T> otherSpecification) {
128 Validate.notNull(otherSpecification, "Specification parameter cannot be null");
129 if (otherSpecification.equals(this)) {
130 return this;
131 }
132 return wrapWithNewSpecification(new DisjunctionSpecification<T>(this.type), otherSpecification);
133 }
134
135
136 @Override
137 @SuppressWarnings("unchecked")
138 public CompositeSpecification<T> remainderUnsatisfiedBy(final T candidate) {
139 Validate.notNull(candidate, "Candidate object parameter cannot be null");
140 if (this.hasDisjunction()) {
141 throw new IllegalArgumentException("Partial satisfaction of disjunctive specifications is not supported");
142 }
143 if (!canCastFrom_To(candidate.getClass(), ((Specification) this).getType())) {
144
145 return this;
146 }
147 if (this.isSatisfiedBy(candidate)) {
148
149 return null;
150 }
151
152 CompositeSpecification<T> remainderSpec = new ConjunctionSpecification<T>(this.getType());
153
154
155 final Map<AccessibleObject, ParameterizedSpecification> parameterizedSpecMap = new HashMap<AccessibleObject, ParameterizedSpecification>();
156 final Set<Specification<?>> wrappedSpecs = new HashSet<Specification<?>>();
157 getAllSpecifications(wrappedSpecs);
158 for (final Specification wrappedSpec : wrappedSpecs) {
159 if (wrappedSpec instanceof ParameterizedSpecification) {
160 final ParameterizedSpecification wrappedParameterizedSpec = (ParameterizedSpecification) wrappedSpec;
161 parameterizedSpecMap.put(wrappedParameterizedSpec.accessibleObject, wrappedParameterizedSpec);
162 }
163 }
164
165 final Map<String, AccessibleObject> accessibleObjectMap = getAllAccessibleObjectsFrom(candidate);
166 for (final AccessibleObject accessibleObject : accessibleObjectMap.values()) {
167 if (accessibleObject instanceof Method) {
168 final Method method = (Method) accessibleObject;
169 try {
170 final Specification spec = parameterizedSpecMap.get(method);
171 if (spec != null) {
172 final Object candidateValue = method.invoke(candidate);
173
174 if (!((ParameterizedSpecification) spec).accessibleObjectSpecification.isSatisfiedBy(candidateValue)) {
175 remainderSpec = remainderSpec.and(spec);
176 parameterizedSpecMap.remove(method);
177 }
178 }
179 } catch (IllegalArgumentException e) {
180
181 e.printStackTrace();
182
183 } catch (IllegalAccessException e) {
184
185 e.printStackTrace();
186
187 } catch (InvocationTargetException e) {
188
189 e.printStackTrace();
190 }
191
192 } else if (accessibleObject instanceof Field) {
193 final Field field = (Field) accessibleObject;
194 doPrivileged(new PrivilegedAction<Void>() {
195 @Override
196 public Void run() {
197 field.setAccessible(true);
198 return null;
199 }
200 });
201 final ParameterizedSpecification spec = parameterizedSpecMap.get(field);
202
203 if (spec != null && !spec.isSatisfiedBy(candidate)) {
204
205 remainderSpec = remainderSpec.and(spec);
206 parameterizedSpecMap.remove(field);
207 }
208 }
209 }
210
211 if (parameterizedSpecMap.isEmpty()) {
212 return this;
213 }
214
215 return remainderSpec;
216 }
217
218
219 @Override
220 public Boolean isSatisfiedBy(final T candidate) {
221 return candidate != null && !(this.type != null && !canCastFrom_To(candidate.getClass(), this.type));
222 }
223
224
225 @Override
226 @SuppressWarnings("unchecked")
227 public Boolean isGeneralizationOf(final Specification<? extends T> specification) {
228 Validate.notNull(specification, "Specification parameter cannot be null");
229 if (this.equals(specification)) {
230 return TRUE;
231 }
232 if (specification instanceof NotNullSpecification) {
233 return FALSE;
234 }
235 if (this instanceof ConjunctionSpecification) {
236 if (!canCastFrom_To(specification.getType(), this.getType())) {
237 return FALSE;
238 }
239 if (specification instanceof DisjunctionSpecification) {
240 final AbstractCompositeSpecification<? extends T> castedSpecification = (AbstractCompositeSpecification<? extends T>) specification;
241
242 final Set<Specification<? extends T>> wrappedSetOfSpecs = (Set) castedSpecification.specifications;
243 for (final Specification<? extends T> wrappedSpec : wrappedSetOfSpecs) {
244 if (!this.isGeneralizationOf(wrappedSpec)) {
245 return FALSE;
246 }
247 }
248 }
249 if (specification instanceof JointDenialSpecification) {
250 return FALSE;
251 }
252
253 for (final Specification<? super T> thisWrappedSpec : this.specifications) {
254 if (thisWrappedSpec instanceof LeafSpecification) {
255 Validate.isTrue(specification instanceof AbstractSpecification, "Specification parameter is not an instance of AbstractSpecification");
256 final Set<Specification<?>> includedSpecs = new HashSet<Specification<?>>();
257 ((AbstractSpecification) specification).getAllSpecifications(includedSpecs);
258 if (!includedSpecs.contains(thisWrappedSpec)) {
259 return FALSE;
260 }
261 } else if (thisWrappedSpec instanceof CompositeSpecification) {
262 if (!thisWrappedSpec.isGeneralizationOf(specification)) {
263 return FALSE;
264 }
265 }
266 }
267 return TRUE;
268
269 } else if (this instanceof DisjunctionSpecification) {
270 Boolean candidateSpecIsGeneralizationOfAllThisWrappedSpecifications = TRUE;
271 for (final Specification thisWrappedSpec : this.specifications) {
272
273 if (thisWrappedSpec.isGeneralizationOf(specification)) {
274 return TRUE;
275 }
276
277 if (specification instanceof ConjunctionSpecification && !specification.isGeneralizationOf(thisWrappedSpec)) {
278 candidateSpecIsGeneralizationOfAllThisWrappedSpecifications = FALSE;
279 }
280 }
281 if (candidateSpecIsGeneralizationOfAllThisWrappedSpecifications) {
282 return FALSE;
283 }
284
285 } else {
286 return FALSE;
287 }
288 return TRUE;
289 }
290
291
292 @Override
293 public Boolean isSpecialCaseOf(final Specification<? super T> specification) {
294 Validate.notNull(specification, "Specification parameter cannot be null");
295 return canCastAtLeastOneWay(this.getType(), specification.getType())
296 && specification.isGeneralizationOf(this);
297 }
298
299
300 @Override
301 protected Boolean hasConjunction() {
302
303 return TRUE;
304 }
305
306
307 @Override
308 protected Boolean hasDisjunction() {
309 if (this instanceof DisjunctionSpecification) {
310 return TRUE;
311 }
312 for (final Specification<? super T> wrappedSpec : this.specifications) {
313 if (((AbstractSpecification) wrappedSpec).hasDisjunction()) {
314 return TRUE;
315 }
316 }
317 return FALSE;
318 }
319
320
321
322 protected Boolean hasParameterization() {
323 for (final Specification<?> spec : getAllSpecifications()) {
324 if (spec instanceof ParameterizedSpecification) {
325 return TRUE;
326 }
327 }
328 return FALSE;
329 }
330
331
332
333 protected Boolean isSimpleComposition() {
334 return !this.hasParameterization();
335 }
336
337
338 @Override
339 protected void getAllSpecifications(final Set<Specification<?>> specificationSet) {
340 Validate.notNull(specificationSet, "Set of specifications parameter cannot be null");
341 specificationSet.add(this);
342 for (final Specification wrappedSpec : this.specifications) {
343 Validate.isTrue(wrappedSpec instanceof AbstractSpecification, "Wrapped specification is not an instance of AbstractSpecification");
344 specificationSet.add(wrappedSpec);
345 ((AbstractSpecification) wrappedSpec).getAllSpecifications(specificationSet);
346 }
347 }
348
349
350
351 Set<Specification<?>> getAllSpecifications() {
352 final Set<Specification<?>> specificationSet = new HashSet<Specification<?>>();
353 for (final Specification wrappedSpec : this.specifications) {
354 Validate.isTrue(wrappedSpec instanceof AbstractSpecification, "Wrapped specification is not an instance of AbstractSpecification");
355 specificationSet.add(wrappedSpec);
356 ((AbstractSpecification) wrappedSpec).getAllSpecifications(specificationSet);
357 }
358 return specificationSet;
359 }
360
361
362
363 Map<String, Set<LeafSpecification>> getLeafSpecificationMap() {
364 final Map<String, Set<LeafSpecification>> map = new HashMap<String, Set<LeafSpecification>>();
365 final Set<Specification<?>> wrappedSpecSet = new HashSet<Specification<?>>();
366 getAllSpecifications(wrappedSpecSet);
367 for (final Specification wrappedSpec : wrappedSpecSet) {
368 if (wrappedSpec instanceof ParameterizedSpecification) {
369 final String accessibleObjectName = ((ParameterizedSpecification) wrappedSpec).getName();
370 final Set<Specification<?>> wrappedAccessibleObjectSpecSet = new HashSet<Specification<?>>();
371 final Set<LeafSpecification> accessibleObjectLeafSpecSet = new HashSet<LeafSpecification>();
372 ((ParameterizedSpecification) wrappedSpec).getAllSpecifications(wrappedAccessibleObjectSpecSet);
373 for (final Specification wrappedAccessibleObjectSpec : wrappedAccessibleObjectSpecSet) {
374 if (wrappedAccessibleObjectSpec instanceof LeafSpecification
375 && !(wrappedAccessibleObjectSpec instanceof ParameterizedSpecification)) {
376 accessibleObjectLeafSpecSet.add((LeafSpecification) wrappedAccessibleObjectSpec);
377 }
378 }
379 map.put(accessibleObjectName, accessibleObjectLeafSpecSet);
380 }
381 }
382 return Collections.unmodifiableMap(map);
383 }
384
385
386
387 List<String> getAccessibleObjectNameList() {
388 final Map<String, Set<LeafSpecification>> map = getLeafSpecificationMap();
389 final List<String> list = new ArrayList<String>(map.size());
390 for (final String accessibleObjectName : map.keySet()) {
391 list.add(accessibleObjectName);
392 }
393 return Collections.unmodifiableList(list);
394 }
395
396
397
398 abstract protected Boolean isSpecifyingAllInstancesOfItsType();
399
400
401 CompositeSpecification<T> wrapWithNewSpecification(final AbstractCompositeSpecification<T> newSpecification,
402 final Specification<? super T> specificationToBeWrapped) {
403 newSpecification.specifications.add(specificationToBeWrapped);
404 newSpecification.specifications.add(this);
405 return (CompositeSpecification<T>) newSpecification.purify(true);
406 }
407
408
409 @Override
410 public int hashCode() {
411 return new HashCodeBuilder(2947, 9401).append(this.type).append(this.specifications).toHashCode();
412 }
413
414
415 @Override
416 public boolean equals(Object otherObject) {
417 if (otherObject == null) { return false; }
418 if (!(otherObject instanceof AbstractCompositeSpecification)) { return false; }
419 return new EqualsBuilder()
420 .append(this.type, ((AbstractCompositeSpecification) otherObject).type)
421 .append(this.specifications, ((AbstractCompositeSpecification) otherObject).specifications)
422 .isEquals();
423 }
424 }