1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sourceforge.domian.specification.client;
17  
18  
19  import java.util.Calendar;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.LinkedList;
26  import java.util.Map;
27  import java.util.TreeSet;
28  import java.util.concurrent.ConcurrentLinkedQueue;
29  
30  import net.sourceforge.domian.specification.CompositeSpecification;
31  import net.sourceforge.domian.specification.Specification;
32  import net.sourceforge.domian.specification.SpecificationFactory;
33  import net.sourceforge.domian.test.domain.Customer;
34  import net.sourceforge.domian.test.domain.Order;
35  import net.sourceforge.domian.test.domain.OrderLine;
36  import net.sourceforge.domian.test.domain.Testdata;
37  import net.sourceforge.domian.test.domain.VipCustomer;
38  
39  import junit.framework.TestCase;
40  
41  import static net.sourceforge.domian.specification.SpecificationFactory.a;
42  import static net.sourceforge.domian.specification.SpecificationFactory.all;
43  import static net.sourceforge.domian.specification.SpecificationFactory.allInstancesOfType;
44  import static net.sourceforge.domian.specification.SpecificationFactory.allObjects;
45  import static net.sourceforge.domian.specification.SpecificationFactory.allOf;
46  import static net.sourceforge.domian.specification.SpecificationFactory.an;
47  import static net.sourceforge.domian.specification.SpecificationFactory.anyOf;
48  import static net.sourceforge.domian.specification.SpecificationFactory.atLeast;
49  import static net.sourceforge.domian.specification.SpecificationFactory.atMost;
50  import static net.sourceforge.domian.specification.SpecificationFactory.before;
51  import static net.sourceforge.domian.specification.SpecificationFactory.both;
52  import static net.sourceforge.domian.specification.SpecificationFactory.createEqualSpecification;
53  import static net.sourceforge.domian.specification.SpecificationFactory.createNotNullSpecification;
54  import static net.sourceforge.domian.specification.SpecificationFactory.createSpecificationFor;
55  import static net.sourceforge.domian.specification.SpecificationFactory.eitherOf;
56  import static net.sourceforge.domian.specification.SpecificationFactory.equalTo;
57  import static net.sourceforge.domian.specification.SpecificationFactory.greaterThan;
58  import static net.sourceforge.domian.specification.SpecificationFactory.haveSize;
59  import static net.sourceforge.domian.specification.SpecificationFactory.include;
60  import static net.sourceforge.domian.specification.SpecificationFactory.includeAPercentageOf;
61  import static net.sourceforge.domian.specification.SpecificationFactory.instancesOfType;
62  import static net.sourceforge.domian.specification.SpecificationFactory.is;
63  import static net.sourceforge.domian.specification.SpecificationFactory.isA;
64  import static net.sourceforge.domian.specification.SpecificationFactory.isAfter;
65  import static net.sourceforge.domian.specification.SpecificationFactory.isAllOf;
66  import static net.sourceforge.domian.specification.SpecificationFactory.isAtTheSameTimeAs;
67  import static net.sourceforge.domian.specification.SpecificationFactory.isBefore;
68  import static net.sourceforge.domian.specification.SpecificationFactory.isBeforeOrAtTheSameTimeAs;
69  import static net.sourceforge.domian.specification.SpecificationFactory.isBoth;
70  import static net.sourceforge.domian.specification.SpecificationFactory.isEither;
71  import static net.sourceforge.domian.specification.SpecificationFactory.isEitherOf;
72  import static net.sourceforge.domian.specification.SpecificationFactory.isEqualTo;
73  import static net.sourceforge.domian.specification.SpecificationFactory.isNotNull;
74  import static net.sourceforge.domian.specification.SpecificationFactory.isSatisfiedBy;
75  import static net.sourceforge.domian.specification.SpecificationFactory.matchesRegularExpression;
76  import static net.sourceforge.domian.specification.SpecificationFactory.matchesWildcardExpression;
77  import static net.sourceforge.domian.specification.SpecificationFactory.matchesWildcardExpressionIgnoringCase;
78  import static net.sourceforge.domian.specification.SpecificationFactory.neitherOf;
79  import static net.sourceforge.domian.specification.SpecificationFactory.not;
80  import static net.sourceforge.domian.specification.SpecificationFactory.shouldBe;
81  import static net.sourceforge.domian.specification.SpecificationFactory.shouldBeBoth;
82  import static net.sourceforge.domian.specification.SpecificationFactory.specify;
83  import static net.sourceforge.domian.test.domain.Customer.Gender.FEMALE;
84  import static net.sourceforge.domian.test.domain.Customer.Gender.MALE;
85  import static net.sourceforge.domian.test.domain.Testdata.childFemaleCustomer;
86  import static net.sourceforge.domian.test.domain.Testdata.childMaleCustomer;
87  import static net.sourceforge.domian.test.domain.Testdata.customer12;
88  import static net.sourceforge.domian.test.domain.Testdata.customer13;
89  import static net.sourceforge.domian.test.domain.Testdata.femaleCustomer;
90  import static net.sourceforge.domian.test.domain.Testdata.maleCustomer;
91  import static net.sourceforge.domian.test.domain.Testdata.maleVipCustomer;
92  import static net.sourceforge.domian.test.domain.Testdata.oldFemaleCustomer;
93  import static net.sourceforge.domian.test.domain.Testdata.oldMaleCustomer;
94  import static net.sourceforge.domian.test.domain.Testdata.order22;
95  import static net.sourceforge.domian.test.domain.Testdata.order23;
96  import static net.sourceforge.domian.test.domain.Testdata.order24;
97  import static net.sourceforge.domian.test.domain.Testdata.orderWithNoCustomer;
98  import static net.sourceforge.domian.test.domain.Testdata.teenagerFemaleCustomer;
99  import static net.sourceforge.domian.test.domain.Testdata.teenagerMaleCustomer;
100 import static net.sourceforge.domian.test.domain.Testdata.thirtyYearsAgo;
101 import static net.sourceforge.domian.test.domain.Testdata.today;
102 import static net.sourceforge.domian.test.domain.Testdata.twentyYearsAgo;
103 import static net.sourceforge.domian.test.domain.Testdata.twoDaysAgo;
104 import static net.sourceforge.domian.test.domain.Testdata.yesterday;
105 
106 
107 /** Specification of behaviour for the {@link net.sourceforge.domian.specification.SpecificationFactory} class. */
108 public class SpecificationFactoryTest extends TestCase {
109 
110 
111     ///////////////////////////////////////////////////////////////////////////
112     //
113     //  H A P P Y - D A Y   B E H A V I O U R
114     //
115     ///////////////////////////////////////////////////////////////////////////
116 
117     public void testNullSpecificationsShouldAcceptEverything() {
118         Specification rawSpec = createSpecificationFor(null);
119         assertNotNull(rawSpec);
120         assertFalse(rawSpec.isSatisfiedBy(null));
121         assertTrue(rawSpec.isSatisfiedBy(0));
122         assertTrue(rawSpec.isSatisfiedBy(100L));
123         assertTrue(rawSpec.isSatisfiedBy("yo"));
124         assertTrue(rawSpec.isSatisfiedBy(order22));
125         assertTrue(rawSpec.isSatisfiedBy(order23));
126         assertTrue(rawSpec.isSatisfiedBy(customer12));
127 
128         Specification<? super Object> spec = (Specification) createSpecificationFor(null);
129         assertNotNull(spec);
130         assertFalse(spec.isSatisfiedBy(null));
131         assertTrue(spec.isSatisfiedBy(0));
132         assertTrue(spec.isSatisfiedBy(100L));
133         assertTrue(spec.isSatisfiedBy("yo"));
134         assertTrue(spec.isSatisfiedBy(order22));
135         assertTrue(spec.isSatisfiedBy(order23));
136         assertTrue(spec.isSatisfiedBy(customer12));
137 
138         /* and(null) not allowed anymore */
139         //spec = createSpecificationFor(null).and(null);
140         //assertNotNull(spec);
141         //assertFalse(spec.isSatisfiedBy(null));
142         //assertTrue(spec.isSatisfiedBy(0));
143         //assertTrue(spec.isSatisfiedBy(100L));
144         //assertTrue(spec.isSatisfiedBy("yo"));
145         //assertTrue(spec.isSatisfiedBy(order22));
146         //assertTrue(spec.isSatisfiedBy(order23));
147         //assertTrue(spec.isSatisfiedBy(customer12));
148 
149         /* and(null) not allowed anymore */
150         //spec = createSpecificationFor(null).or(null);
151         //assertNotNull(spec);
152         //assertFalse(spec.isSatisfiedBy(null));
153         //assertTrue(spec.isSatisfiedBy(0));
154         //assertTrue(spec.isSatisfiedBy(100L));
155         //assertTrue(spec.isSatisfiedBy("yo"));
156         //assertTrue(spec.isSatisfiedBy(order22));
157         //assertTrue(spec.isSatisfiedBy(order23));
158         //assertTrue(spec.isSatisfiedBy(customer12));
159     }
160 
161 
162     public void testRawSpecificationsWithOnlyTypeShouldBeSatisfiedByAllObjectsOfThatType() {
163         Specification orderSpec = createSpecificationFor(Order.class);
164         assertNotNull(orderSpec);
165         assertFalse(orderSpec.isSatisfiedBy(null));
166         assertFalse(orderSpec.isSatisfiedBy(0));
167         assertFalse(orderSpec.isSatisfiedBy(100L));
168         assertFalse(orderSpec.isSatisfiedBy("yo"));
169         assertTrue(orderSpec.isSatisfiedBy(order22));
170         assertTrue(orderSpec.isSatisfiedBy(order23));
171         assertFalse(orderSpec.isSatisfiedBy(customer12));
172     }
173 
174 
175     public void testSpecificationsWithoutTypeShouldBeSatisfiedByAllObjects() {
176         /* Conjunction */
177         Specification rawOrderSpec = createSpecificationFor(Order.class).and((Specification<Order>) isNotNull());
178         assertNotNull(rawOrderSpec);
179         assertFalse(rawOrderSpec.isSatisfiedBy(null));
180         assertFalse(rawOrderSpec.isSatisfiedBy(0));
181         assertFalse(rawOrderSpec.isSatisfiedBy(100L));
182         assertFalse(rawOrderSpec.isSatisfiedBy("yo"));
183         assertTrue(rawOrderSpec.isSatisfiedBy(order22));
184         assertTrue(rawOrderSpec.isSatisfiedBy(order23));
185         assertFalse(rawOrderSpec.isSatisfiedBy(customer12));
186 
187         // TODO: activate when and(specification) method is moved to specification interface
188         /*
189         Specification rawOrderSpec = isNotNull().and(SpecificationFactory.allInstancesOfType(Customer.class));
190         assertNotNull(rawOrderSpec);
191         assertFalse(rawOrderSpec.isSatisfiedBy(null));
192         assertFalse(rawOrderSpec.isSatisfiedBy(0));
193         assertFalse(rawOrderSpec.isSatisfiedBy(100L));
194         assertFalse(rawOrderSpec.isSatisfiedBy("yo"));
195         assertTrue(rawOrderSpec.isSatisfiedBy(order22));
196         assertTrue(rawOrderSpec.isSatisfiedBy(order23));
197         assertFalse(rawOrderSpec.isSatisfiedBy(customer12));
198         */
199 
200         /* and(null) not allowed anymore */
201         //Specification<Order> orderSpec = createSpecificationFor(Order.class).and(null); // Should really not compile... - well, OK
202         //assertNotNull(orderSpec);
203         //assertFalse(orderSpec.isSatisfiedBy(null));
204         //assertFalse(orderSpec.isSatisfiedBy(0));                // Should compile actually - well, OK
205         //assertFalse(orderSpec.isSatisfiedBy(100L));             // Should compile actually - well, OK
206         //assertFalse(orderSpec.isSatisfiedBy("yo"));             // Should compile actually - well, OK
207         //assertTrue(orderSpec.isSatisfiedBy(order22));
208         //assertTrue(orderSpec.isSatisfiedBy(order23));
209         //assertFalse(orderSpec.isSatisfiedBy(customer12));       // Should compile actually - well, OK
210 
211         /* Disjunction */
212         rawOrderSpec = createSpecificationFor(Order.class).or((Specification<Order>) isNotNull());
213         assertNotNull(rawOrderSpec);
214         assertFalse(rawOrderSpec.isSatisfiedBy(null));
215         assertTrue(rawOrderSpec.isSatisfiedBy(0));
216         assertTrue(rawOrderSpec.isSatisfiedBy(100L));
217         assertTrue(rawOrderSpec.isSatisfiedBy("yo"));
218         assertTrue(rawOrderSpec.isSatisfiedBy(order22));
219         assertTrue(rawOrderSpec.isSatisfiedBy(order23));
220         assertTrue(rawOrderSpec.isSatisfiedBy(customer12));
221 
222         // TODO: activate when or(specification) method is moved to specification interface
223         /*
224         RawOrderSpec = isNotNull().or(SpecificationFactory.allInstancesOfType(Customer.class));
225         assertNotNull(rawOrderSpec);
226         assertFalse(rawOrderSpec.isSatisfiedBy(null));
227         assertTrue(rawOrderSpec.isSatisfiedBy(0));
228         assertTrue(rawOrderSpec.isSatisfiedBy(100L));
229         assertTrue(rawOrderSpec.isSatisfiedBy("yo"));
230         assertTrue(rawOrderSpec.isSatisfiedBy(order22));
231         assertTrue(rawOrderSpec.isSatisfiedBy(order23));
232         assertTrue(rawOrderSpec.isSatisfiedBy(customer12));
233         */
234 
235         /* or(null) not allowed anymore */
236         //rawOrderSpec = createSpecificationFor(Order.class).or(null);
237         //assertNotNull(rawOrderSpec);
238         //assertFalse(rawOrderSpec.isSatisfiedBy(null));
239         //assertTrue(rawOrderSpec.isSatisfiedBy(0));
240         //assertTrue(rawOrderSpec.isSatisfiedBy(100L));
241         //assertTrue(rawOrderSpec.isSatisfiedBy("yo"));
242         //assertTrue(rawOrderSpec.isSatisfiedBy(order22));
243         //assertTrue(rawOrderSpec.isSatisfiedBy(order23));
244         //assertTrue(rawOrderSpec.isSatisfiedBy(customer12));
245 
246         //orderSpec = createSpecificationFor(Order.class).or(allObjects()); // Should not compile - OK
247         //assertNotNull(orderSpec);
248         //assertFalse(orderSpec.isSatisfiedBy(null));
249         //assertTrue(orderSpec.isSatisfiedBy(0));                 // Should compile actually - well, OK
250         //assertTrue(orderSpec.isSatisfiedBy(100L));              // Should compile actually - well, OK
251         //assertTrue(orderSpec.isSatisfiedBy("yo"));              // Should compile actually - well, OK
252         //assertTrue(orderSpec.isSatisfiedBy(order22));
253         //assertTrue(orderSpec.isSatisfiedBy(order23));
254         //assertTrue(orderSpec.isSatisfiedBy(customer12));        // Should compile actually - well, OK
255     }
256 
257 
258     public void testShouldCreateSimpleSpecifications() {
259         CompositeSpecification<Order> orderSpec = createSpecificationFor(Order.class);
260 
261         // Specifications are immutable - Condition method 'where' ('and') always returns new specification!
262         orderSpec.where("orderId", isEqualTo(23L));
263         assertNotNull(orderSpec);
264         assertFalse(orderSpec.isSatisfiedBy(null));
265         //assertFalse(orderSpec.isSatisfiedBy(0));                // Should not compile - OK
266         //assertFalse(orderSpec.isSatisfiedBy(100L));             // Should not compile - OK
267         //assertFalse(orderSpec.isSatisfiedBy("yo"));             // Should not compile - OK
268         assertTrue(orderSpec.isSatisfiedBy(order22));
269         assertTrue(orderSpec.isSatisfiedBy(order23));
270         //assertFalse(orderSpec.isSatisfiedBy(customer12));       // Should not compile - OK
271 
272         // Like this!
273         orderSpec = orderSpec.where("orderId", isEqualTo(23L));
274         assertNotNull(orderSpec);
275         assertFalse(orderSpec.isSatisfiedBy(null));
276         assertFalse(orderSpec.isSatisfiedBy(order22));
277         assertTrue(orderSpec.isSatisfiedBy(order23));
278 
279         // Specifications are immutable - Condition method 'or' always returns new specification!
280         orderSpec.or("orderId", isEqualTo(22L));
281         assertNotNull(orderSpec);
282         assertFalse(orderSpec.isSatisfiedBy(null));
283         assertFalse(orderSpec.isSatisfiedBy(order22));
284         assertTrue(orderSpec.isSatisfiedBy(order23));
285 
286         // Like this!
287         orderSpec = orderSpec.or("orderId", isEqualTo(22L));
288         assertNotNull(orderSpec);
289         assertFalse(orderSpec.isSatisfiedBy(null));
290         assertTrue(orderSpec.isSatisfiedBy(order22));
291         assertTrue(orderSpec.isSatisfiedBy(order23));
292     }
293 
294 
295     public void testShouldCreateDisjunctiveSpecifications() {
296         Specification<Order> orderSpec = createSpecificationFor(Order.class)
297                 .where("orderId", isEqualTo(22L))
298                 .or("orderId", isEqualTo(23L));
299         assertNotNull(orderSpec);
300         assertFalse(orderSpec.isSatisfiedBy(null));
301         assertTrue(orderSpec.isSatisfiedBy(order22));
302         assertTrue(orderSpec.isSatisfiedBy(order23));
303         assertFalse(orderSpec.isSatisfiedBy(order24));
304 
305         /* This is the symmetrical way of specification building */
306         orderSpec = createSpecificationFor(Order.class)
307                 .where("orderId", isSatisfiedBy(anyOf(isEqualTo(22L), isEqualTo(23L))));
308         assertNotNull(orderSpec);
309         assertFalse(orderSpec.isSatisfiedBy(null));
310         assertTrue(orderSpec.isSatisfiedBy(order22));
311         assertTrue(orderSpec.isSatisfiedBy(order23));
312         assertFalse(orderSpec.isSatisfiedBy(order24));
313 
314         /* More fluent with composite specifications */
315         Specification<Long> long22 = isEqualTo(22L);
316         Specification<Long> long23 = isEqualTo(23L);
317 
318         orderSpec = allInstancesOfType(Order.class).where("orderId", isSatisfiedBy(anyOf(long22, long23)));
319         assertNotNull(orderSpec);
320         assertFalse(orderSpec.isSatisfiedBy(null));
321         assertTrue(orderSpec.isSatisfiedBy(order22));
322         assertTrue(orderSpec.isSatisfiedBy(order23));
323         assertFalse(orderSpec.isSatisfiedBy(order24));
324 
325         /* More compact */
326         orderSpec = an(Order.class).where("orderId", isEither(long22, long23));
327         assertNotNull(orderSpec);
328         assertFalse(orderSpec.isSatisfiedBy(null));
329         assertTrue(orderSpec.isSatisfiedBy(order22));
330         assertTrue(orderSpec.isSatisfiedBy(order23));
331         assertFalse(orderSpec.isSatisfiedBy(order24));
332     }
333 
334 
335     public void testShouldCreateConjunctiveSpecifications_asymmetrical() {
336         /* This is the asymmetrical way (and maybe a bit confusing) of specification building */
337         CompositeSpecification<Order> orderSpec = createSpecificationFor(Order.class)
338                 .where("orderId", isEqualTo(22L))
339                 .or("orderId", isEqualTo(23L))
340                 .and("orderDate", isAfter(yesterday));
341 
342         assertNotNull(orderSpec);
343         assertFalse(orderSpec.isSatisfiedBy(null));
344         assertFalse(orderSpec.isSatisfiedBy(order22));
345         assertTrue(orderSpec.isSatisfiedBy(order23));
346 
347         Specification<Order> orderSpec2 = orderSpec.or("customer", isNotNull());
348         assertNotNull(orderSpec2);
349         assertFalse(orderSpec2.isSatisfiedBy(null));
350         assertTrue(orderSpec2.isSatisfiedBy(order22));
351         assertTrue(orderSpec2.isSatisfiedBy(order23));
352 
353         /* The wrapped specification should still hold */
354         assertNotNull(orderSpec);
355         assertFalse(orderSpec.isSatisfiedBy(null));
356         assertFalse(orderSpec.isSatisfiedBy(order22));
357         assertTrue(orderSpec.isSatisfiedBy(order23));
358     }
359 
360 
361     public void testShouldCreateConjunctiveSpecifications_symmetrical() {
362         /* This is the symmetrical way of specification building */
363         CompositeSpecification<Order> orderSpec = createSpecificationFor(Order.class)
364                 .where("orderId", isSatisfiedBy(anyOf(isEqualTo(22L), isEqualTo(23L))))
365                 .and("orderDate", isAfter(yesterday));
366 
367         assertNotNull(orderSpec);
368         assertFalse(orderSpec.isSatisfiedBy(null));
369         assertFalse(orderSpec.isSatisfiedBy(order22));
370         assertTrue(orderSpec.isSatisfiedBy(order23));
371 
372         Specification<Order> orderSpec2 = orderSpec.or("customer", isNotNull());
373         assertNotNull(orderSpec2);
374         assertFalse(orderSpec.isSatisfiedBy(null));
375         assertTrue(orderSpec2.isSatisfiedBy(order22));
376         assertTrue(orderSpec2.isSatisfiedBy(order23));
377 
378         // The wrapped specification should still hold
379         assertNotNull(orderSpec);
380         assertFalse(orderSpec.isSatisfiedBy(null));
381         assertFalse(orderSpec.isSatisfiedBy(order22));
382         assertTrue(orderSpec.isSatisfiedBy(order23));
383 
384         /* Alternative 2, even more fluent (a head start with recursive specifications) */
385         Specification<Long> long22 = createEqualSpecification(22L);
386         Specification<Long> long23 = createEqualSpecification(23L);
387         orderSpec = allInstancesOfType(Order.class).where("orderId", isEitherOf(long22, long23)).and("orderDate", isAfter(yesterday));
388 
389         assertNotNull(orderSpec);
390         assertFalse(orderSpec.isSatisfiedBy(null));
391         assertFalse(orderSpec.isSatisfiedBy(order22));
392         assertTrue(orderSpec.isSatisfiedBy(order23));
393 
394         orderSpec2 = orderSpec.or("customer", isNotNull());
395         assertNotNull(orderSpec2);
396         assertFalse(orderSpec.isSatisfiedBy(null));
397         assertTrue(orderSpec2.isSatisfiedBy(order22));
398         assertTrue(orderSpec2.isSatisfiedBy(order23));
399 
400         // The wrapped specification should still hold
401         assertNotNull(orderSpec);
402         assertFalse(orderSpec.isSatisfiedBy(null));
403         assertFalse(orderSpec.isSatisfiedBy(order22));
404         assertTrue(orderSpec.isSatisfiedBy(order23));
405     }
406 
407 
408     public void testShouldCreateSimpleRecursiveSpecifications() {
409         Specification<Customer> customersWhoHasBeenMembersForTwoDaysOrMore = createSpecificationFor(Customer.class)
410                 .where("membershipDate", isBeforeOrAtTheSameTimeAs(twoDaysAgo));
411 
412         Specification<Order> orderSpec = createSpecificationFor(Order.class)
413                 .where("customer", isSatisfiedBy(customersWhoHasBeenMembersForTwoDaysOrMore));
414 
415         assertNotNull(orderSpec);
416         assertFalse(orderSpec.isSatisfiedBy(null));
417         assertTrue(orderSpec.isSatisfiedBy(order22));
418         assertFalse(orderSpec.isSatisfiedBy(order23));
419 
420         orderSpec = createSpecificationFor(Order.class)
421                 .where("customer", isSatisfiedBy(customersWhoHasBeenMembersForTwoDaysOrMore))
422                 .and("orderDate", isAfter(today));
423 
424         assertNotNull(orderSpec);
425         assertFalse(orderSpec.isSatisfiedBy(null));
426         assertFalse(orderSpec.isSatisfiedBy(order22));
427         assertFalse(orderSpec.isSatisfiedBy(order23));
428 
429         orderSpec = createSpecificationFor(Order.class)
430                 .where("customer", isSatisfiedBy(customersWhoHasBeenMembersForTwoDaysOrMore))
431                 .or("orderDate", isBeforeOrAtTheSameTimeAs(today));
432 
433         assertNotNull(orderSpec);
434         assertFalse(orderSpec.isSatisfiedBy(null));
435         assertTrue(orderSpec.isSatisfiedBy(order22));
436         assertTrue(orderSpec.isSatisfiedBy(order23));
437     }
438 
439 
440     public void testShouldCreateDoubleRecursiveSpecifications() {
441         Specification<Customer> allCustomers = allInstancesOfType(Customer.class);
442 
443         Specification<Customer> customersWhoHasBeenMembersForTwoDaysOrMore = createSpecificationFor(Customer.class)
444                 .where("membershipDate", isBeforeOrAtTheSameTimeAs(twoDaysAgo));
445 
446         Specification<Order> orderSpec = createSpecificationFor(Order.class)
447                 .where("customer", isSatisfiedBy(anyOf(customersWhoHasBeenMembersForTwoDaysOrMore, allCustomers)));
448 
449         assertNotNull(orderSpec);
450         assertFalse(orderSpec.isSatisfiedBy(null));
451         assertTrue(orderSpec.isSatisfiedBy(order22));
452         assertTrue(orderSpec.isSatisfiedBy(order23));
453         assertFalse(orderSpec.isSatisfiedBy(orderWithNoCustomer));
454 
455         orderSpec = createSpecificationFor(Order.class)
456                 .where("customer", isSatisfiedBy(allOf(customersWhoHasBeenMembersForTwoDaysOrMore, allCustomers)));
457 
458         assertNotNull(orderSpec);
459         assertFalse(orderSpec.isSatisfiedBy(null));
460         assertTrue(orderSpec.isSatisfiedBy(order22));
461         assertFalse(orderSpec.isSatisfiedBy(order23));
462         assertFalse(orderSpec.isSatisfiedBy(orderWithNoCustomer));
463 
464         /* Implicit */
465         orderSpec = createSpecificationFor(Order.class)
466                 .where("customer", isEitherOf(customersWhoHasBeenMembersForTwoDaysOrMore, allCustomers));
467 
468         assertNotNull(orderSpec);
469         assertFalse(orderSpec.isSatisfiedBy(null));
470         assertTrue(orderSpec.isSatisfiedBy(order22));
471         assertTrue(orderSpec.isSatisfiedBy(order23));
472         assertFalse(orderSpec.isSatisfiedBy(orderWithNoCustomer));
473 
474         orderSpec = createSpecificationFor(Order.class)
475                 .where("customer", isAllOf(customersWhoHasBeenMembersForTwoDaysOrMore, allCustomers));
476 
477         assertNotNull(orderSpec);
478         assertFalse(orderSpec.isSatisfiedBy(null));
479         assertTrue(orderSpec.isSatisfiedBy(order22));
480         assertFalse(orderSpec.isSatisfiedBy(order23));
481         assertFalse(orderSpec.isSatisfiedBy(orderWithNoCustomer));
482     }
483 
484 
485     public void testShouldCreateSpecificationsWithSpecificationVarargParameters() {
486         Specification<Customer> maleCustomer_1 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(childMaleCustomer.getCustomerId()));
487         Specification<Customer> maleCustomer_2 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(teenagerMaleCustomer.getCustomerId()));
488         Specification<Customer> maleCustomer_3 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(maleCustomer.getCustomerId()));
489         Specification<Customer> maleCustomer_4 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(oldMaleCustomer.getCustomerId()));
490 
491         Specification<Customer> femaleCustomer_1 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(childFemaleCustomer.getCustomerId()));
492         Specification<Customer> femaleCustomer_2 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(teenagerFemaleCustomer.getCustomerId()));
493         Specification<Customer> femaleCustomer_3 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(femaleCustomer.getCustomerId()));
494         Specification<Customer> femaleCustomer_4 = createSpecificationFor(Customer.class).where("customerId", isEqualTo(oldFemaleCustomer.getCustomerId()));
495 
496         Specification<Customer> maleCustomer = createSpecificationFor(Customer.class).where("gender", is(MALE));
497         Specification<Customer> femaleCustomer = createSpecificationFor(Customer.class).where("gender", is(FEMALE));
498 
499         Specification<Customer> customer = createSpecificationFor(Customer.class);
500 
501         Specification allObjects = allObjects();
502 
503         Order maleCustomer_1_Order = new Order(1001L, today, childMaleCustomer, Collections.<OrderLine>emptyList());
504         Order maleCustomer_2_Order = new Order(1002L, today, teenagerMaleCustomer, Collections.<OrderLine>emptyList());
505         Order maleCustomer_3_Order = new Order(1003L, yesterday, Testdata.maleCustomer, Collections.<OrderLine>emptyList());
506         Order maleCustomer_4_Order = new Order(1004L, yesterday, oldMaleCustomer, Collections.<OrderLine>emptyList());
507         Order femaleCustomer_1_Order = new Order(2001L, today, childFemaleCustomer, Collections.<OrderLine>emptyList());
508         Order femaleCustomer_2_Order = new Order(2002L, today, teenagerFemaleCustomer, Collections.<OrderLine>emptyList());
509         Order femaleCustomer_3_Order = new Order(2003L, yesterday, Testdata.femaleCustomer, Collections.<OrderLine>emptyList());
510         Order femaleCustomer_4_Order = new Order(2004L, yesterday, oldFemaleCustomer, Collections.<OrderLine>emptyList());
511 
512         Specification<Order> orderSpec;
513 
514         /* Three spec parameters */
515         orderSpec = createSpecificationFor(Order.class).where("customer", isEitherOf(maleCustomer_1, maleCustomer_2, maleCustomer_3));
516 
517         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_1_Order));
518         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_2_Order));
519         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_3_Order));
520         assertFalse(orderSpec.isSatisfiedBy(maleCustomer_4_Order));
521         assertFalse(orderSpec.isSatisfiedBy(femaleCustomer_1_Order));
522         assertFalse(orderSpec.isSatisfiedBy(femaleCustomer_2_Order));
523         assertFalse(orderSpec.isSatisfiedBy(femaleCustomer_3_Order));
524         assertFalse(orderSpec.isSatisfiedBy(femaleCustomer_4_Order));
525 
526         orderSpec = createSpecificationFor(Order.class).where("customer", isEitherOf(femaleCustomer_1, femaleCustomer_3, allObjects));
527 
528         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_1_Order));
529         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_2_Order));
530         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_3_Order));
531         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_4_Order));
532         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_1_Order));
533         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_2_Order));
534         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_3_Order));
535         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_4_Order));
536 
537         /* Four spec parameters */
538         orderSpec = createSpecificationFor(Order.class).where("customer", isSatisfiedBy(eitherOf(maleCustomer_1, maleCustomer_2, maleCustomer_3, femaleCustomer)));
539 
540         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_1_Order));
541         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_2_Order));
542         assertTrue(orderSpec.isSatisfiedBy(maleCustomer_3_Order));
543         assertFalse(orderSpec.isSatisfiedBy(maleCustomer_4_Order));
544         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_1_Order));
545         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_2_Order));
546         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_3_Order));
547         assertTrue(orderSpec.isSatisfiedBy(femaleCustomer_4_Order));
548     }
549 
550 
551     /* More behaviour specifications */
552     public void testShouldCreateCompositeSpecifications_1() {
553         Specification<Customer> femaleTeenagerSpec = specify(Customer.class)
554                 .where("birthDate", isAfter(thirtyYearsAgo))
555                 .and("gender", is(FEMALE));
556 
557         assertFalse(femaleTeenagerSpec.isSatisfiedBy(null));
558         assertFalse(femaleTeenagerSpec.isSatisfiedBy(teenagerMaleCustomer));
559         assertFalse(femaleTeenagerSpec.isSatisfiedBy(maleCustomer));
560         assertFalse(femaleTeenagerSpec.isSatisfiedBy(oldMaleCustomer));
561         assertTrue(femaleTeenagerSpec.isSatisfiedBy(teenagerFemaleCustomer));
562         assertFalse(femaleTeenagerSpec.isSatisfiedBy(femaleCustomer));
563         assertFalse(femaleTeenagerSpec.isSatisfiedBy(oldFemaleCustomer));
564     }
565 
566 
567     public void testShouldCreateCompositeSpecifications_2() {
568         //Specification<Customer> femaleCustomerSpec = specify(Customer.class).where("gender", is(Gender.class, FEMALE));
569         //Specification<Customer> maleCustomerSpec = specify(Customer.class).where("gender", is(Gender.class, MALE));
570         //Specification<Customer> teenagerCustomerSpec = specify(Customer.class).where("birthDate", isAfter(twentyYearsAgo));
571 
572         /* This is the asymmetrical way (and maybe a bit confusing) of specification building */
573         /* But it is all wrong specified... will throw java.lang.IllegalArgumentException due to wrong typing
574         Specification<Customer> spec = specify(Customer.class)
575                 .where("birthDate", isSatisfiedBy(teenagerCustomerSpec))
576                 .and("gender", isSatisfiedBy(femaleCustomerSpec))
577                 .or("gender", isSatisfiedBy(maleCustomerSpec));
578         */
579 
580         /* This is the correct asymmetrical way (and maybe a bit confusing) of specification building */
581         Specification<Customer.Gender> femaleSpec = is(FEMALE);
582         Specification<Customer.Gender> maleSpec = is(MALE);
583         Specification<Date> teenagerSpec = isAfter(twentyYearsAgo);
584 
585         Specification<Customer> spec = specify(Customer.class)
586                 .where("birthDate", isSatisfiedBy(teenagerSpec))
587                 .and("gender", isSatisfiedBy(femaleSpec))
588                 .or("gender", isSatisfiedBy(maleSpec));
589 
590         assertFalse(spec.isSatisfiedBy(null));
591         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
592         assertTrue(spec.isSatisfiedBy(maleCustomer));
593         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
594         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
595         assertFalse(spec.isSatisfiedBy(femaleCustomer));
596         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
597     }
598 
599 
600     public void testShouldCreateCompositeSpecifications_3() {
601         Specification<Customer.Gender> femaleSpec = is(FEMALE);
602         Specification<Customer.Gender> maleSpec = is(MALE);
603         Specification<Date> teenagerSpec = isAfter(twentyYearsAgo);
604 
605         Specification<Customer> femaleCustomer = a(Customer.class).where("gender", isSatisfiedBy(femaleSpec));
606         Specification<Customer> maleCustomer = a(Customer.class).where("gender", isSatisfiedBy(maleSpec));
607         Specification<Customer> teenagerCustomer = a(Customer.class).where("birthDate", isSatisfiedBy(teenagerSpec));
608         Specification<Customer> teenageFemaleCustomer = isBoth(femaleCustomer, teenagerCustomer);
609 
610         /* This is the symmetrical (and maybe safer) way of specification building */
611         //                             1                                              3  2
612         Specification<Customer> spec = shouldBeBoth(femaleCustomer, teenagerCustomer).or(a(maleCustomer));
613         /* Here I want a OR spec with two wrapped specs
614          * ...one being a AND spec with one wrapped specs: maleCustomer
615          * ...the other being a AND spec with two wrapped spec: femaleCustomer, teenagerCustomer
616          */
617         assertFalse(spec.isSatisfiedBy(null));
618         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
619         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
620         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
621         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
622         assertFalse(spec.isSatisfiedBy(Testdata.femaleCustomer));
623         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
624 
625         /* Alternative way */
626         spec = teenageFemaleCustomer.or(maleCustomer);
627         assertFalse(spec.isSatisfiedBy(null));
628         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
629         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
630         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
631         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
632         assertFalse(spec.isSatisfiedBy(Testdata.femaleCustomer));
633         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
634 
635         /* Alternative way */
636         spec = eitherOf(maleCustomer, teenageFemaleCustomer);
637         assertFalse(spec.isSatisfiedBy(null));
638         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
639         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
640         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
641         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
642         assertFalse(spec.isSatisfiedBy(Testdata.femaleCustomer));
643         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
644 
645         /* Alternative way */
646         spec = shouldBe(eitherOf(maleCustomer, both(femaleCustomer, teenagerCustomer)));
647         assertFalse(spec.isSatisfiedBy(null));
648         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
649         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
650         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
651         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
652         assertFalse(spec.isSatisfiedBy(Testdata.femaleCustomer));
653         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
654 
655         //     2        1                4  3
656         spec = shouldBe(a(maleCustomer)).or(both(femaleCustomer, teenagerCustomer));
657         /* Here I want a OR spec with two wrapped specs
658          * ...one being a AND spec with two wrapped specs: femaleCustomer, teenagerCustomer
659          * ...the other being a AND spec with one wrapped spec: maleCustomer
660          *    ... or maybe a AND spec with field and fieldSpec (should also work)
661          */
662         assertFalse(spec.isSatisfiedBy(null));
663         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
664         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
665         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
666         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
667         assertFalse(spec.isSatisfiedBy(Testdata.femaleCustomer));
668         assertFalse(spec.isSatisfiedBy(oldFemaleCustomer));
669     }
670 
671 
672     public void testShouldCreateCompositeSpecifications_4() {
673         Specification<Customer> customer = SpecificationFactory.allInstancesOfType(Customer.class);
674         Specification<Customer> femaleCustomer = isA(Customer.class).where("gender", is(FEMALE));
675         Specification<Customer> maleCustomer = isA(Customer.class).where("gender", is(MALE));
676         Specification<Customer> teenagerCustomer = a(Customer.class).where("birthDate", isAfter(twentyYearsAgo));
677 
678         /* Both symmetrical and asymmetrical specification building */
679         Specification<Customer> spec = shouldBe(a(maleCustomer)).or(both(femaleCustomer, teenagerCustomer)).or(customer);
680 
681         assertFalse(spec.isSatisfiedBy(null));
682         assertTrue(spec.isSatisfiedBy(teenagerMaleCustomer));
683         assertTrue(spec.isSatisfiedBy(Testdata.maleCustomer));
684         assertTrue(spec.isSatisfiedBy(oldMaleCustomer));
685         assertTrue(spec.isSatisfiedBy(teenagerFemaleCustomer));
686         assertTrue(spec.isSatisfiedBy(Testdata.femaleCustomer));
687         assertTrue(spec.isSatisfiedBy(oldFemaleCustomer));
688     }
689 
690 
691     public void testShouldConvertWildcardExpressionsToRegularExpressions() {
692         // How to convert e* -> [eE]\S*
693         Specification<String> stringsStartingWithIgnoreCaseESpec = matchesRegularExpression("[eE]\\S*");
694         assertFalse(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy(null));
695         assertFalse(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy(""));
696         assertFalse(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("  "));
697         assertFalse(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("Peer"));
698         assertTrue(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("e"));
699         assertTrue(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("eirik"));
700         assertTrue(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("E"));
701         assertTrue(stringsStartingWithIgnoreCaseESpec.isSatisfiedBy("Eirik"));
702 
703         // Mundane format of "string starting with 'e' or 'E'
704         Specification<String> convertedStringsStartingWithESpec = matchesWildcardExpression("e*");
705         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy(null));
706         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy(""));
707         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy("  "));
708         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy("Peer"));
709         assertTrue(convertedStringsStartingWithESpec.isSatisfiedBy("e"));
710         assertTrue(convertedStringsStartingWithESpec.isSatisfiedBy("eirik"));
711         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy("E"));
712         assertFalse(convertedStringsStartingWithESpec.isSatisfiedBy("Eirik"));
713 
714         Specification<String> convertedStringsStartingWithEIgnoringCaseSpec = matchesWildcardExpressionIgnoringCase("e*");
715         assertFalse(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy(null));
716         assertFalse(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy(""));
717         assertFalse(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("  "));
718         assertFalse(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("Peer"));
719         assertTrue(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("e"));
720         assertTrue(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("eirik"));
721         assertTrue(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("E"));
722         assertTrue(convertedStringsStartingWithEIgnoringCaseSpec.isSatisfiedBy("Eirik"));
723     }
724 
725 
726     public void testNegatedSpecification_1() {
727         Specification<Integer> int42 = isEqualTo(42);
728         Specification<Integer> notInt42 = not(int42);
729         Specification<Integer> notNotNotInt42 = not(not(not(int42)));
730         Specification<Integer> notNotNotNotInt42 = not(not(not(not(int42))));
731 
732         assertTrue(int42.isSatisfiedBy(42));
733         assertFalse(int42.isSatisfiedBy(43));
734 
735         assertFalse(notInt42.isSatisfiedBy(42));
736         assertTrue(notInt42.isSatisfiedBy(41));
737 
738         assertFalse(notNotNotInt42.isSatisfiedBy(42));
739         assertTrue(notNotNotInt42.isSatisfiedBy(43));
740 
741         assertTrue(notNotNotNotInt42.isSatisfiedBy(42));
742         assertFalse(notNotNotNotInt42.isSatisfiedBy(41));
743     }
744 
745 
746     public void testNegatedSpecification_2() {
747         Specification<Customer> female = a(Customer.class).where("gender", is(FEMALE));
748         Specification<Customer> teenager = a(Customer.class).where("birthDate", isAfter(twentyYearsAgo));
749 
750         Specification<Customer> spec = shouldBe(a(female)).and(not(a(teenager)));
751 
752         assertFalse(spec.isSatisfiedBy(null));
753         assertFalse(spec.isSatisfiedBy(teenagerMaleCustomer));
754         assertFalse(spec.isSatisfiedBy(Testdata.maleCustomer));
755         assertFalse(spec.isSatisfiedBy(oldMaleCustomer));
756         assertFalse(spec.isSatisfiedBy(teenagerFemaleCustomer));
757         assertTrue(spec.isSatisfiedBy(Testdata.femaleCustomer));
758         assertTrue(spec.isSatisfiedBy(oldFemaleCustomer));
759     }
760 
761 
762     /* TODO v0.5: activate
763     public void testNegatedSpecification_3() {
764         try {
765             // Contradiction
766             not(instancesOfType(Customer.class)).and(instancesOfType(Customer.class));
767             fail("Should have thrown exception");
768 
769         } catch (IllegalArgumentException e) {
770             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
771             assertEquals(expectedMessage, e.getMessage());
772         }
773         try {
774             // Contradiction
775             instancesOfType(Customer.class).and(not(instancesOfType(Customer.class)));
776             fail("Should have thrown exception");
777 
778         } catch (IllegalArgumentException e) {
779             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
780             assertEquals(expectedMessage, e.getMessage());
781         }
782 
783         // TODO: Shoudn't this compile as well? Preferred form...
784         //Specification<Customer> spec2 = instancesOfType(Customer.class).and(not(instancesOfType(VipCustomer.class)));
785         // ...or even better
786         //Specification spec2 = instancesOfType(Customer.class).but(not(instancesOfType(VipCustomer.class)));
787 
788         //assertFalse(spec2.isSatisfiedBy(new Date())); // Should not compile - OK
789         //assertTrue(spec2.isSatisfiedBy(maleCustomer));
790         //assertFalse(spec2.isSatisfiedBy(maleVipCustomer));
791 
792         Specification spec4 = not(instancesOfType(VipCustomer.class)).and(instancesOfType(Customer.class));
793         assertFalse(spec4.isSatisfiedBy(new Date()));  // Should not compile - ...
794         assertTrue(spec4.isSatisfiedBy(maleCustomer));
795         assertFalse(spec4.isSatisfiedBy(maleVipCustomer));
796 
797 
798         // Tautology
799         //Specification spec3 = instancesOfType(VipCustomer.class).and(instancesOfType(Customer.class));
800 
801 
802         // Forvansking
803         //Specification spec4 = instancesOfType(Customer.class).and(instancesOfType(VipCustomer.class));
804         //Specification spec5 = all(Customer.class);
805         //assertEquals(spec4, spec5);
806 
807         try {
808             // Contradiction
809             instancesOfType(VipCustomer.class).and(not(instancesOfType(Customer.class)));
810             fail("Should have thrown exception");
811 
812         } catch (IllegalArgumentException e) {
813             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
814             assertEquals(expectedMessage, e.getMessage());
815         }
816     }
817     */
818 
819 
820     public void testCollectionSpecificationHaveSize() {
821         customer12.addOrder(order22);
822         customer13.addOrder(order23);
823         customer13.addOrder(order24);
824 
825         Specification<Customer> customersWithMoreThanOneOrderSpec = createSpecificationFor(Customer.class).where("orders", haveSize(greaterThan(1)));
826         assertNotNull(customersWithMoreThanOneOrderSpec);
827         assertFalse(customersWithMoreThanOneOrderSpec.isSatisfiedBy(customer12));
828         assertTrue(customersWithMoreThanOneOrderSpec.isSatisfiedBy(customer13));
829 
830         customer12.clearOrders();
831         customer13.clearOrders();
832     }
833 
834 
835     public void testCollectionSpecificationInclude() {
836         customer12.addOrder(order22);
837         customer13.addOrder(order23);
838         customer13.addOrder(order24);
839 
840         Specification<Order> orderFromBeforeToday = an(Order.class).where("orderDate", isBefore(today));
841 
842         Specification<Customer> customersWithAtLeastOneOrderFromBeforeTodaySpec =
843                 createSpecificationFor(Customer.class).where("orders", include(atLeast(1), orderFromBeforeToday));
844         assertTrue(customersWithAtLeastOneOrderFromBeforeTodaySpec.isSatisfiedBy(customer12));
845         assertFalse(customersWithAtLeastOneOrderFromBeforeTodaySpec.isSatisfiedBy(customer13));
846 
847         customer12.clearOrders();
848         customer13.clearOrders();
849     }
850 
851 
852     public void testCollectionSpecificationIncludeAPercentageOf() {
853         customer12.addOrder(order22);
854         customer13.addOrder(order23);
855         customer13.addOrder(order24);
856 
857         Specification<Order> ordersFromToday = an(Order.class).where("orderDate", isAtTheSameTimeAs(today));
858 
859         Specification<Customer> customersWithAtMost5PercentOfOrdersAreFromTodaySpec =
860                 createSpecificationFor(Customer.class).where("orders", includeAPercentageOf(atMost(5), ordersFromToday));
861         assertTrue(customersWithAtMost5PercentOfOrdersAreFromTodaySpec.isSatisfiedBy(customer12));
862         assertFalse(customersWithAtMost5PercentOfOrdersAreFromTodaySpec.isSatisfiedBy(customer13));
863 
864         Specification<Customer> customersWithMoreThan90PercentOfOrdersAreFromTodaySpec =
865                 createSpecificationFor(Customer.class).where("orders", includeAPercentageOf(atLeast(90), ordersFromToday));
866         assertFalse(customersWithMoreThan90PercentOfOrdersAreFromTodaySpec.isSatisfiedBy(customer12));
867         assertTrue(customersWithMoreThan90PercentOfOrdersAreFromTodaySpec.isSatisfiedBy(customer13));
868 
869         customer12.clearOrders();
870         customer13.clearOrders();
871     }
872 
873 
874     public void testCompositeCollectionSpecification() {
875         customer12.addOrder(order22);
876         customer13.addOrder(order23);
877         customer13.addOrder(order24);
878 
879         Specification<Order> ordersFromAfterTwoDaysAgo = all(Order.class).where("orderDate", isAfter(twoDaysAgo));
880 
881         Specification<Customer> customersWithMoreThanOneOrder =
882                 createSpecificationFor(Customer.class).where("orders", haveSize(greaterThan(1)));
883         assertFalse(customersWithMoreThanOneOrder.isSatisfiedBy(customer12));
884         assertTrue(customersWithMoreThanOneOrder.isSatisfiedBy(customer13));
885 
886         Specification<Customer> customersWithAtLeastOneOrderMoreRecentThanTwoDaysAgo = createSpecificationFor(Customer.class)
887                 .where("orders", include(atLeast(1), ordersFromAfterTwoDaysAgo));
888         assertTrue(customersWithAtLeastOneOrderMoreRecentThanTwoDaysAgo.isSatisfiedBy(customer12));
889         assertTrue(customersWithAtLeastOneOrderMoreRecentThanTwoDaysAgo.isSatisfiedBy(customer13));
890 
891         Specification<Customer> customersWithAtLeastTwoOrdersMoreRecentThanTwoDaysAgo = createSpecificationFor(Customer.class)
892                 .where("orders", haveSize(greaterThan(1)))
893                 .and("orders", include(atLeast(2), ordersFromAfterTwoDaysAgo));
894         assertFalse(customersWithAtLeastTwoOrdersMoreRecentThanTwoDaysAgo.isSatisfiedBy(customer12));
895         assertTrue(customersWithAtLeastTwoOrdersMoreRecentThanTwoDaysAgo.isSatisfiedBy(customer13));
896 
897         customer12.clearOrders();
898         customer13.clearOrders();
899     }
900 
901 
902     public void testDirectDateStringSpecifications() {
903         String errorMsg = " is not recognized as a valid date string -" +
904                           " use a method with java.util.Date as parameter type to see whether the provided date parameter is actually illegal," +
905                           " or expressed in a not-supported format";
906         try {
907             before("20077-10-10");
908             fail("Should have thrown exception");
909 
910         } catch (IllegalArgumentException e) {
911             String expectedMessage = "\"20077-10-10\"" + errorMsg;
912             assertEquals(expectedMessage, e.getMessage());
913         }
914         try {
915             before("10252007");
916             fail("Should have thrown exception");
917 
918         } catch (IllegalArgumentException e) {
919             String expectedMessage = "\"10252007\"" + errorMsg;
920             assertEquals(expectedMessage, e.getMessage());
921         }
922         try {
923             before("10.25.2007");
924             fail("Should have thrown exception");
925 
926         } catch (IllegalArgumentException e) {
927             String expectedMessage = "\"10.25.2007\"" + errorMsg;
928             assertEquals(expectedMessage, e.getMessage());
929         }
930         try {
931             before("10-25-2007");
932             fail("Should have thrown exception");
933 
934         } catch (IllegalArgumentException e) {
935             String expectedMessage = "\"10-25-2007\"" + errorMsg;
936             assertEquals(expectedMessage, e.getMessage());
937         }
938         Calendar cal = Calendar.getInstance();
939         cal.set(2007, Calendar.OCTOBER, 25, 0, 0, 0);
940         long milliSec = cal.getTime().getTime();
941         milliSec = milliSec / 1000 * 1000;
942         Date startOf25Oct2007 = new Date(milliSec);
943         Specification<Date> expectedSpec = before(startOf25Oct2007);
944 
945         Specification<Date> spec = before("20071025");
946         assertEquals(expectedSpec, spec);
947 
948         spec = before("2007.10.25");
949         assertEquals(expectedSpec, spec);
950 
951         spec = before("25.10.2007");
952         assertEquals(expectedSpec, spec);
953 
954         spec = before("2007-10-25");
955         assertEquals(expectedSpec, spec);
956 
957         spec = before("25-10-2007");
958         assertEquals(expectedSpec, spec);
959 
960         spec = before("2007/10/25");
961         assertEquals(expectedSpec, spec);
962 
963         spec = before("25/10/2007");
964         assertEquals(expectedSpec, spec);
965     }
966 
967 
968     ///////////////////////////////////////////////////////////////////////////
969     //
970     //  E X C E P T I O N   B E H A V I O U R
971     //
972     ///////////////////////////////////////////////////////////////////////////
973 
974     public void testShouldNotAcceptWhereClauseMoreThanOnce() {
975         try {
976             CompositeSpecification<Order> orderSpec = createSpecificationFor(Order.class).where("orderId", isEqualTo(23L));
977             orderSpec.where(null, null);
978             fail("Should have thrown exception");
979 
980         } catch (UnsupportedOperationException e) {
981             String expectedMessage = "The \"where\" clause can only be invoked once in specification expressions";
982             assertEquals(expectedMessage, e.getMessage());
983         }
984     }
985 
986 
987     public void testShouldNotAcceptAndOrAsFirstParameterizedClause() {
988         try {
989             createSpecificationFor(Order.class).and("orderId", isEqualTo(23L));
990             fail("Should have thrown exception");
991 
992         } catch (UnsupportedOperationException e) {
993             String expectedMessage = "The \"where\" clause must be invoked before \"and\"/\"or\" in parameterized specification expressions";
994             assertEquals(expectedMessage, e.getMessage());
995         }
996         try {
997             createSpecificationFor(Order.class).or("orderId", isEqualTo(23L));
998             fail("Should have thrown exception");
999 
1000         } catch (UnsupportedOperationException e) {
1001             String expectedMessage = "The \"where\" clause must be invoked before \"and\"/\"or\" in parameterized specification expressions";
1002             assertEquals(expectedMessage, e.getMessage());
1003         }
1004     }
1005 
1006 
1007     public void testShouldNotAcceptNullAsAccessibleObjectNameArgument() {
1008         try {
1009             createSpecificationFor(Order.class).where(null, null);
1010             fail("Should have thrown exception");
1011 
1012         } catch (IllegalArgumentException e) {
1013             String expectedMessage = "Accessible object name parameter cannot be null";
1014             assertEquals(expectedMessage, e.getMessage());
1015         }
1016     }
1017 
1018 
1019     public void testShouldNotAcceptBlankAccessibleObjectNameArgument() {
1020         try {
1021             createSpecificationFor(Order.class).where("", isEqualTo(42L));
1022             fail("Should have thrown exception");
1023 
1024         } catch (IllegalArgumentException e) {
1025             String expectedMessage = "Accessible object name parameter \"\" is neither a valid method name, nor a valid field name";
1026             assertEquals(expectedMessage, e.getMessage());
1027         }
1028         try {
1029             createSpecificationFor(Order.class).where("   ", null);
1030             fail("Should have thrown exception");
1031 
1032         } catch (IllegalArgumentException e) {
1033             String expectedMessage = "Accessible object name parameter \"   \" is neither a valid method name, nor a valid field name";
1034             assertEquals(expectedMessage, e.getMessage());
1035         }
1036     }
1037 
1038 
1039     public void testShouldNotAcceptIllegalAccessibleObjectName() {
1040         try {
1041             createSpecificationFor(Order.class).where("nonExistingAccessibleObject", isEqualTo(23L));
1042             fail("Should have thrown exception");
1043 
1044         } catch (IllegalArgumentException e) {
1045             String expectedMessage = "Neither a field nor a method (with no formal parameters) named \"nonExistingAccessibleObject\" found in class net.sourceforge.domian.test.domain.Order";
1046             assertEquals(expectedMessage, e.getMessage());
1047         }
1048     }
1049 
1050 
1051     public void testShouldNotAcceptTypeMismatchBetweenFieldAndValueBoundedSpecification_1() {
1052         Specification<Customer> customersWhoHasBeenMembersForTwoDaysOrMore =
1053                 specify(Customer.class).where("membershipDate", isBeforeOrAtTheSameTimeAs(twoDaysAgo));
1054         try {
1055             createSpecificationFor(Order.class)
1056                     .where("customer", isSatisfiedBy(customersWhoHasBeenMembersForTwoDaysOrMore))
1057                     .and("customer", isAfter(today));
1058             fail("Should have thrown exception");
1059 
1060         } catch (IllegalArgumentException e) {
1061             String expectedMessage = "Field \"customer\" of type net.sourceforge.domian.test.domain.Customer [in class net.sourceforge.domian.test.domain.Order], cannot be specified by a Specification<java.util.Date>";
1062             assertEquals(expectedMessage, e.getMessage());
1063         }
1064     }
1065 
1066 
1067     public void testShouldNotAcceptTypeMismatchBetweenFieldAndValueBoundedSpecification_2() {
1068         Specification<Date> today = createEqualSpecification(Testdata.today);
1069         Specification<Date> tomorrow = createEqualSpecification(Testdata.tomorrow);
1070 
1071         try {
1072             createSpecificationFor(Order.class).where("orderId", isEither(today, tomorrow));
1073             fail("Should have thrown exception");
1074 
1075         } catch (IllegalArgumentException e) {
1076             String expectedMessage = "Field \"orderId\" of type java.lang.Long [in class net.sourceforge.domian.test.domain.Order], cannot be specified by a Specification<java.util.Date>";
1077             assertEquals(expectedMessage, e.getMessage());
1078         }
1079 
1080         // TODO: better yet, compile-time failure
1081         /* Until java.lang.reflect.Field become type parametrized (why isn't it?!) */
1082         //all(Order.class).where("orderId", isEither(today, tomorrow)); // Should not compile (in a perfect world)
1083     }
1084 
1085 
1086     public void testShouldNotAcceptNullOrEmptySpecificationVarargs() {
1087         /* Conjunction */
1088         try {
1089             allOf((Specification) null);
1090             fail("Should have thrown exception");
1091 
1092         } catch (Exception e) {
1093             String expectedMessage = "Specification varargs cannot be null";
1094             assertEquals(expectedMessage, e.getMessage());
1095         }
1096 
1097         try {
1098             allOf((Specification[]) null);
1099             fail("Should have thrown exception");
1100 
1101         } catch (Exception e) {
1102             String expectedMessage = "Specification varargs cannot be null";
1103             assertEquals(expectedMessage, e.getMessage());
1104         }
1105 
1106         try {
1107             allOf((Specification[]) new Specification[]{});
1108             fail("Should have thrown exception");
1109 
1110         } catch (Exception e) {
1111             String expectedMessage = "Specification varargs cannot be empty";
1112             assertEquals(expectedMessage, e.getMessage());
1113         }
1114         // But this is acceptable, of course
1115         allOf((Specification[]) new Specification[]{createNotNullSpecification(Object.class)});
1116 
1117         /* Disjunction */
1118         try {
1119             anyOf((Specification) null);
1120             fail("Should have thrown exception");
1121 
1122         } catch (Exception e) {
1123             String expectedMessage = "Specification varargs cannot be null";
1124             assertEquals(expectedMessage, e.getMessage());
1125         }
1126 
1127         try {
1128             anyOf((Specification[]) null);
1129             fail("Should have thrown exception");
1130 
1131         } catch (Exception e) {
1132             String expectedMessage = "Specification varargs cannot be null";
1133             assertEquals(expectedMessage, e.getMessage());
1134         }
1135 
1136         try {
1137             anyOf((Specification[]) new Specification[]{});
1138             fail("Should have thrown exception");
1139 
1140         } catch (Exception e) {
1141             String expectedMessage = "Specification varargs cannot be empty";
1142             assertEquals(expectedMessage, e.getMessage());
1143         }
1144 
1145         /* Joint denial */
1146         try {
1147             neitherOf((Specification) null);
1148             fail("Should have thrown exception");
1149 
1150         } catch (Exception e) {
1151             String expectedMessage = "Specification varargs cannot be null";
1152             assertEquals(expectedMessage, e.getMessage());
1153         }
1154 
1155         try {
1156             neitherOf((Specification[]) null);
1157             fail("Should have thrown exception");
1158 
1159         } catch (Exception e) {
1160             String expectedMessage = "Specification varargs cannot be null";
1161             assertEquals(expectedMessage, e.getMessage());
1162         }
1163 
1164         try {
1165             neitherOf((Specification[]) new Specification[]{});
1166             fail("Should have thrown exception");
1167 
1168         } catch (Exception e) {
1169             String expectedMessage = "Specification varargs cannot be empty";
1170             assertEquals(expectedMessage, e.getMessage());
1171         }
1172         // But this is acceptable, of course
1173         anyOf((Specification[]) new Specification[]{createNotNullSpecification(Object.class)});
1174     }
1175 
1176 
1177     public void testCollectionWithNonCollectionTypeField() {
1178         try {
1179             createSpecificationFor(Customer.class).where("membershipDate", haveSize(greaterThan(1), Long.class));
1180             fail("Should have thrown exception");
1181 
1182         } catch (Exception e) {
1183             String expectedMessage = "Field \"membershipDate\" of type java.util.Date [in class net.sourceforge.domian.test.domain.Customer], cannot be specified by a net.sourceforge.domian.specification.CollectionSpecification";
1184             assertEquals(expectedMessage, e.getMessage());
1185         }
1186 
1187         createSpecificationFor(TestClass.class).where("rawHashSetField", haveSize(greaterThan(8)));
1188         createSpecificationFor(TestClass.class).where("rawLinkedListField", haveSize(greaterThan(8)));
1189         createSpecificationFor(TestClass.class).where("rawConcurrentLinkedQueueField", haveSize(greaterThan(8)));
1190         createSpecificationFor(TestClass.class).where("rawSubClassedTreeSetField", haveSize(greaterThan(8)));
1191         createSpecificationFor(TestClass.class).where("hashSetField", haveSize(greaterThan(8)));
1192         createSpecificationFor(TestClass.class).where("linkedListField", haveSize(greaterThan(8)));
1193         createSpecificationFor(TestClass.class).where("concurrentLinkedQueueField", haveSize(greaterThan(8)));
1194         createSpecificationFor(TestClass.class).where("subClassedTreeSetField", haveSize(greaterThan(8)));
1195 
1196         try {
1197             createSpecificationFor(TestClass.class).where("rawHashMapField", haveSize(greaterThan(8)));
1198             fail("Should have thrown exception");
1199 
1200         } catch (Exception e) {
1201             String expectedMessage = "Field \"rawHashMapField\" of type java.util.Map [in class net.sourceforge.domian.specification.client.SpecificationFactoryTest$TestClass], cannot be specified by a net.sourceforge.domian.specification.CollectionSpecification";
1202             assertEquals(expectedMessage, e.getMessage());
1203         }
1204         try {
1205             createSpecificationFor(TestClass.class).where("hashMapField", haveSize(greaterThan(8)));
1206             fail("Should have thrown exception");
1207 
1208         } catch (Exception e) {
1209             String expectedMessage = "Field \"hashMapField\" of type java.util.Map [in class net.sourceforge.domian.specification.client.SpecificationFactoryTest$TestClass], cannot be specified by a net.sourceforge.domian.specification.CollectionSpecification";
1210             assertEquals(expectedMessage, e.getMessage());
1211         }
1212     }
1213 
1214 
1215     public void testCannotCreateConjunctionOutOfDisjointSpecifications() {
1216         CompositeSpecification<Customer> customers = allInstancesOfType(Customer.class);
1217         try {
1218             customers.and(not(customers));
1219             fail("Should have thrown exception");
1220 
1221         } catch (IllegalArgumentException e) {
1222             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
1223             assertEquals(expectedMessage, e.getMessage());
1224         }
1225 
1226         try {
1227             not(customers).and(customers);
1228             fail("Should have thrown exception");
1229 
1230         } catch (IllegalArgumentException e) {
1231             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
1232             assertEquals(expectedMessage, e.getMessage());
1233         }
1234 
1235         try {
1236             CompositeSpecification<Customer> notCustomer = (CompositeSpecification<Customer>) not(customers);
1237             Specification<Customer> customersNotNamedTommy = all(Customer.class).where("name", is(not("Tommy")));
1238             notCustomer.and(customersNotNamedTommy);
1239             fail("Should have thrown exception");
1240 
1241         } catch (IllegalArgumentException e) {
1242             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
1243             assertEquals(expectedMessage, e.getMessage());
1244         }
1245 
1246         try {
1247             CompositeSpecification<Customer> notCustomer = (CompositeSpecification<Customer>) not(customers);
1248             CompositeSpecification<Customer> customersNotNamedTommy = all(Customer.class).where("name", is(not("Tommy")));
1249             customersNotNamedTommy.and(notCustomer);
1250             fail("Should have thrown exception");
1251 
1252         } catch (IllegalArgumentException e) {
1253             String expectedMessage = "Cannot create a conjunction out of two disjoint specifications";
1254             assertEquals(expectedMessage, e.getMessage());
1255         }
1256 
1257         // This should be OK of course
1258         Specification<Customer> customersNamedJohnny = all(Customer.class).where("name", is("Johnny"));
1259         Specification<Customer> customersNotNamedJohnny = not(customersNamedJohnny);
1260         Specification<Customer> customersNotNamedTommy = all(Customer.class).where("name", is(not("Tommy")));
1261         customersNotNamedTommy.and(customersNotNamedJohnny);
1262     }
1263 
1264 
1265     public void testShouldNotApproveOfWhereClauseFromLeafSpecification() {
1266         try {
1267             Specification<Integer> greaterThan12 = greaterThan(12).where("value", is(not(equalTo(12))));
1268             assertTrue(greaterThan12.isSatisfiedBy(13));
1269             assertFalse(greaterThan12.isSatisfiedBy(12));
1270 
1271         } catch (UnsupportedOperationException e) {
1272             String expectedMessage = "'where' clause is applicable only for CompositeSpecification instances";
1273             assertEquals(expectedMessage, e.getMessage());
1274         }
1275     }
1276 
1277 
1278     ///////////////////////////////////////////////////////////////////////////
1279     //
1280     //  B E H A V I O U R   N O T   Y E T   S P E C I F I E D
1281     //
1282     ///////////////////////////////////////////////////////////////////////////
1283 
1284     // TODO: use java.lang.reflect.Field instead of strings and classes - that would increased type-safety when it comes to specifying class fields...
1285     public void testShouldHandleReadyMadeFieldParametersAsWell() {}
1286 
1287 
1288     ///////////////////////////////////////////////////////////////////////////
1289     //
1290     //  T E S T   C L A S S E S
1291     //
1292     ///////////////////////////////////////////////////////////////////////////
1293 
1294     class TestClass {
1295         Collection rawHashSetField = new HashSet();
1296         Collection rawLinkedListField = new LinkedList();
1297         Collection rawConcurrentLinkedQueueField = new ConcurrentLinkedQueue();
1298         Collection rawSubClassedTreeSetField = new TreeSet() {
1299             // Subclass - deliberately left empty
1300         };
1301         Map rawHashMapField = new HashMap();
1302         Collection hashSetField = new HashSet<Integer>();
1303         Collection linkedListField = new LinkedList<String>();
1304         Collection concurrentLinkedQueueField = new ConcurrentLinkedQueue<OrderLine>();
1305         Collection subClassedTreeSetField = new TreeSet<Collection<Customer>>() {
1306             // Subclass - deliberately left empty
1307         };
1308         Map hashMapField = new HashMap<Integer, Collection>();
1309     }
1310 }