View Javadoc
1   /*******************************************************************************
2    * Portions created by Sebastian Thomschke are copyright (c) 2005-2015 Sebastian
3    * Thomschke.
4    *
5    * All Rights Reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    * http://www.eclipse.org/legal/epl-v10.html
9    *
10   * Contributors:
11   *     Sebastian Thomschke - initial implementation.
12   *     Chris Pheby - interface based method parameter validation (inspectInterfaces)
13   *******************************************************************************/
14  package net.sf.oval.configuration.annotation;
15  
16  import static net.sf.oval.Validator.*;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.LinkedHashSet;
24  import java.util.List;
25  import java.util.Set;
26  
27  import net.sf.oval.Check;
28  import net.sf.oval.CheckExclusion;
29  import net.sf.oval.collection.CollectionFactory;
30  import net.sf.oval.configuration.CheckInitializationListener;
31  import net.sf.oval.configuration.Configurer;
32  import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
33  import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
34  import net.sf.oval.configuration.pojo.elements.ConstructorConfiguration;
35  import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
36  import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
37  import net.sf.oval.configuration.pojo.elements.MethodPostExecutionConfiguration;
38  import net.sf.oval.configuration.pojo.elements.MethodPreExecutionConfiguration;
39  import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration;
40  import net.sf.oval.configuration.pojo.elements.ObjectConfiguration;
41  import net.sf.oval.configuration.pojo.elements.ParameterConfiguration;
42  import net.sf.oval.constraint.ConstraintsCheck;
43  import net.sf.oval.exception.OValException;
44  import net.sf.oval.exception.ReflectionException;
45  import net.sf.oval.guard.Guarded;
46  import net.sf.oval.guard.Post;
47  import net.sf.oval.guard.PostCheck;
48  import net.sf.oval.guard.PostValidateThis;
49  import net.sf.oval.guard.Pre;
50  import net.sf.oval.guard.PreCheck;
51  import net.sf.oval.guard.PreValidateThis;
52  import net.sf.oval.internal.util.Assert;
53  import net.sf.oval.internal.util.ReflectionUtils;
54  
55  /**
56   * Configurer that configures constraints based on annotations tagged with {@link Constraint}
57   *
58   * @author Sebastian Thomschke
59   */
60  public class AnnotationsConfigurer implements Configurer
61  {
62  	protected final Set<CheckInitializationListener> listeners = new LinkedHashSet<CheckInitializationListener>(2);
63  
64  	private List<ParameterConfiguration> _createParameterConfiguration(final Annotation[][] paramAnnotations,
65  			final Class< ? >[] parameterTypes)
66  	{
67  		final CollectionFactory cf = getCollectionFactory();
68  
69  		final List<ParameterConfiguration> paramCfg = cf.createList(paramAnnotations.length);
70  
71  		List<Check> paramChecks = cf.createList(2);
72  		List<CheckExclusion> paramCheckExclusions = cf.createList(2);
73  
74  		// loop over all parameters of the current constructor
75  		for (int i = 0; i < paramAnnotations.length; i++)
76  		{
77  			// loop over all annotations of the current constructor parameter
78  			for (final Annotation annotation : paramAnnotations[i])
79  				// check if the current annotation is a constraint annotation
80  				if (annotation.annotationType().isAnnotationPresent(Constraint.class))
81  				{
82  					paramChecks.add(initializeCheck(annotation));
83  				}
84  				else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
85  				{
86  					initializeChecks(annotation, paramChecks);
87  				}
88  				else if (annotation.annotationType().isAnnotationPresent(Exclusion.class))
89  				{
90  					paramCheckExclusions.add(initializeExclusion(annotation));
91  				}
92  
93  			final ParameterConfiguration pc = new ParameterConfiguration();
94  			paramCfg.add(pc);
95  			pc.type = parameterTypes[i];
96  			if (paramChecks.size() > 0)
97  			{
98  				pc.checks = paramChecks;
99  				paramChecks = cf.createList(2); // create a new list for the next parameter having checks
100 			}
101 			if (paramCheckExclusions.size() > 0)
102 			{
103 				pc.checkExclusions = paramCheckExclusions;
104 				paramCheckExclusions = cf.createList(2); // create a new list for the next parameter having check exclusions
105 			}
106 		}
107 		return paramCfg;
108 	}
109 
110 	public boolean addCheckInitializationListener(final CheckInitializationListener listener)
111 	{
112 		Assert.argumentNotNull("listener", "[listener] must not be null");
113 		return listeners.add(listener);
114 	}
115 
116 	protected void configureConstructorParameterChecks(final ClassConfiguration classCfg)
117 	{
118 		final CollectionFactory cf = getCollectionFactory();
119 
120 		for (final Constructor< ? > ctor : classCfg.type.getDeclaredConstructors())
121 		{
122 			final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(ctor.getParameterAnnotations(),
123 					ctor.getParameterTypes());
124 
125 			final boolean postValidateThis = ctor.isAnnotationPresent(PostValidateThis.class);
126 
127 			if (paramCfg.size() > 0 || postValidateThis)
128 			{
129 				if (classCfg.constructorConfigurations == null)
130 				{
131 					classCfg.constructorConfigurations = cf.createSet(2);
132 				}
133 
134 				final ConstructorConfiguration cc = new ConstructorConfiguration();
135 				cc.parameterConfigurations = paramCfg;
136 				cc.postCheckInvariants = postValidateThis;
137 				classCfg.constructorConfigurations.add(cc);
138 			}
139 		}
140 	}
141 
142 	protected void configureFieldChecks(final ClassConfiguration classCfg)
143 	{
144 		final CollectionFactory cf = getCollectionFactory();
145 
146 		List<Check> checks = cf.createList(2);
147 
148 		for (final Field field : classCfg.type.getDeclaredFields())
149 		{
150 			// loop over all annotations of the current field
151 			for (final Annotation annotation : field.getAnnotations())
152 				// check if the current annotation is a constraint annotation
153 				if (annotation.annotationType().isAnnotationPresent(Constraint.class))
154 				{
155 					checks.add(initializeCheck(annotation));
156 				}
157 				else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
158 				{
159 					initializeChecks(annotation, checks);
160 				}
161 
162 			if (checks.size() > 0)
163 			{
164 				if (classCfg.fieldConfigurations == null)
165 				{
166 					classCfg.fieldConfigurations = cf.createSet(2);
167 				}
168 
169 				final FieldConfiguration fc = new FieldConfiguration();
170 				fc.name = field.getName();
171 				fc.checks = checks;
172 				classCfg.fieldConfigurations.add(fc);
173 				checks = cf.createList(2); // create a new list for the next field with checks
174 			}
175 		}
176 	}
177 
178 	/**
179 	 * configure method return value and parameter checks
180 	 */
181 	protected void configureMethodChecks(final ClassConfiguration classCfg)
182 	{
183 		final CollectionFactory cf = getCollectionFactory();
184 
185 		List<Check> returnValueChecks = cf.createList(2);
186 		List<PreCheck> preChecks = cf.createList(2);
187 		List<PostCheck> postChecks = cf.createList(2);
188 
189 		for (final Method method : classCfg.type.getDeclaredMethods())
190 		{
191 			/*
192 			 * determine method return value checks and method pre/post
193 			 * conditions
194 			 */
195 			boolean preValidateThis = false;
196 			boolean postValidateThis = false;
197 
198 			// loop over all annotations
199 			for (final Annotation annotation : ReflectionUtils.getAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)))
200 				if (annotation instanceof Pre)
201 				{
202 					final PreCheck pc = new PreCheck();
203 					pc.configure((Pre) annotation);
204 					preChecks.add(pc);
205 				}
206 				else if (annotation instanceof PreValidateThis)
207 				{
208 					preValidateThis = true;
209 				}
210 				else if (annotation instanceof Post)
211 				{
212 					final PostCheck pc = new PostCheck();
213 					pc.configure((Post) annotation);
214 					postChecks.add(pc);
215 				}
216 				else if (annotation instanceof PostValidateThis)
217 				{
218 					postValidateThis = true;
219 				}
220 				else if (annotation.annotationType().isAnnotationPresent(Constraint.class))
221 				{
222 					returnValueChecks.add(initializeCheck(annotation));
223 				}
224 				else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
225 				{
226 					initializeChecks(annotation, returnValueChecks);
227 				}
228 
229 			/*
230 			 * determine parameter checks
231 			 */
232 			final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(
233 					ReflectionUtils.getParameterAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)),
234 					method.getParameterTypes());
235 
236 			// check if anything has been configured for this method at all
237 			if (paramCfg.size() > 0 || returnValueChecks.size() > 0 || preChecks.size() > 0 || postChecks.size() > 0 || preValidateThis
238 					|| postValidateThis)
239 			{
240 				if (classCfg.methodConfigurations == null)
241 				{
242 					classCfg.methodConfigurations = cf.createSet(2);
243 				}
244 
245 				final MethodConfiguration mc = new MethodConfiguration();
246 				mc.name = method.getName();
247 				mc.parameterConfigurations = paramCfg;
248 				mc.isInvariant = ReflectionUtils.isAnnotationPresent(method, IsInvariant.class,
249 						Boolean.TRUE.equals(classCfg.inspectInterfaces));
250 				mc.preCheckInvariants = preValidateThis;
251 				mc.postCheckInvariants = postValidateThis;
252 				if (returnValueChecks.size() > 0)
253 				{
254 					mc.returnValueConfiguration = new MethodReturnValueConfiguration();
255 					mc.returnValueConfiguration.checks = returnValueChecks;
256 					returnValueChecks = cf.createList(2); // create a new list for the next method having return value checks
257 				}
258 				if (preChecks.size() > 0)
259 				{
260 					mc.preExecutionConfiguration = new MethodPreExecutionConfiguration();
261 					mc.preExecutionConfiguration.checks = preChecks;
262 					preChecks = cf.createList(2); // create a new list for the next method having pre checks
263 				}
264 				if (postChecks.size() > 0)
265 				{
266 					mc.postExecutionConfiguration = new MethodPostExecutionConfiguration();
267 					mc.postExecutionConfiguration.checks = postChecks;
268 					postChecks = cf.createList(2); // create a new list for the next method having post checks
269 				}
270 				classCfg.methodConfigurations.add(mc);
271 			}
272 		}
273 	}
274 
275 	protected void configureObjectLevelChecks(final ClassConfiguration classCfg)
276 	{
277 		final List<Check> checks = getCollectionFactory().createList(2);
278 
279 		for (final Annotation annotation : ReflectionUtils.getAnnotations(classCfg.type, Boolean.TRUE.equals(classCfg.inspectInterfaces)))
280 			// check if the current annotation is a constraint annotation
281 			if (annotation.annotationType().isAnnotationPresent(Constraint.class))
282 			{
283 				checks.add(initializeCheck(annotation));
284 			}
285 			else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
286 			{
287 				initializeChecks(annotation, checks);
288 			}
289 
290 		if (checks.size() > 0)
291 		{
292 			classCfg.objectConfiguration = new ObjectConfiguration();
293 			classCfg.objectConfiguration.checks = checks;
294 		}
295 	}
296 
297 	public ClassConfiguration getClassConfiguration(final Class< ? > clazz)
298 	{
299 		final ClassConfiguration classCfg = new ClassConfiguration();
300 		classCfg.type = clazz;
301 
302 		final Guarded guarded = clazz.getAnnotation(Guarded.class);
303 
304 		if (guarded == null)
305 		{
306 			classCfg.applyFieldConstraintsToConstructors = false;
307 			classCfg.applyFieldConstraintsToSetters = false;
308 			classCfg.assertParametersNotNull = false;
309 			classCfg.checkInvariants = false;
310 			classCfg.inspectInterfaces = false;
311 		}
312 		else
313 		{
314 			classCfg.applyFieldConstraintsToConstructors = guarded.applyFieldConstraintsToConstructors();
315 			classCfg.applyFieldConstraintsToSetters = guarded.applyFieldConstraintsToSetters();
316 			classCfg.assertParametersNotNull = guarded.assertParametersNotNull();
317 			classCfg.checkInvariants = guarded.checkInvariants();
318 			classCfg.inspectInterfaces = guarded.inspectInterfaces();
319 		}
320 
321 		configureObjectLevelChecks(classCfg);
322 		configureFieldChecks(classCfg);
323 		configureConstructorParameterChecks(classCfg);
324 		configureMethodChecks(classCfg);
325 
326 		return classCfg;
327 	}
328 
329 	public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId)
330 	{
331 		return null;
332 	}
333 
334 	protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> initializeCheck(
335 			final ConstraintAnnotation constraintAnnotation) throws ReflectionException
336 	{
337 		assert constraintAnnotation != null;
338 
339 		final Constraint constraint = constraintAnnotation.annotationType().getAnnotation(Constraint.class);
340 
341 		// determine the check class
342 		@SuppressWarnings("unchecked")
343 		final Class<AnnotationCheck<ConstraintAnnotation>> checkClass = (Class<AnnotationCheck<ConstraintAnnotation>>) constraint
344 				.checkWith();
345 
346 		// instantiate the appropriate check for the found constraint
347 		final AnnotationCheck<ConstraintAnnotation> check = newCheckInstance(checkClass);
348 		check.configure(constraintAnnotation);
349 
350 		for (final CheckInitializationListener listener : listeners)
351 		{
352 			listener.onCheckInitialized(check);
353 		}
354 		return check;
355 	}
356 
357 	/**
358 	 * handles list of annotations like @Assert.List(...)
359 	 */
360 	protected <ConstraintsAnnotation extends Annotation> void initializeChecks(final ConstraintsAnnotation constraintsAnnotation,
361 			final List<Check> checks) throws ReflectionException
362 	{
363 		try
364 		{
365 			final Method getValue = constraintsAnnotation.annotationType().getDeclaredMethod("value", (Class< ? >[]) null);
366 			final Object[] constraintAnnotations = (Object[]) getValue.invoke(constraintsAnnotation, (Object[]) null);
367 
368 			final ConstraintsCheck constraintsCheck = new ConstraintsCheck();
369 			constraintsCheck.configure(constraintsAnnotation);
370 			constraintsCheck.checks = new ArrayList<Check>(constraintAnnotations.length);
371 			for (final Object ca : constraintAnnotations)
372 			{
373 				constraintsCheck.checks.add(initializeCheck((Annotation) ca));
374 			}
375 			checks.add(constraintsCheck);
376 		}
377 		catch (final ReflectionException ex)
378 		{
379 			throw ex;
380 		}
381 		catch (final Exception ex)
382 		{
383 			throw new ReflectionException("Cannot initialize constraint check " + constraintsAnnotation.annotationType().getName(), ex);
384 		}
385 	}
386 
387 	protected <ExclusionAnnotation extends Annotation> AnnotationCheckExclusion<ExclusionAnnotation> initializeExclusion(
388 			final ExclusionAnnotation exclusionAnnotation) throws ReflectionException
389 	{
390 		assert exclusionAnnotation != null;
391 
392 		final Exclusion constraint = exclusionAnnotation.annotationType().getAnnotation(Exclusion.class);
393 
394 		// determine the check class
395 		final Class< ? > exclusionClass = constraint.excludeWith();
396 
397 		try
398 		{
399 			// instantiate the appropriate exclusion for the found annotation
400 			@SuppressWarnings("unchecked")
401 			final AnnotationCheckExclusion<ExclusionAnnotation> exclusion = (AnnotationCheckExclusion<ExclusionAnnotation>) exclusionClass
402 					.newInstance();
403 			exclusion.configure(exclusionAnnotation);
404 			return exclusion;
405 		}
406 		catch (final Exception ex)
407 		{
408 			throw new ReflectionException("Cannot initialize constraint exclusion " + exclusionClass.getName(), ex);
409 		}
410 	}
411 
412 	/**
413 	 * @return a new instance of the given constraint check implementation class
414 	 */
415 	protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> newCheckInstance(
416 			final Class<AnnotationCheck<ConstraintAnnotation>> checkClass) throws OValException
417 	{
418 		try
419 		{
420 			return checkClass.newInstance();
421 		}
422 		catch (final InstantiationException ex)
423 		{
424 			throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex);
425 		}
426 		catch (final IllegalAccessException ex)
427 		{
428 			throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex);
429 		}
430 	}
431 
432 	public boolean removeCheckInitializationListener(final CheckInitializationListener listener)
433 	{
434 		return listeners.remove(listener);
435 	}
436 }