001 /******************************************************************************* 002 * Portions created by Sebastian Thomschke are copyright (c) 2005-2011 Sebastian 003 * Thomschke. 004 * 005 * All Rights Reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 * 010 * Contributors: 011 * Sebastian Thomschke - initial implementation. 012 * Chris Pheby - interface based method parameter validation (inspectInterfaces) 013 *******************************************************************************/ 014 package net.sf.oval.configuration.annotation; 015 016 import static net.sf.oval.Validator.getCollectionFactory; 017 018 import java.lang.annotation.Annotation; 019 import java.lang.reflect.Constructor; 020 import java.lang.reflect.Field; 021 import java.lang.reflect.Method; 022 import java.util.List; 023 import java.util.Set; 024 025 import net.sf.oval.Check; 026 import net.sf.oval.CheckExclusion; 027 import net.sf.oval.collection.CollectionFactory; 028 import net.sf.oval.configuration.CheckInitializationListener; 029 import net.sf.oval.configuration.Configurer; 030 import net.sf.oval.configuration.pojo.elements.ClassConfiguration; 031 import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration; 032 import net.sf.oval.configuration.pojo.elements.ConstructorConfiguration; 033 import net.sf.oval.configuration.pojo.elements.FieldConfiguration; 034 import net.sf.oval.configuration.pojo.elements.MethodConfiguration; 035 import net.sf.oval.configuration.pojo.elements.MethodPostExecutionConfiguration; 036 import net.sf.oval.configuration.pojo.elements.MethodPreExecutionConfiguration; 037 import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration; 038 import net.sf.oval.configuration.pojo.elements.ObjectConfiguration; 039 import net.sf.oval.configuration.pojo.elements.ParameterConfiguration; 040 import net.sf.oval.exception.OValException; 041 import net.sf.oval.exception.ReflectionException; 042 import net.sf.oval.guard.Guarded; 043 import net.sf.oval.guard.Post; 044 import net.sf.oval.guard.PostCheck; 045 import net.sf.oval.guard.PostValidateThis; 046 import net.sf.oval.guard.Pre; 047 import net.sf.oval.guard.PreCheck; 048 import net.sf.oval.guard.PreValidateThis; 049 import net.sf.oval.internal.util.Assert; 050 import net.sf.oval.internal.util.LinkedSet; 051 import net.sf.oval.internal.util.ReflectionUtils; 052 053 /** 054 * Configurer that configures constraints based on annotations tagged with {@link Constraint} 055 * 056 * @author Sebastian Thomschke 057 */ 058 public class AnnotationsConfigurer implements Configurer 059 { 060 protected final Set<CheckInitializationListener> listeners = new LinkedSet<CheckInitializationListener>(2); 061 062 private List<ParameterConfiguration> _createParameterConfiguration(final Annotation[][] paramAnnotations, 063 final Class< ? >[] parameterTypes) 064 { 065 final CollectionFactory cf = getCollectionFactory(); 066 067 final List<ParameterConfiguration> paramCfg = cf.createList(paramAnnotations.length); 068 069 List<Check> paramChecks = cf.createList(2); 070 List<CheckExclusion> paramCheckExclusions = cf.createList(2); 071 072 // loop over all parameters of the current constructor 073 for (int i = 0; i < paramAnnotations.length; i++) 074 { 075 // loop over all annotations of the current constructor parameter 076 for (final Annotation annotation : paramAnnotations[i]) 077 // check if the current annotation is a constraint annotation 078 if (annotation.annotationType().isAnnotationPresent(Constraint.class)) 079 paramChecks.add(initializeCheck(annotation)); 080 else if (annotation.annotationType().isAnnotationPresent(Constraints.class)) 081 initializeChecks(annotation, paramChecks); 082 else if (annotation.annotationType().isAnnotationPresent(Exclusion.class)) 083 paramCheckExclusions.add(initializeExclusion(annotation)); 084 085 final ParameterConfiguration pc = new ParameterConfiguration(); 086 paramCfg.add(pc); 087 pc.type = parameterTypes[i]; 088 if (paramChecks.size() > 0) 089 { 090 pc.checks = paramChecks; 091 paramChecks = cf.createList(2); // create a new list for the next parameter having checks 092 } 093 if (paramCheckExclusions.size() > 0) 094 { 095 pc.checkExclusions = paramCheckExclusions; 096 paramCheckExclusions = cf.createList(2); // create a new list for the next parameter having check exclusions 097 } 098 } 099 return paramCfg; 100 } 101 102 public boolean addCheckInitializationListener(final CheckInitializationListener listener) 103 { 104 Assert.argumentNotNull("listener", "[listener] must not be null"); 105 return listeners.add(listener); 106 } 107 108 protected void configureConstructorParameterChecks(final ClassConfiguration classCfg) 109 { 110 final CollectionFactory cf = getCollectionFactory(); 111 112 for (final Constructor< ? > ctor : classCfg.type.getDeclaredConstructors()) 113 { 114 final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(ctor.getParameterAnnotations(), 115 ctor.getParameterTypes()); 116 117 final boolean postValidateThis = ctor.isAnnotationPresent(PostValidateThis.class); 118 119 if (paramCfg.size() > 0 || postValidateThis) 120 { 121 if (classCfg.constructorConfigurations == null) classCfg.constructorConfigurations = cf.createSet(2); 122 123 final ConstructorConfiguration cc = new ConstructorConfiguration(); 124 cc.parameterConfigurations = paramCfg; 125 cc.postCheckInvariants = postValidateThis; 126 classCfg.constructorConfigurations.add(cc); 127 } 128 } 129 } 130 131 protected void configureFieldChecks(final ClassConfiguration classCfg) 132 { 133 final CollectionFactory cf = getCollectionFactory(); 134 135 List<Check> checks = cf.createList(2); 136 137 for (final Field field : classCfg.type.getDeclaredFields()) 138 { 139 // loop over all annotations of the current field 140 for (final Annotation annotation : field.getAnnotations()) 141 // check if the current annotation is a constraint annotation 142 if (annotation.annotationType().isAnnotationPresent(Constraint.class)) 143 checks.add(initializeCheck(annotation)); 144 else if (annotation.annotationType().isAnnotationPresent(Constraints.class)) 145 initializeChecks(annotation, checks); 146 147 if (checks.size() > 0) 148 { 149 if (classCfg.fieldConfigurations == null) classCfg.fieldConfigurations = cf.createSet(2); 150 151 final FieldConfiguration fc = new FieldConfiguration(); 152 fc.name = field.getName(); 153 fc.checks = checks; 154 classCfg.fieldConfigurations.add(fc); 155 checks = cf.createList(2); // create a new list for the next field with checks 156 } 157 } 158 } 159 160 /** 161 * configure method return value and parameter checks 162 */ 163 protected void configureMethodChecks(final ClassConfiguration classCfg) 164 { 165 final CollectionFactory cf = getCollectionFactory(); 166 167 List<Check> returnValueChecks = cf.createList(2); 168 List<PreCheck> preChecks = cf.createList(2); 169 List<PostCheck> postChecks = cf.createList(2); 170 171 for (final Method method : classCfg.type.getDeclaredMethods()) 172 { 173 /* 174 * determine method return value checks and method pre/post 175 * conditions 176 */ 177 boolean preValidateThis = false; 178 boolean postValidateThis = false; 179 180 // loop over all annotations 181 for (final Annotation annotation : ReflectionUtils.getAnnotations(method, 182 Boolean.TRUE.equals(classCfg.inspectInterfaces))) 183 if (annotation instanceof Pre) 184 { 185 final PreCheck pc = new PreCheck(); 186 pc.configure((Pre) annotation); 187 preChecks.add(pc); 188 } 189 else if (annotation instanceof PreValidateThis) 190 preValidateThis = true; 191 else if (annotation instanceof Post) 192 { 193 final PostCheck pc = new PostCheck(); 194 pc.configure((Post) annotation); 195 postChecks.add(pc); 196 } 197 else if (annotation instanceof PostValidateThis) 198 postValidateThis = true; 199 else if (annotation.annotationType().isAnnotationPresent(Constraint.class)) 200 returnValueChecks.add(initializeCheck(annotation)); 201 else if (annotation.annotationType().isAnnotationPresent(Constraints.class)) 202 initializeChecks(annotation, returnValueChecks); 203 204 /* 205 * determine parameter checks 206 */ 207 final List<ParameterConfiguration> paramCfg = _createParameterConfiguration( 208 ReflectionUtils.getParameterAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)), 209 method.getParameterTypes()); 210 211 // check if anything has been configured for this method at all 212 if (paramCfg.size() > 0 || returnValueChecks.size() > 0 || preChecks.size() > 0 || postChecks.size() > 0 213 || preValidateThis || postValidateThis) 214 { 215 if (classCfg.methodConfigurations == null) classCfg.methodConfigurations = cf.createSet(2); 216 217 final MethodConfiguration mc = new MethodConfiguration(); 218 mc.name = method.getName(); 219 mc.parameterConfigurations = paramCfg; 220 mc.isInvariant = ReflectionUtils.isAnnotationPresent(method, IsInvariant.class, 221 Boolean.TRUE.equals(classCfg.inspectInterfaces)); 222 mc.preCheckInvariants = preValidateThis; 223 mc.postCheckInvariants = postValidateThis; 224 if (returnValueChecks.size() > 0) 225 { 226 mc.returnValueConfiguration = new MethodReturnValueConfiguration(); 227 mc.returnValueConfiguration.checks = returnValueChecks; 228 returnValueChecks = cf.createList(2); // create a new list for the next method having return value checks 229 } 230 if (preChecks.size() > 0) 231 { 232 mc.preExecutionConfiguration = new MethodPreExecutionConfiguration(); 233 mc.preExecutionConfiguration.checks = preChecks; 234 preChecks = cf.createList(2); // create a new list for the next method having pre checks 235 } 236 if (postChecks.size() > 0) 237 { 238 mc.postExecutionConfiguration = new MethodPostExecutionConfiguration(); 239 mc.postExecutionConfiguration.checks = postChecks; 240 postChecks = cf.createList(2); // create a new list for the next method having post checks 241 } 242 classCfg.methodConfigurations.add(mc); 243 } 244 } 245 } 246 247 protected void configureObjectLevelChecks(final ClassConfiguration classCfg) 248 { 249 final List<Check> checks = getCollectionFactory().createList(2); 250 251 for (final Annotation annotation : ReflectionUtils.getAnnotations(classCfg.type, 252 Boolean.TRUE.equals(classCfg.inspectInterfaces))) 253 // check if the current annotation is a constraint annotation 254 if (annotation.annotationType().isAnnotationPresent(Constraint.class)) 255 checks.add(initializeCheck(annotation)); 256 else if (annotation.annotationType().isAnnotationPresent(Constraints.class)) 257 initializeChecks(annotation, checks); 258 259 if (checks.size() > 0) 260 { 261 classCfg.objectConfiguration = new ObjectConfiguration(); 262 classCfg.objectConfiguration.checks = checks; 263 } 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 public ClassConfiguration getClassConfiguration(final Class< ? > clazz) 270 { 271 final ClassConfiguration classCfg = new ClassConfiguration(); 272 classCfg.type = clazz; 273 274 final Guarded guarded = clazz.getAnnotation(Guarded.class); 275 276 if (guarded == null) 277 { 278 classCfg.applyFieldConstraintsToConstructors = false; 279 classCfg.applyFieldConstraintsToSetters = false; 280 classCfg.assertParametersNotNull = false; 281 classCfg.checkInvariants = false; 282 classCfg.inspectInterfaces = false; 283 } 284 else 285 { 286 classCfg.applyFieldConstraintsToConstructors = guarded.applyFieldConstraintsToConstructors(); 287 classCfg.applyFieldConstraintsToSetters = guarded.applyFieldConstraintsToSetters(); 288 classCfg.assertParametersNotNull = guarded.assertParametersNotNull(); 289 classCfg.checkInvariants = guarded.checkInvariants(); 290 classCfg.inspectInterfaces = guarded.inspectInterfaces(); 291 } 292 293 configureObjectLevelChecks(classCfg); 294 configureFieldChecks(classCfg); 295 configureConstructorParameterChecks(classCfg); 296 configureMethodChecks(classCfg); 297 298 return classCfg; 299 } 300 301 /** 302 * {@inheritDoc} 303 */ 304 public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId) 305 { 306 return null; 307 } 308 309 protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> initializeCheck( 310 final ConstraintAnnotation constraintAnnotation) throws ReflectionException 311 { 312 assert constraintAnnotation != null; 313 314 final Constraint constraint = constraintAnnotation.annotationType().getAnnotation(Constraint.class); 315 316 // determine the check class 317 @SuppressWarnings("unchecked") 318 final Class<AnnotationCheck<ConstraintAnnotation>> checkClass = (Class<AnnotationCheck<ConstraintAnnotation>>) constraint 319 .checkWith(); 320 321 // instantiate the appropriate check for the found constraint 322 final AnnotationCheck<ConstraintAnnotation> check = newCheckInstance(checkClass); 323 check.configure(constraintAnnotation); 324 325 for (final CheckInitializationListener listener : listeners) 326 listener.onCheckInitialized(check); 327 return check; 328 } 329 330 protected <ConstraintsAnnotation extends Annotation> void initializeChecks( 331 final ConstraintsAnnotation constraintsAnnotation, final List<Check> checks) throws ReflectionException 332 { 333 try 334 { 335 final Method getValue = constraintsAnnotation.annotationType().getDeclaredMethod("value", 336 (Class< ? >[]) null); 337 final Object[] constraintAnnotations = (Object[]) getValue.invoke(constraintsAnnotation, (Object[]) null); 338 for (final Object ca : constraintAnnotations) 339 checks.add(initializeCheck((Annotation) ca)); 340 } 341 catch (final ReflectionException ex) 342 { 343 throw ex; 344 } 345 catch (final Exception ex) 346 { 347 throw new ReflectionException("Cannot initialize constraint check " 348 + constraintsAnnotation.annotationType().getName(), ex); 349 } 350 } 351 352 protected <ExclusionAnnotation extends Annotation> AnnotationCheckExclusion<ExclusionAnnotation> initializeExclusion( 353 final ExclusionAnnotation exclusionAnnotation) throws ReflectionException 354 { 355 assert exclusionAnnotation != null; 356 357 final Exclusion constraint = exclusionAnnotation.annotationType().getAnnotation(Exclusion.class); 358 359 // determine the check class 360 final Class< ? > exclusionClass = constraint.excludeWith(); 361 362 try 363 { 364 // instantiate the appropriate exclusion for the found annotation 365 @SuppressWarnings("unchecked") 366 final AnnotationCheckExclusion<ExclusionAnnotation> exclusion = (AnnotationCheckExclusion<ExclusionAnnotation>) exclusionClass 367 .newInstance(); 368 exclusion.configure(exclusionAnnotation); 369 return exclusion; 370 } 371 catch (final Exception ex) 372 { 373 throw new ReflectionException("Cannot initialize constraint exclusion " + exclusionClass.getName(), ex); 374 } 375 } 376 377 /** 378 * @return a new instance of the given constraint check implementation class 379 */ 380 protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> newCheckInstance( 381 final Class<AnnotationCheck<ConstraintAnnotation>> checkClass) throws OValException 382 { 383 try 384 { 385 return checkClass.newInstance(); 386 } 387 catch (final InstantiationException ex) 388 { 389 throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex); 390 } 391 catch (final IllegalAccessException ex) 392 { 393 throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex); 394 } 395 } 396 397 public boolean removeCheckInitializationListener(final CheckInitializationListener listener) 398 { 399 return listeners.remove(listener); 400 } 401 }