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.Set;
20  
21  import org.apache.commons.lang.NotImplementedException;
22  import org.apache.commons.lang.Validate;
23  import org.apache.commons.lang.builder.ReflectionToStringBuilder;
24  
25  import net.sourceforge.domian.util.ReflectionUtils;
26  
27  import static java.lang.Boolean.FALSE;
28  import static java.lang.Boolean.TRUE;
29  
30  
31  /**
32   * All classes (belonging to this package) implementing {@link Specification}
33   * or one of its sub-interfaces, should extend this class.
34   *
35   * @author Eirik Torske
36   * @since 0.3
37   */
38  abstract class AbstractSpecification<T> implements Specification<T> {
39  
40      /** The type of the class this {@link Specification} is actually specifying. */
41      protected volatile Class<T> type;
42  
43  
44      @SuppressWarnings("unchecked")
45      private synchronized Class<T> createType() {
46          /* NB!
47           * Using static imports in this method causes this bug to surface for some strange reason:
48           * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6292765
49           * So don't use static imports here... until JDK7 that is, where this particular bug is fixed.
50           */
51          if (this.type == null) {
52              /* @SuppressWarnings("unchecked") -> Just apply proper type parameters, and you'll be safe */
53              this.type = (Class<T>) ReflectionUtils.getTypeArguments(AbstractSpecification.class, getClass()).get(0);
54              return this.type;
55          }
56          throw new IllegalStateException("This method should not be invoked unless type is null");
57      }
58  
59  
60      @Override
61      public Class<T> getType() {
62          return this.type == null ? createType() : this.type;
63      }
64  
65  
66      @Override
67      public Boolean isGeneralizationOf(final Specification<? extends T> specification) {
68          //throw new UnsupportedOperationException();
69  
70          Validate.notNull(specification, "Specification parameter cannot be null");
71          return this.equals(specification);
72      }
73  
74  
75      @Override
76      public Boolean isSpecialCaseOf(final Specification<? super T> specification) {
77          //throw new UnsupportedOperationException();
78  
79          Validate.notNull(specification, "Specification parameter cannot be null");
80          return this.equals(specification);
81      }
82  
83  
84      @Override
85      public Boolean isDisjointWith(final Specification<?> specification) {
86          Validate.notNull(specification, "Specification parameter cannot be null");
87          if (specification instanceof JointDenialSpecification) {
88              return specification.isDisjointWith(this);
89          }
90          return (this == specification || this.equals(specification)) ? FALSE : TRUE;
91      }
92  
93  
94      // TODO: consider including this in Specification interface
95      // TODO: test it
96      // TODO: type parameterization
97      public Boolean isIntersectionOf(final Specification specification) {
98          Validate.notNull(specification, "Specification parameter cannot be null");
99          //return !this.isGeneralizationOf(specification) && !this.isSpecialCaseOf(specification) && !this.isDisjointWith(specification);
100         throw new NotImplementedException();
101     }
102 
103 
104     // TODO: consider including this in Specification interface
105     // TODO: test it
106     public Boolean intersectsWith(final Specification specification) {
107         return isIntersectionOf(specification);
108     }
109 
110 
111     @Override
112     public <F> CompositeSpecification<T> where(final String accessibleObjectName, final Specification<F> accessibleObjectSpecification) {
113         throw new UnsupportedOperationException("'where' clause is applicable only for CompositeSpecification instances");
114     }
115 
116 
117     @Override
118     public CompositeSpecification<T> and(final Specification<? super T> otherSpecification) {
119         /* NB!
120          * Using static imports in this method causes this bug to surface for some strange reason:
121          * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6292765
122          * So don't use static imports here... until JDK7 that is, where this particular bug is fixed.
123          */
124         Validate.notNull(otherSpecification, "Specification parameter cannot be null");
125         if (!ReflectionUtils.canCastAtLeastOneWay(this.getType(), otherSpecification.getType())) {
126             throw new IllegalArgumentException("Cannot create conjunction of a Specification<" + this.getType().getName() + "> and a Specification<" + otherSpecification.getType().getName() + ">");
127         }
128         if (this.isDisjointWith(otherSpecification)) {
129             throw new IllegalArgumentException("Cannot create a conjunction out of two disjoint specifications");
130         }
131         return (CompositeSpecification<T>) SpecificationFactory.allOf(this, (Specification<T>) otherSpecification);
132     }
133 
134 
135     @Override
136     public CompositeSpecification<T> or(final Specification<? super T> otherSpecification) {
137         /* NB!
138          * Using static imports in this method causes this bug to surface for some strange reason:
139          * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6292765
140          * So don't use static imports here... until JDK7 that is, where this particular bug is fixed.
141          */
142         return (CompositeSpecification<T>) SpecificationFactory.eitherOf(this, otherSpecification);
143     }
144 
145 
146     // To be considered - but hides IDE 'override' marker :-\
147     //abstract public int hashCode();
148 
149 
150     // To be considered - but hides IDE 'override' marker :-\
151     //abstract public boolean equals(Object otherObject);
152 
153 
154     /** @return <code>true</code> if at least one of this specification's composites is a <i>conjunction</i> */
155     protected Boolean hasConjunction() {
156         return FALSE;
157     }
158 
159 
160     /** @return <code>true</code> if at least one of this specification's composites is a <i>disjunction</i> */
161     protected Boolean hasDisjunction() {
162         return FALSE;
163     }
164 
165 
166     /**
167      * Populates the given set of specifications with all {@link Specification} objects in this object graph.
168      * In other words, the {@link Set} is a <i>flattened</i>, <i>unordered</i> version of the {@link Specification} object graph.
169      *
170      * @param specificationSet Set of specifications
171      * @throws IllegalArgumentException if parameter is null
172      */
173     protected void getAllSpecifications(final Set<Specification<?>> specificationSet) {
174         Validate.notNull(specificationSet, "Set of specifications parameter cannot be null");
175         specificationSet.add(this);
176     }
177 
178 
179     /**
180      * Flag to indicate whether or not this specification has been through a purification routine,
181      * meaning the {@code purify()} method has been invoked.}
182      */
183     // TODO: cleanup
184     //@Deprecated
185     //protected AtomicBoolean purified = new AtomicBoolean(FALSE);
186 
187     /*
188     //@Deprecated // Does not compile in Maven...!?
189     protected boolean isPurified() {
190         //return this.purified.get();
191         return true;
192     }
193 
194     //@Deprecated // Does not compile in Maven...!?
195     protected void setPurified(final boolean purificationFlag) {
196         //this.purified.set(purificationFlag);
197     }
198     */
199 
200     /**
201      * <i>Purify</i> this specification.
202      * <p/>
203      * This may imply both simplification of the specification object graph,
204      * as well as applying boolean algebra on the overall logical expression.
205      *
206      * @param doPurifyInversions if {@code false} leave inversions alone (they must be purified only when spec creation is completed)
207      * @return a purified specification
208      */
209     protected AbstractSpecification<T> purify(final boolean doPurifyInversions) {
210         //setPurified(true);
211         return this;
212     }
213 
214     /*
215     protected AbstractSpecification<T> purify() {
216         setPurified(true);
217         return this;
218     }
219     */
220 
221     protected Boolean isInvertible() {
222         return FALSE;
223     }
224 
225     protected Specification<T> invert() {
226         throw new UnsupportedOperationException();
227     }
228 
229 
230     protected static <T> Boolean containsValueBoundSpecificationsOnly(final AbstractCompositeSpecification<T> compositeSpecification) {
231         if (compositeSpecification.specifications.isEmpty()) {
232             return FALSE;
233         }
234         for (final Specification<?> spec : compositeSpecification.specifications) {
235             if (!(spec instanceof ValueBoundSpecification)) {
236                 return FALSE;
237             }
238         }
239         return TRUE;
240     }
241 
242 
243     protected <T> Boolean containsSimpleCompositesOnly(final AbstractCompositeSpecification<T> compositeSpecification) {
244         if (compositeSpecification.specifications.isEmpty()) {
245             return FALSE;
246         }
247         for (final Specification<?> spec : compositeSpecification.specifications) {
248             if (spec instanceof CompositeSpecification) {
249                 final AbstractCompositeSpecification wrappedCompositeSpecification = (AbstractCompositeSpecification) spec;
250                 if (!wrappedCompositeSpecification.isSimpleComposition()) {
251                     return FALSE;
252                 }
253             } else if (spec instanceof ParameterizedSpecification) {
254                 return FALSE;
255             }
256         }
257         return TRUE;
258     }
259 
260 
261 
262     // TODO: add generics for this one
263     protected static Specification createValueBoundSpecification(final RelationalOperator relationalOperator, final Object operandValue) {
264         /* NB!
265          * Using static imports in this method causes this bug to surface for some strange reason:
266          * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6292765
267          * So don't use static imports here... until JDK7 that is, where this particular bug is fixed.
268          */
269         switch (relationalOperator) {
270             case EQUAL:
271                 return SpecificationFactory.equalTo(operandValue);
272 
273             case NOT_EQUAL:
274                 return SpecificationFactory.not(SpecificationFactory.equalTo(operandValue));
275 
276             case LESS_THAN:
277                 if (operandValue instanceof Comparable) {
278                     return SpecificationFactory.lessThan((Comparable) operandValue);
279                 } else {
280                     throw new IllegalArgumentException("Cannot create a LessThanSpecification with non-Comparable value");
281                 }
282 
283             case LESS_THAN_OR_EQUAL:
284                 if (operandValue instanceof Comparable) {
285                     return SpecificationFactory.lessThanOrEqualTo((Comparable) operandValue);
286                 } else {
287                     throw new IllegalArgumentException("Cannot create a LessThanSpecification with non-Comparable value");
288                 }
289 
290             case GREATER_THAN:
291                 if (operandValue instanceof Comparable) {
292                     return SpecificationFactory.greaterThan((Comparable) operandValue);
293                 } else {
294                     throw new IllegalArgumentException("Cannot create a LessThanSpecification with non-Comparable value");
295                 }
296 
297             case GREATER_THAN_OR_EQUAL:
298                 if (operandValue instanceof Comparable) {
299                     return SpecificationFactory.greaterThanOrEqualTo((Comparable) operandValue);
300                 } else {
301                     throw new IllegalArgumentException("Cannot create a LessThanSpecification with non-Comparable value");
302                 }
303         }
304         throw new NotImplementedException();
305     }
306 
307 
308     protected static int getNumberOfLevelsOfNegations(final JointDenialSpecification jointDenialSpecification, int levelIndex) {
309         final Set<Specification<?>> subSpecs = jointDenialSpecification.specifications;
310         if (subSpecs.size() != 1) {
311             throw new IllegalStateException();
312         }
313         final Specification subSpec = subSpecs.iterator().next();
314         ++levelIndex;
315         if (subSpec instanceof JointDenialSpecification) {
316             return getNumberOfLevelsOfNegations((JointDenialSpecification) subSpec, levelIndex);
317         } else {
318             return levelIndex;
319         }
320     }
321 
322 
323     protected static <V> Specification<V> getNegatedSpecification(final JointDenialSpecification<V> jointDenialSpecification) {
324         final Set<Specification<? super V>> subSpecs = jointDenialSpecification.specifications;
325         final Specification<? super V> subSpec = subSpecs.iterator().next();
326         if (subSpecs.size() != 1) {
327             throw new IllegalStateException();
328         }
329         if (subSpec instanceof JointDenialSpecification) {
330             return (Specification<V>) getNegatedSpecification((JointDenialSpecification<? super V>) subSpec);
331         } else {
332             return (Specification<V>) subSpec;
333         }
334     }
335 
336 
337     protected static boolean isOddNumber(final int number) {
338         return number % 2 != 0;
339     }
340 
341 
342     @Override
343     public String toString() {
344         return new ReflectionToStringBuilder(this).toString();
345     }
346 }