1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
49
50
51
52
53
54
55
56 abstract class ParameterizedSpecification<T, F> extends AbstractSpecification<T> implements LeafSpecification<T> {
57
58
59
60
61
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
72 final Field field = getFieldByName(accessibleObjectName, declaringClass);
73 if (field != null) {
74 final Type fieldGenericType = field.getGenericType();
75 if (accessibleObjectSpecification instanceof CollectionSpecification) {
76
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
81 } else if (!(fieldGenericType instanceof ParameterizedType)
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
96 final Method method = getMethodByNameWithPossiblePrefix(accessibleObjectName, declaringClass, asList("get", "is"));
97 if (method != null && accessibleObjectSpecification instanceof CollectionSpecification) {
98 final Type methodReturnType = method.getReturnType();
99
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
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
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
195 if (this == specification || this.equals(specification)) {
196 return FALSE;
197 }
198
199 if (!canCastAtLeastOneWay(this.getType(), specification.getType())) {
200 return TRUE;
201 }
202 if (specification instanceof ParameterizedSpecification) {
203
204 if (!canCastAtLeastOneWay(this.getAccessibleObjectSpecificationType(), ((ParameterizedSpecification) specification).getAccessibleObjectSpecificationType())) {
205 return TRUE;
206 }
207
208 if (this.getAccessibleObjectSpecification().isDisjointWith(((ParameterizedSpecification) specification).getAccessibleObjectSpecification())) {
209 return TRUE;
210 }
211 }
212
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 }