View Javadoc

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.HashSet;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import org.apache.commons.lang.Validate;
24  
25  import static java.lang.Boolean.FALSE;
26  import static java.lang.Boolean.TRUE;
27  import static net.sourceforge.domian.util.ReflectionUtils.canCast;
28  import static net.sourceforge.domian.util.ReflectionUtils.canCastAtLeastOneWay;
29  import static net.sourceforge.domian.util.ReflectionUtils.canCastFrom_To;
30  
31  
32  /**
33   * A composite specification where <i>at least one</i> of the wrapped specifications
34   * must be satisfied for this overall composite specification to be satisfied.
35   * <p/>
36   * The boolean logic operator equivalent is <i><b>OR</b></i>.
37   *
38   * @author Eirik Torske
39   * @since 0.1
40   */
41  final class DisjunctionSpecification<T> extends AbstractCompositeSpecification<T> {
42  
43      DisjunctionSpecification(final Class<T> type) {
44          super(type);
45      }
46  
47  
48      @Override
49      public Boolean isSatisfiedBy(final T candidate) {
50          if (candidate == null) {
51              return FALSE;
52          }
53          if (this.type == null && this.specifications.isEmpty()) {
54              return TRUE;
55          }
56          if (this.type != null && canCastFrom_To(candidate.getClass(), this.type) && this.specifications.isEmpty()) {
57              return TRUE;
58          }
59          for (final Specification<? super T> specification : this.specifications) {
60              if (specification == null || specification.isSatisfiedBy(candidate)) {
61                  return TRUE;
62              }
63          }
64          return FALSE;
65      }
66  
67  
68      @Override
69      public Boolean isDisjointWith(final Specification<?> specification) {
70          Validate.notNull(specification, "Specification parameter cannot be null");
71  
72          // Pruning: if same/equal specifications
73          if (this == specification || this.equals(specification)) {
74              return FALSE;
75          }
76  
77          // Pruning: if specifications and types are fundamentally disjoint
78          if (!canCastAtLeastOneWay(this.getType(), specification.getType())) {
79              return TRUE;
80          }
81  
82          // Pruning: if type-only spec - and candidate is negated type-only-spec
83          if (this.isSpecifyingAllInstancesOfItsType()) {
84              if (specification instanceof JointDenialSpecification) {
85                  final Specification<?> negatedSpec = getNegatedSpecification((JointDenialSpecification<T>) specification);
86                  if (negatedSpec.equals(this)) {
87                      return TRUE;
88                  }
89              }
90              return FALSE;
91          }
92  
93          if (specification instanceof ValueBoundSpecification) {
94              for (final Specification<?> spec : this.specifications) {
95                  if (!spec.isDisjointWith(specification)) {
96                      return FALSE;
97                  }
98              }
99              return TRUE;
100         }
101 
102         if (specification instanceof JointDenialSpecification) {
103             // Pruning: if specification and types are fundamentally disjoint
104             final JointDenialSpecification jointDenialSpecification = (JointDenialSpecification) specification;
105             final Specification negatedSpec = getNegatedSpecification(jointDenialSpecification);
106             if (canCast(this.getType(), negatedSpec.getType()) && jointDenialSpecification.isTypeExcludingSpecification()) {
107                 return TRUE;
108             }
109             // Delegate negated stuff to JointDenialSpecification
110             return specification.isDisjointWith(this);
111         }
112 
113         if (specification instanceof DisjunctionSpecification) {
114             if (this.hasParameterization()) {
115                 final AbstractCompositeSpecification candidateConjuntionSpecification = (AbstractCompositeSpecification) specification;
116                 if (candidateConjuntionSpecification.hasParameterization()) {
117                     final Map<String, Set<LeafSpecification>> leafSpecificationMap = this.getLeafSpecificationMap();
118                     final Map<String, Set<LeafSpecification>> candidateLeafSpecificationMap = candidateConjuntionSpecification.getLeafSpecificationMap();
119                     for (final String memberName : leafSpecificationMap.keySet()) {
120                         if (candidateLeafSpecificationMap.keySet().contains(memberName)) {
121                             final Set<LeafSpecification> specificationSet = leafSpecificationMap.get(memberName);
122                             final Set<LeafSpecification> memberSpecificationSet = candidateLeafSpecificationMap.get(memberName);
123                             for (final Specification leafSpecification : specificationSet) {
124                                 for (final Specification memberLeafSpecification : memberSpecificationSet) {
125                                     if (!leafSpecification.isDisjointWith(memberLeafSpecification)) {
126                                         return FALSE;
127                                     }
128                                 }
129                             }
130                         } else {
131                             return FALSE;
132                         }
133                     }
134                     for (final String memberName : candidateLeafSpecificationMap.keySet()) {
135                         if (leafSpecificationMap.keySet().contains(memberName)) {
136                             final Set<LeafSpecification> specificationSet = leafSpecificationMap.get(memberName);
137                             final Set<LeafSpecification> memberSpecificationSet = candidateLeafSpecificationMap.get(memberName);
138                             for (final Specification leafSpecification : memberSpecificationSet) {
139                                 for (final Specification memberLeafSpecification : specificationSet) {
140                                     if (!leafSpecification.isDisjointWith(memberLeafSpecification)) {
141                                         return FALSE;
142                                     }
143                                 }
144                             }
145                         } else {
146                             return FALSE;
147                         }
148                     }
149                     return TRUE;
150                 }
151 
152             } else {
153                 // Lemma: if one of composites are NOT disjoint => return false; else true
154                 final AbstractCompositeSpecification<?> compositeSpecification = (AbstractCompositeSpecification) specification;
155                 for (final Specification spec : compositeSpecification.specifications) {
156                     if (!spec.isDisjointWith(this)) {
157                         return FALSE;
158                     }
159                 }
160                 return TRUE;
161             }
162         }
163 
164         if (specification instanceof ConjunctionSpecification) {
165             final AbstractCompositeSpecification<?> conjunctionCandidate = (AbstractCompositeSpecification) specification;
166             if (conjunctionCandidate.isSimpleComposition()) {
167                 // Lemma: hvis _alle_ "conjuncte" wrappede specs tilsammen er disjoint til alle "disjuncte" wrappede specs -> disjoint
168                 final Set<Specification<?>> listOfDisjointDisjunctionSpecs = new HashSet<Specification<?>>();
169                 for (final Specification wrappedSpec : conjunctionCandidate.specifications) {
170                     for (final Specification spec : this.specifications) {
171                         if (wrappedSpec.isDisjointWith(spec)) {
172                             listOfDisjointDisjunctionSpecs.add(spec);
173                         }
174                     }
175                 }
176                 return listOfDisjointDisjunctionSpecs.equals(this.specifications);
177             }
178 
179             // Normal chained/wrapped/parameterized specifications structure
180             // Lemma: hvis _alle_ "conjuncte" wrappede specs er disjoint til minst én og samme av de "disjuncte" wrappede specs -> disjoint
181             final Set<Specification> commonDisjointToSpecSet = new HashSet<Specification>();
182             for (final Specification wrappedSpec : conjunctionCandidate.specifications) {
183                 final Set<Specification> wrappedSpecDisjointToSpecSet = new HashSet<Specification>();
184                 for (final Specification spec : this.specifications) {
185                     if (wrappedSpec.isDisjointWith(spec)) {
186                         wrappedSpecDisjointToSpecSet.add(spec);
187                     }
188                 }
189                 if (wrappedSpecDisjointToSpecSet.isEmpty()) {
190                     return FALSE;
191                 }
192                 if (commonDisjointToSpecSet.isEmpty()) {
193                     commonDisjointToSpecSet.addAll(commonDisjointToSpecSet);
194                 } else {
195                     commonDisjointToSpecSet.retainAll(wrappedSpecDisjointToSpecSet);
196                 }
197             }
198             return !commonDisjointToSpecSet.isEmpty();
199         }
200 
201         return FALSE;
202     }
203 
204 
205     @Override
206     protected Boolean isSpecifyingAllInstancesOfItsType() {
207         for (final Specification<? super T> wrappedSpec : this.specifications) {
208             if (wrappedSpec instanceof AbstractCompositeSpecification) {
209                 if (((AbstractCompositeSpecification) wrappedSpec).isSpecifyingAllInstancesOfItsType()) {
210                     return TRUE;
211                 }
212             }
213         }
214         return FALSE;
215     }
216 
217 
218     @Override
219     protected AbstractSpecification<T> purify(final boolean doPurifyInversions) {
220         //setPurified(true);
221         if (this.specifications.isEmpty()) {
222             return this;
223         }
224         /* Nope, not ready for ditching the composite specification type altogether... */
225         if (this.specifications.size() == 1) {
226             return (((AbstractSpecification<T>) this.specifications.iterator().next()).purify(true));
227         }
228         // TODO: pruning - check for wrapped spec types... is it purifiable?
229         final DisjunctionSpecification<T> newPurifiedDisjunctionSpecification = new DisjunctionSpecification<T>(this.getType());
230         //newPurifiedDisjunctionSpecification.setPurified(true);
231         for (final Specification<? super T> wrappedSpec : this.specifications) {
232             //if (((AbstractSpecification) wrappedSpec).isPurified()) {
233             //    newPurifiedDisjunctionSpecification.specifications.add(wrappedSpec);
234             //} else {
235             newPurifiedDisjunctionSpecification.specifications.add(((AbstractSpecification<? super T>) wrappedSpec).purify(true));
236             //}
237         }
238         return newPurifiedDisjunctionSpecification;
239     }
240 
241 
242     @Override
243     protected Boolean isInvertible() {
244         // TODO: cleanup
245         /* This is just too ugly and ineffective...
246         try {
247             invert();
248             return TRUE;
249 
250         } catch (IllegalStateException e) {
251             return FALSE;
252         }
253         */
254         if (containsValueBoundSpecificationsOnly(this)) {
255             return TRUE;
256         }
257         return FALSE;
258     }
259 
260 
261     @Override
262     protected Specification<T> invert() {
263         /* NB!
264          * Using static imports in this method causes a javac bug to surface, probably:
265          * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6292765
266          * So don't use static imports here... until JDK7 that is, where the bug is fixed.
267          */
268         if (containsValueBoundSpecificationsOnly(this)) {
269             final Set<Specification<?>> specificationSet = this.getAllSpecifications();
270             Object value = null;
271             boolean hasEqualSpecification = false;
272             boolean hasLessThanSpecification = false;
273             boolean hasGreaterThanSpecification = false;
274             for (Specification specification : specificationSet) {
275                 if (specification == this) {
276                     continue;
277                 }
278                 if (specification instanceof EqualSpecification) {
279                     hasEqualSpecification = true;
280                     if (value == null) {
281                         value = ((EqualSpecification) specification).getValue();
282                     } else {
283                         if (!value.equals(((EqualSpecification) specification).getValue())) {
284                             //throw new IllegalStateException("This specification cannot be inverted");
285                             return doBooleanAlgebraInversion();
286                         } else {
287                             if (hasGreaterThanSpecification) {
288                                 return AbstractSpecification.createValueBoundSpecification(RelationalOperator.LESS_THAN, value);
289                             } else if (hasLessThanSpecification) {
290                                 return AbstractSpecification.createValueBoundSpecification(RelationalOperator.GREATER_THAN, value);
291                             }
292 
293                         }
294                     }
295                 } else if (specification instanceof GreaterThanSpecification) {
296                     hasGreaterThanSpecification = true;
297                     if (value == null) {
298                         value = ((GreaterThanSpecification) specification).getValue();
299                     } else {
300                         if (!value.equals(((GreaterThanSpecification) specification).getValue())) {
301                             //throw new IllegalStateException("This specification cannot be inverted");
302                             return doBooleanAlgebraInversion();
303                         } else {
304                             if (hasEqualSpecification) {
305                                 return AbstractSpecification.createValueBoundSpecification(RelationalOperator.LESS_THAN, value);
306                             }
307                         }
308                     }
309                 } else if (specification instanceof LessThanSpecification) {
310                     hasLessThanSpecification = true;
311                     if (value == null) {
312                         value = ((LessThanSpecification) specification).getValue();
313                     } else {
314                         if (!value.equals(((LessThanSpecification) specification).getValue())) {
315                             //throw new IllegalStateException("This specification cannot be inverted");
316                             return doBooleanAlgebraInversion();
317                         } else {
318                             if (hasEqualSpecification) {
319                                 return AbstractSpecification.createValueBoundSpecification(RelationalOperator.GREATER_THAN, value);
320                             }
321                         }
322                     }
323                 }
324             }
325         }
326         throw new IllegalStateException("This specification cannot be inverted");
327     }
328 
329 
330     private Specification<T> doBooleanAlgebraInversion() {
331         final ConjunctionSpecification<T> conjunctionSpecification = new ConjunctionSpecification<T>(getType());
332         for (final Specification<?> specification : this.getAllSpecifications()) {
333             conjunctionSpecification.specifications.add(((AbstractSpecification<? super T>) specification).invert());
334         }
335         return conjunctionSpecification;
336     }
337 }