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