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;
17  
18  
19  import java.util.Date;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import net.sourceforge.domian.test.domain.Customer;
26  import net.sourceforge.domian.test.domain.Order;
27  import net.sourceforge.domian.test.domain.VipCustomer;
28  
29  import junit.framework.TestCase;
30  
31  import static net.sourceforge.domian.specification.SpecificationFactory.a;
32  import static net.sourceforge.domian.specification.SpecificationFactory.all;
33  import static net.sourceforge.domian.specification.SpecificationFactory.allInstancesOfType;
34  import static net.sourceforge.domian.specification.SpecificationFactory.an;
35  import static net.sourceforge.domian.specification.SpecificationFactory.createSpecificationFor;
36  import static net.sourceforge.domian.specification.SpecificationFactory.is;
37  import static net.sourceforge.domian.specification.SpecificationFactory.isBefore;
38  import static net.sourceforge.domian.specification.SpecificationFactory.isEitherOf;
39  import static net.sourceforge.domian.specification.SpecificationFactory.isEqualTo;
40  import static net.sourceforge.domian.specification.SpecificationFactory.not;
41  import static net.sourceforge.domian.test.domain.Customer.Gender.FEMALE;
42  import static net.sourceforge.domian.test.domain.Customer.Gender.MALE;
43  import static net.sourceforge.domian.test.domain.Testdata.femaleCustomer;
44  import static net.sourceforge.domian.test.domain.Testdata.today;
45  import static net.sourceforge.domian.test.domain.Testdata.yesterday;
46  
47  
48  public class CompositeSpecificationTest extends TestCase {
49  
50      Specification<? extends Number> isNumber42 = isEqualTo(42L);
51      Specification<Long> isLong42 = isEqualTo(42L);
52      Specification<Long> isLong44 = isEqualTo(44L);
53      Specification<String> isJoey = isEqualTo("Joey");
54  
55  
56      public void testNullAsType() {
57          Specification spec = new ConjunctionSpecification(null);
58          assertFalse(spec.isSatisfiedBy(null));
59          assertTrue(spec.isSatisfiedBy(Object.class));
60          assertTrue(spec.isSatisfiedBy(new Object()));
61          assertTrue(spec.isSatisfiedBy(new Object[]{null}));
62          assertTrue(spec.isSatisfiedBy(new Object[]{null, null}));
63          assertTrue(spec.isSatisfiedBy(""));
64          assertTrue(spec.isSatisfiedBy("yo!"));
65          assertTrue(spec.isSatisfiedBy(femaleCustomer));
66          assertTrue(spec.isSatisfiedBy(today));
67  
68          spec = new DisjunctionSpecification(null);
69          assertFalse(spec.isSatisfiedBy(null));
70          assertTrue(spec.isSatisfiedBy(Object.class));
71          assertTrue(spec.isSatisfiedBy(new Object()));
72          assertTrue(spec.isSatisfiedBy(new Object[]{null}));
73          assertTrue(spec.isSatisfiedBy(new Object[]{null, null}));
74          assertTrue(spec.isSatisfiedBy(""));
75          assertTrue(spec.isSatisfiedBy("yo!"));
76          assertTrue(spec.isSatisfiedBy(femaleCustomer));
77          assertTrue(spec.isSatisfiedBy(today));
78  
79          spec = new JointDenialSpecification(null);
80          assertFalse(spec.isSatisfiedBy(null));
81          assertTrue(spec.isSatisfiedBy(Object.class));
82          assertTrue(spec.isSatisfiedBy(new Object()));
83          assertTrue(spec.isSatisfiedBy(new Object[]{null}));
84          assertTrue(spec.isSatisfiedBy(new Object[]{null, null}));
85          assertTrue(spec.isSatisfiedBy(""));
86          assertTrue(spec.isSatisfiedBy("yo!"));
87          assertTrue(spec.isSatisfiedBy(femaleCustomer));
88          assertTrue(spec.isSatisfiedBy(today));
89      }
90  
91  
92      public void testRawSpecificationsWithType() {
93          Specification spec = new ConjunctionSpecification(Date.class);
94          assertFalse(spec.isSatisfiedBy(null));
95          assertFalse(spec.isSatisfiedBy(Object.class));
96          assertFalse(spec.isSatisfiedBy(new Object()));
97          assertFalse(spec.isSatisfiedBy(new Object[]{null}));
98          assertFalse(spec.isSatisfiedBy(new Object[]{null, null}));
99          assertFalse(spec.isSatisfiedBy(""));
100         assertFalse(spec.isSatisfiedBy("yo!"));
101         assertFalse(spec.isSatisfiedBy(femaleCustomer));
102         assertTrue(spec.isSatisfiedBy(today));
103 
104         spec = new DisjunctionSpecification(Date.class);
105         assertFalse(spec.isSatisfiedBy(null));
106         assertFalse(spec.isSatisfiedBy(Object.class));
107         assertFalse(spec.isSatisfiedBy(new Object()));
108         assertFalse(spec.isSatisfiedBy(new Object[]{null}));
109         assertFalse(spec.isSatisfiedBy(new Object[]{null, null}));
110         assertFalse(spec.isSatisfiedBy(""));
111         assertFalse(spec.isSatisfiedBy("yo!"));
112         assertFalse(spec.isSatisfiedBy(femaleCustomer));
113         assertTrue(spec.isSatisfiedBy(today));
114 
115         spec = new JointDenialSpecification(Date.class);
116         assertFalse(spec.isSatisfiedBy(null));
117         assertFalse(spec.isSatisfiedBy(Object.class));
118         assertFalse(spec.isSatisfiedBy(new Object()));
119         assertFalse(spec.isSatisfiedBy(new Object[]{null}));
120         assertFalse(spec.isSatisfiedBy(new Object[]{null, null}));
121         assertFalse(spec.isSatisfiedBy(""));
122         assertFalse(spec.isSatisfiedBy("yo!"));
123         assertFalse(spec.isSatisfiedBy(femaleCustomer));
124         assertTrue(spec.isSatisfiedBy(today));
125     }
126 
127 
128     public void testCombinedByNull() {
129         try {
130             a(Customer.class).and(null);
131             fail("Should have thrown exception");
132 
133         } catch (IllegalArgumentException e) {
134             String expectedMessage = "Specification parameter cannot be null";
135             assertEquals(expectedMessage, e.getMessage());
136         }
137 
138         try {
139             a(Customer.class).or(null);
140             fail("Should have thrown exception");
141 
142         } catch (IllegalArgumentException e) {
143             String expectedMessage = "Specification parameter cannot be null";
144             assertEquals(expectedMessage, e.getMessage());
145         }
146 
147         try {
148             ((CompositeSpecification) not(a(Customer.class))).and(null);
149             fail("Should have thrown exception");
150 
151         } catch (IllegalArgumentException e) {
152             String expectedMessage = "Specification parameter cannot be null";
153             assertEquals(expectedMessage, e.getMessage());
154         }
155     }
156 
157 
158     public void testCombinedByItself() {
159         Customer donald = new Customer(1L, new Date()).name("Donald");
160         Customer goofy = new Customer(2L, new Date()).name("Goofy");
161 
162         CompositeSpecification<Customer> spec = a(Customer.class).where("name", is("Donald"));
163         assertTrue(spec.isSatisfiedBy(donald));
164         assertFalse(spec.isSatisfiedBy(goofy));
165 
166         CompositeSpecification<Customer> spec1 = spec.and(spec);
167         assertEquals(spec, spec1);
168         assertTrue(spec1.isSatisfiedBy(donald));
169         assertFalse(spec1.isSatisfiedBy(goofy));
170 
171         CompositeSpecification<Customer> spec2 = spec.or(spec);
172         assertEquals(spec, spec2);
173         assertTrue(spec2.isSatisfiedBy(donald));
174         assertFalse(spec2.isSatisfiedBy(goofy));
175 
176         CompositeSpecification<Customer> spec3 = spec.and(spec).or(spec);
177         assertEquals(spec, spec3);
178         assertTrue(spec3.isSatisfiedBy(donald));
179         assertFalse(spec3.isSatisfiedBy(goofy));
180 
181         CompositeSpecification<Customer> spec4 = spec1.or(spec2).and(spec3).and(spec);
182         assertEquals(spec, spec4);
183         assertTrue(spec4.isSatisfiedBy(donald));
184         assertFalse(spec4.isSatisfiedBy(goofy));
185     }
186 
187 
188     public void testHashCode() {
189         CompositeSpecification<Customer> spec1 = createSpecificationFor(null);
190         CompositeSpecification<Customer> spec2 = createSpecificationFor(null);
191         CompositeSpecification<Customer> spec3 = createSpecificationFor(Customer.class);
192         Set<Specification> set = new HashSet<Specification>(3);
193         set.add(spec1);
194         set.add(spec2);
195         set.add(spec3);
196         assertEquals(2, set.size());
197 
198         spec1 = createSpecificationFor(Customer.class);
199         spec2 = createSpecificationFor(Customer.class);
200         spec3 = createSpecificationFor(Customer.class).where("customerId", isNumber42);
201         set = new HashSet<Specification>(3);
202         set.add(spec1);
203         set.add(spec2);
204         set.add(spec3);
205         assertEquals(2, set.size());
206 
207         spec1 = createSpecificationFor(Customer.class).where("customerId", isLong42);
208         spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42);
209         spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
210         set = new HashSet<Specification>(3);
211         set.add(spec1);
212         set.add(spec2);
213         set.add(spec3);
214         assertEquals(2, set.size());
215 
216         spec1 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
217         spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
218         spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42);
219         set = new HashSet<Specification>(3);
220         set.add(spec1);
221         set.add(spec2);
222         set.add(spec3);
223         assertEquals(2, set.size());
224     }
225 
226 
227     public void testEquality() {
228         CompositeSpecification<Customer> spec1 = createSpecificationFor(null);
229         CompositeSpecification<Customer> spec2 = createSpecificationFor(null);
230         CompositeSpecification<Customer> spec3 = createSpecificationFor(Customer.class);
231         assertNotSame(spec1, spec2);
232         assertEquals(spec1, spec2);
233         assertNotSame(spec1, spec3);
234         assertFalse(spec1.equals(spec3));
235         assertNotSame(spec2, spec3);
236         assertFalse(spec2.equals(spec3));
237 
238         spec1 = createSpecificationFor(Customer.class);
239         spec2 = createSpecificationFor(Customer.class);
240         spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42);
241         assertNotSame(spec1, spec2);
242         assertEquals(spec1, spec2);
243         assertNotSame(spec1, spec3);
244         assertFalse(spec1.equals(spec3));
245         assertNotSame(spec2, spec3);
246         assertFalse(spec2.equals(spec3));
247 
248         spec1 = createSpecificationFor(Customer.class).where("customerId", isLong42);
249         spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42);
250         spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
251         assertNotSame(spec1, spec2);
252         assertEquals(spec1, spec2);
253         assertNotSame(spec1, spec3);
254         assertFalse(spec1.equals(spec3));
255         assertNotSame(spec2, spec3);
256         assertFalse(spec2.equals(spec3));
257 
258         spec1 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
259         spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
260         spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42);
261         assertNotSame(spec1, spec2);
262         assertEquals(spec1, spec2);
263         assertNotSame(spec1, spec3);
264         assertFalse(spec1.equals(spec3));
265         assertNotSame(spec2, spec3);
266         assertFalse(spec2.equals(spec3));
267 
268         // and(null) not allowed anymore...
269         /*
270         spec1 = createSpecificationFor(Customer.class).where("customerId", isLong42);
271         spec2 = spec1.and(null);
272         spec3 = spec2.and("name", isJoey).or("customerId", isLong44);
273         assertNotSame(spec1, spec2);
274         assertFalse(spec1.equals(spec2));
275         assertNotSame(spec1, spec3);
276         assertFalse(spec1.equals(spec3));
277         assertNotSame(spec2, spec3);
278         assertFalse(spec2.equals(spec3));
279         */
280     }
281 
282 
283     public void testHashCodeForSpecificationSubTypes() {
284         CompositeSpecification<Customer> spec1 = createSpecificationFor(Customer.class).where("customerId", isNumber42);
285         CompositeSpecification<Customer> spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42);
286         CompositeSpecification<Customer> spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
287         Set<Specification> set = new HashSet<Specification>(3);
288         set.add(spec1);
289         set.add(spec2);
290         set.add(spec3);
291         assertEquals(2, set.size());
292     }
293 
294 
295     public void testEqualityForSpecificationSubTypes() {
296         Specification<Customer> spec1 = createSpecificationFor(Customer.class).where("customerId", isNumber42);
297         Specification<Customer> spec2 = createSpecificationFor(Customer.class).where("customerId", isLong42);
298         Specification<Customer> spec3 = createSpecificationFor(Customer.class).where("customerId", isLong42).and("name", isJoey).or("customerId", isLong44);
299         assertEquals(spec1, spec2);
300         assertFalse(spec2.equals(spec3));
301         assertFalse(spec1.equals(spec3));
302     }
303 
304 
305     public void testCandidateSubtypes() {
306         VipCustomer vipCustomer = new VipCustomer(1001L, yesterday);
307         vipCustomer.gender(MALE);
308         vipCustomer.name("John");
309 
310         Specification<Customer> customerSpec = all(Customer.class).where("gender", isEqualTo(MALE));
311 
312         assertTrue(customerSpec.isSatisfiedBy(vipCustomer));
313     }
314 
315 
316     public void testHasConjunctions() {
317         Specification<Long> long42 = is(42L);
318         Specification<Long> long44 = is(44L);
319         Specification<Date> isBeforeYesterday = isBefore(yesterday);
320 
321         assertTrue(((AbstractCompositeSpecification) an(Order.class)).hasConjunction());
322         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42)).hasConjunction());
323         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isEitherOf(long42, long44)).and("membershipDate", isBeforeYesterday)).hasConjunction());
324         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).and("membershipDate", isBeforeYesterday).or("gender", is(FEMALE))).hasConjunction());
325         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).or("membershipDate", isBeforeYesterday).and("gender", is(FEMALE))).hasConjunction());
326         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).and("membershipDate", isBeforeYesterday).and("gender", is(FEMALE))).hasConjunction());
327     }
328 
329 
330     public void testHasDisjunctions() {
331         Specification<Long> long42 = is(42L);
332         Specification<Long> long44 = is(44L);
333         Specification<Date> isBeforeYesterday = isBefore(yesterday);
334 
335         assertFalse(((AbstractCompositeSpecification) an(Order.class)).hasDisjunction());
336         assertFalse(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42)).hasDisjunction());
337         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isEitherOf(long42, long44)).and("membershipDate", isBeforeYesterday)).hasDisjunction());
338         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).and("membershipDate", isBeforeYesterday).or("gender", is(FEMALE))).hasDisjunction());
339         assertTrue(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).or("membershipDate", isBeforeYesterday).and("gender", is(FEMALE))).hasConjunction());
340         assertFalse(((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42).and("membershipDate", isBeforeYesterday).and("gender", is(FEMALE))).hasDisjunction());
341     }
342 
343 
344     public void testGetLeafSpecificationMap() {
345         Specification<Long> long42 = is(42L);
346         Specification<Long> long44 = is(44L);
347         Specification<Date> isBeforeYesterday = isBefore(yesterday);
348 
349         assertTrue(((AbstractCompositeSpecification) an(Order.class)).getLeafSpecificationMap().isEmpty());
350 
351         Map<String, Set<LeafSpecification>> map = ((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42)).getLeafSpecificationMap();
352         assertEquals(1, map.size());
353         assertEquals("customerId", map.keySet().toArray()[0]);
354         assertEquals(1, ((Set) map.values().toArray()[0]).size());
355         assertTrue(((Set) map.values().toArray()[0]).contains(isNumber42));
356 
357         map = ((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isEitherOf(long42, long44)).and("membershipDate", isBeforeYesterday)).getLeafSpecificationMap();
358         assertEquals(2, map.size());
359         Set<String> keySet = map.keySet();
360         for (String key : keySet) {
361             if (key.equals("customerId")) {
362                 assertEquals(2, (map.get(key)).size());
363                 assertTrue((map.get(key)).contains(long42));
364                 assertTrue((map.get(key)).contains(long44));
365             } else {
366                 assertEquals("membershipDate", key);
367                 assertTrue((map.get(key).contains(isBeforeYesterday)));
368             }
369         }
370     }
371 
372 
373     public void testGetFieldNameList() {
374         Specification<Long> long42 = is(42L);
375         Specification<Long> long44 = is(44L);
376         Specification<Date> isBeforeYesterday = isBefore(yesterday);
377 
378         assertTrue(((AbstractCompositeSpecification) an(Order.class)).getAccessibleObjectNameList().isEmpty());
379 
380         List<String> list = ((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isNumber42)).getAccessibleObjectNameList();
381         assertEquals(1, list.size());
382         assertTrue(list.contains("customerId"));
383 
384         list = ((AbstractCompositeSpecification) createSpecificationFor(Customer.class).where("customerId", isEitherOf(long42, long44)).and("membershipDate", isBeforeYesterday)).getAccessibleObjectNameList();
385         assertEquals(2, list.size());
386         assertTrue(list.contains("customerId"));
387         assertTrue(list.contains("membershipDate"));
388     }
389 
390 
391     public void testCannotCreateConjunctionOfRawSpecificationsOfDifferentTypes() {
392         // This will, as we all know, not compile
393         /*
394         CompositeSpecification<Customer> customerSpec = all(Customer.class);
395         CompositeSpecification<Integer> integerSpec = all(Integer.class);
396         customerSpec.and(integerSpec);
397         */
398 
399         // But with raw specification, we have to settle with a runtime-exception
400         CompositeSpecification rawCustomerSpec = all(Customer.class);
401         CompositeSpecification rawIntegerSpec = all(Integer.class);
402         try {
403             rawCustomerSpec.and(rawIntegerSpec);
404             fail("Should have thrown exception");
405 
406         } catch (IllegalArgumentException e) {
407             String expectedMessage = "Cannot create conjunction of a Specification<net.sourceforge.domian.test.domain.Customer> and a Specification<java.lang.Integer>";
408             assertEquals(expectedMessage, e.getMessage());
409         }
410     }
411 
412 
413     public void testisTypeExcludingSpecification() {
414         CompositeSpecification<Customer> customer = allInstancesOfType(Customer.class);
415         Specification<Customer> notCustomer = not(customer);
416         Specification<Customer> customerWithNameJohnny = allInstancesOfType(Customer.class).where("name", is(not("Johnny")));
417         JointDenialSpecification<Customer> notCustomerWithNameJohnny = (JointDenialSpecification<Customer>) not(customerWithNameJohnny);
418         // Just checking...
419         assertFalse(notCustomer.isSatisfiedBy(new VipCustomer()));
420 
421         assertTrue(((JointDenialSpecification) notCustomer).isTypeExcludingSpecification());
422         assertFalse(notCustomerWithNameJohnny.isTypeExcludingSpecification());
423 
424         Specification<Customer> redundantConjunctionSpec = customer.and(customerWithNameJohnny);
425         notCustomerWithNameJohnny = (JointDenialSpecification<Customer>) not(redundantConjunctionSpec);
426         assertFalse(notCustomerWithNameJohnny.isTypeExcludingSpecification());
427 
428         Specification<Customer> redundantDisjunctionSpec = customer.or(customerWithNameJohnny);
429         notCustomerWithNameJohnny = (JointDenialSpecification<Customer>) not(redundantDisjunctionSpec);
430         assertTrue(notCustomerWithNameJohnny.isTypeExcludingSpecification());
431 
432         // Raw versions
433         CompositeSpecification rawCustomer = allInstancesOfType(Customer.class);
434         JointDenialSpecification rawNotCustomer = (JointDenialSpecification) not(rawCustomer);
435         Specification rawCustomerWithNameJohnny = allInstancesOfType(Customer.class).where("name", is(not("Johnny")));
436         JointDenialSpecification rawNotCustomerWithNameJohnny = (JointDenialSpecification) not(rawCustomerWithNameJohnny);
437 
438         assertTrue(rawNotCustomer.isTypeExcludingSpecification());
439         assertFalse(rawNotCustomerWithNameJohnny.isTypeExcludingSpecification());
440 
441         Specification rawRedundantConjunctionSpec = rawCustomer.and(rawCustomerWithNameJohnny);
442         rawNotCustomerWithNameJohnny = (JointDenialSpecification) not(rawRedundantConjunctionSpec);
443         assertFalse(rawNotCustomerWithNameJohnny.isTypeExcludingSpecification());
444 
445         Specification rawRedundantDisjunctionSpec = rawCustomer.or(rawCustomerWithNameJohnny);
446         rawNotCustomerWithNameJohnny = (JointDenialSpecification) not(rawRedundantDisjunctionSpec);
447         assertTrue(rawNotCustomerWithNameJohnny.isTypeExcludingSpecification());
448     }
449 }