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.Method;
22  import java.lang.reflect.ParameterizedType;
23  import java.lang.reflect.Type;
24  import java.security.PrivilegedAction;
25  import java.util.Collection;
26  import java.util.Set;
27  
28  import org.apache.commons.lang.NotImplementedException;
29  import org.apache.commons.lang.Validate;
30  import org.apache.commons.lang.builder.EqualsBuilder;
31  import org.apache.commons.lang.builder.HashCodeBuilder;
32  
33  import static java.lang.Boolean.FALSE;
34  import static java.lang.Boolean.TRUE;
35  import static java.security.AccessController.doPrivileged;
36  import static java.util.Arrays.asList;
37  import static net.sourceforge.domian.specification.SpecificationFactory.all;
38  import static net.sourceforge.domian.specification.SpecificationFactory.isA;
39  import static net.sourceforge.domian.util.ReflectionUtils.canCastAtLeastOneWay;
40  import static net.sourceforge.domian.util.ReflectionUtils.canCastFrom_To;
41  import static net.sourceforge.domian.util.ReflectionUtils.getFieldByName;
42  import static net.sourceforge.domian.util.ReflectionUtils.getMethodByNameWithPossiblePrefix;
43  import static org.apache.commons.lang.StringUtils.isBlank;
44  import static org.apache.commons.lang.StringUtils.isNumeric;
45  
46  
47  /**
48   * A parameterized specification deals, as opposed to <i>value bound specifications</i>, also with <i>value context</i>.
49   * In this specification, that context is a class method, or a class field.
50   *
51   * @author Eirik Torske
52   * @since 0.1
53   */
54  // TODO: rename to AbstractAccessibleObjectSpecification ?
55  // TODO: rename to AbstractAccessibleObjectParameterizedSpecification ? 
56  abstract class ParameterizedSpecification<T, F> extends AbstractSpecification<T> implements LeafSpecification<T> {
57  
58      /**
59       * Rather ugly factory method for creating parameterized specifications...
60       *
61       * @throws SecurityException if a security manager exists and if the caller does not have granted java.lang.reflect.ReflectPermission.suppressAccessChecks
62       */
63      static <T, F> ParameterizedSpecification<T, F> createParameterizedSpecification(final Class<T> declaringClass,
64                                                                                      final String accessibleObjectName,
65                                                                                      final Specification<F> accessibleObjectSpecification) {
66          Validate.notNull(declaringClass, "Declaring class parameter cannot be null");
67          Validate.notNull(accessibleObjectName, "Accessible object name parameter cannot be null");
68          Validate.isTrue(isValidFieldName(accessibleObjectName), "Accessible object name parameter \"" + accessibleObjectName + "\" is neither a valid method name, nor a valid field name");
69          Validate.notNull(accessibleObjectSpecification, "Accessible object specification parameter cannot be null");
70  
71          // This field extraction verifies if the field name exists in parameterized type T
72          final Field field = getFieldByName(accessibleObjectName, declaringClass);
73          if (field != null) {
74              final Type fieldGenericType = field.getGenericType();
75              if (accessibleObjectSpecification instanceof CollectionSpecification) {
76                  // This check verifies that field is in fact a java.util.Collection type
77                  if (!(fieldGenericType instanceof ParameterizedType) && !canCastFrom_To((Class) fieldGenericType, Collection.class)) {
78                      throw new IllegalArgumentException("Field \"" + accessibleObjectName + "\" of type " + ((Class) fieldGenericType).getName() + " [in class " + field.getDeclaringClass().getName() + "], cannot be specified by a " + CollectionSpecification.class.getName());
79                  }
80                  // This check verifies that field specification type F is compatible with field type
81              } else if (!(fieldGenericType instanceof ParameterizedType) // TODO: this is an ugly workaround for the missing CollectionSpecification.getType()... help!
82                         && !canCastFrom_To((Class) field.getGenericType(), accessibleObjectSpecification.getType())) {
83                  throw new IllegalArgumentException("Field \"" + accessibleObjectName + "\" of type " + ((Class) fieldGenericType).getName() + " [in class " + declaringClass.getName() + "], cannot be specified by a Specification<" + accessibleObjectSpecification.getType().getName() + ">");
84              }
85              doPrivileged(new PrivilegedAction<Void>() {
86                  @Override
87                  public Void run() {
88                      field.setAccessible(true);
89                      return null;
90                  }
91              });
92              return new FieldParameterizedSpecification<T, F>(declaringClass, field, accessibleObjectSpecification);
93          }
94  
95          // These method extractions verifies if the name represents a method in parameterized type T
96          final Method method = getMethodByNameWithPossiblePrefix(accessibleObjectName, declaringClass, asList("get", "is"));
97          if (method != null && accessibleObjectSpecification instanceof CollectionSpecification) {
98              final Type methodReturnType = method.getReturnType();
99              // This check verifies that the method return type is in fact a java.util.Collection type
100             if (!canCastFrom_To((Class) methodReturnType, Collection.class)) {
101                 throw new IllegalArgumentException("Method \"" + method.getName() + "\" with return type " + method.getReturnType().getName() + " [in class " + declaringClass.getName() + "], cannot be specified by a " + CollectionSpecification.class.getName());
102             }
103             // This check verifies that field specification type F is compatible with field type
104         } else if (method != null && !canCastAtLeastOneWay(method.getReturnType(), accessibleObjectSpecification.getType())) {
105             throw new IllegalArgumentException("Method \"" + method.getName() + "\" with return type " + method.getReturnType().getName() + " [in class " + declaringClass + "], cannot be specified by a Specification<" + accessibleObjectSpecification.getType().getName() + ">");
106         }
107         if (method != null) {
108             doPrivileged(new PrivilegedAction<Void>() {
109                 @Override
110                 public Void run() {
111                     method.setAccessible(true);
112                     return null;
113                 }
114             });
115             return new MethodParameterizedSpecification<T, F>(declaringClass, method, accessibleObjectSpecification);
116 
117         } else {
118             throw new IllegalArgumentException("Neither a field nor a method (with no formal parameters) named \"" + accessibleObjectName + "\" found in " + declaringClass);
119         }
120     }
121 
122 
123     /** @return <code>true</code> if the field name does not start with '.',or a number */
124     protected static boolean isValidFieldName(final String fieldName) {
125         final char[] illegalFieldStartCharacters = new char[]{'.'};
126         if (isBlank(fieldName)) {
127             return false;
128         }
129         if (isNumeric(Character.toString(fieldName.charAt(0)))) {
130             return false;
131         }
132         for (final char illegalChar : illegalFieldStartCharacters) {
133             if (fieldName.charAt(0) == illegalChar) {
134                 return false;
135             }
136         }
137         return true;
138     }
139 
140 
141     protected final static Specification<ParameterizedSpecification> parameterizedSpecificationWithConjunctionSpec =
142             all(ParameterizedSpecification.class).where("accessibleObjectSpecification", isA(ConjunctionSpecification.class));
143 
144     protected final static Specification<ParameterizedSpecification> parameterizedSpecificationWithDisjunctionSpec =
145             all(ParameterizedSpecification.class).where("accessibleObjectSpecification", isA(DisjunctionSpecification.class));
146 
147 
148     protected AccessibleObject accessibleObject;
149     protected Specification<F> accessibleObjectSpecification;
150 
151 
152     public Class<T> getDeclaringClass() {
153         return getType();
154     }
155 
156     public AccessibleObject getAccessibleObject() {
157         return this.accessibleObject;
158     }
159 
160     public Specification<F> getAccessibleObjectSpecification() {
161         return this.accessibleObjectSpecification;
162     }
163 
164     public Class<F> getAccessibleObjectSpecificationType() {
165         return this.accessibleObjectSpecification.getType();
166     }
167 
168 
169     abstract String getName();
170 
171 
172     @Override
173     protected Boolean hasConjunction() {
174         return parameterizedSpecificationWithConjunctionSpec.isSatisfiedBy(this);
175     }
176 
177     @Override
178     protected Boolean hasDisjunction() {
179         return parameterizedSpecificationWithDisjunctionSpec.isSatisfiedBy(this);
180     }
181 
182     @Override
183     protected final void getAllSpecifications(final Set<Specification<?>> specificationSet) {
184         Validate.notNull(specificationSet, "Set of specifications parameter cannot be null");
185         Validate.isTrue(this.accessibleObjectSpecification instanceof AbstractSpecification, "Specification parameter is not an instance of AbstractSpecification");
186         specificationSet.add(this);
187         specificationSet.add(this.accessibleObjectSpecification);
188         ((AbstractSpecification) this.accessibleObjectSpecification).getAllSpecifications(specificationSet);
189     }
190 
191     @Override
192     public Boolean isDisjointWith(final Specification<?> specification) {
193         Validate.notNull(specification, "Specification parameter cannot be null");
194         // If same/equal specifications:
195         if (this == specification || this.equals(specification)) {
196             return FALSE;
197         }
198         // If specification types are not the same:
199         if (!canCastAtLeastOneWay(this.getType(), specification.getType())) {
200             return TRUE;
201         }
202         if (specification instanceof ParameterizedSpecification) {
203             // If accessible object specification types are not the same:
204             if (!canCastAtLeastOneWay(this.getAccessibleObjectSpecificationType(), ((ParameterizedSpecification) specification).getAccessibleObjectSpecificationType())) {
205                 return TRUE;
206             }
207             // If accessible object specifications are disjoint:
208             if (this.getAccessibleObjectSpecification().isDisjointWith(((ParameterizedSpecification) specification).getAccessibleObjectSpecification())) {
209                 return TRUE;
210             }
211         }
212         // Otherwise
213         return FALSE;
214     }
215 
216     @Override
217     public int hashCode() {
218         return new HashCodeBuilder(8473, 7293)
219                 .append(this.accessibleObject)
220                 .append(this.accessibleObjectSpecification)
221                 .toHashCode();
222     }
223 
224     @Override
225     public boolean equals(Object otherObject) {
226         if (otherObject == null) { return false; }
227         if (!(otherObject instanceof ParameterizedSpecification)) { return false; }
228         return new EqualsBuilder()
229                 .append(this.accessibleObject, ((ParameterizedSpecification) otherObject).accessibleObject)
230                 .append(this.accessibleObjectSpecification, ((ParameterizedSpecification) otherObject).accessibleObjectSpecification)
231                 .isEquals();
232     }
233 }