001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2012 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     *******************************************************************************/
013    package net.sf.oval.guard;
014    
015    import java.lang.reflect.Constructor;
016    import java.lang.reflect.Method;
017    import java.util.Collection;
018    import java.util.Iterator;
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    import java.util.WeakHashMap;
024    
025    import net.sf.oval.Check;
026    import net.sf.oval.CheckExclusion;
027    import net.sf.oval.ConstraintViolation;
028    import net.sf.oval.Validator;
029    import net.sf.oval.configuration.Configurer;
030    import net.sf.oval.context.ConstructorParameterContext;
031    import net.sf.oval.context.MethodEntryContext;
032    import net.sf.oval.context.MethodExitContext;
033    import net.sf.oval.context.MethodParameterContext;
034    import net.sf.oval.context.MethodReturnValueContext;
035    import net.sf.oval.context.OValContext;
036    import net.sf.oval.exception.ConstraintsViolatedException;
037    import net.sf.oval.exception.InvalidConfigurationException;
038    import net.sf.oval.exception.OValException;
039    import net.sf.oval.exception.ValidationFailedException;
040    import net.sf.oval.expression.ExpressionLanguage;
041    import net.sf.oval.internal.ClassChecks;
042    import net.sf.oval.internal.ContextCache;
043    import net.sf.oval.internal.Log;
044    import net.sf.oval.internal.ParameterChecks;
045    import net.sf.oval.internal.util.ArrayUtils;
046    import net.sf.oval.internal.util.Assert;
047    import net.sf.oval.internal.util.IdentitySet;
048    import net.sf.oval.internal.util.Invocable;
049    import net.sf.oval.internal.util.LinkedSet;
050    import net.sf.oval.internal.util.ReflectionUtils;
051    import net.sf.oval.internal.util.ThreadLocalList;
052    import net.sf.oval.internal.util.ThreadLocalWeakHashMap;
053    
054    /**
055     * Extended version of the validator to realize programming by contract.
056     * 
057     * @author Sebastian Thomschke
058     */
059    public class Guard extends Validator
060    {
061    
062            /**
063             * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions 
064             */
065            protected static final class GuardMethodPreResult
066            {
067                    protected final boolean checkInvariants;
068                    protected final Method method;
069                    protected final Object[] args;
070                    protected final ClassChecks cc;
071                    protected final List<ConstraintViolation> violations;
072                    protected final Map<PostCheck, Object> postCheckOldValues;
073                    protected final Object guardedObject;
074    
075                    public GuardMethodPreResult(final Object guardedObject, final Method method, final Object[] args,
076                                    final ClassChecks cc, final boolean checkInvariants, final Map<PostCheck, Object> postCheckOldValues,
077                                    final List<ConstraintViolation> violations)
078                    {
079                            this.guardedObject = guardedObject;
080                            this.method = method;
081                            this.args = args;
082                            this.cc = cc;
083                            this.checkInvariants = checkInvariants;
084                            this.postCheckOldValues = postCheckOldValues;
085                            this.violations = violations;
086                    }
087            }
088    
089            /**
090             * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions 
091             */
092            protected static final GuardMethodPreResult DO_NOT_PROCEED = new GuardMethodPreResult(null, null, null, null,
093                            false, null, null);
094    
095            private static final Log LOG = Log.getLog(Guard.class);
096    
097            /**
098             * string based on validated object hashcode + method hashcode for currently validated method return values
099             */
100            private static final ThreadLocalList<String> currentlyCheckingMethodReturnValues = new ThreadLocalList<String>();
101    
102            /**
103             * string based on validated object hashcode + method hashcode for currently validated method pre-conditions
104             */
105            private static final ThreadLocalList<String> currentlyCheckingPreConditions = new ThreadLocalList<String>();
106    
107            /**
108             * string based on validated object hashcode + method hashcode for currently validated method post-conditions
109             */
110            private static final ThreadLocalList<String> currentlyCheckingPostConditions = new ThreadLocalList<String>();
111    
112            private boolean isActivated = true;
113            private boolean isInvariantsEnabled = true;
114            private boolean isPreConditionsEnabled = true;
115            private boolean isPostConditionsEnabled = true;
116    
117            /**
118             * Flag that indicates if any listeners were registered at any time. Used for improved performance.
119             */
120            private boolean isListenersFeatureUsed = false;
121            /**
122             * Flag that indicates if exception suppressing was used at any time. Used for improved performance.
123             */
124            private boolean isProbeModeFeatureUsed = false;
125    
126            private final Set<ConstraintsViolatedListener> listeners = new IdentitySet<ConstraintsViolatedListener>(4);
127    
128            private final Map<Class< ? >, Set<ConstraintsViolatedListener>> listenersByClass = new WeakHashMap<Class< ? >, Set<ConstraintsViolatedListener>>(
129                            4);
130    
131            private final Map<Object, Set<ConstraintsViolatedListener>> listenersByObject = new WeakHashMap<Object, Set<ConstraintsViolatedListener>>(
132                            4);
133    
134            /**
135             * Objects for OVal suppresses occurring ConstraintViolationExceptions for pre condition violations on setter methods
136             * for the current thread.
137             */
138            private final ThreadLocalWeakHashMap<Object, ProbeModeListener> objectsInProbeMode = new ThreadLocalWeakHashMap<Object, ProbeModeListener>();
139    
140            /**
141             * Constructs a new guard object and uses a new instance of AnnotationsConfigurer
142             */
143            public Guard()
144            {
145                    super();
146            }
147    
148            public Guard(final Collection<Configurer> configurers)
149            {
150                    super(configurers);
151            }
152    
153            public Guard(final Configurer... configurers)
154            {
155                    super(configurers);
156            }
157    
158            private List<CheckExclusion> _getActiveExclusions(final Set<CheckExclusion> exclusions)
159            {
160                    final List<CheckExclusion> activeExclusions = new LinkedList<CheckExclusion>(exclusions);
161                    for (final Iterator<CheckExclusion> it = activeExclusions.iterator(); it.hasNext();)
162                    {
163                            final CheckExclusion exclusion = it.next();
164                            if (!isAnyProfileEnabled(exclusion.getProfiles(), null)) it.remove();
165                    }
166                    return activeExclusions.size() == 0 ? null : activeExclusions;
167            }
168    
169            private void _validateParameterChecks(final ParameterChecks checks, final Object validatedObject,
170                            final Object valueToValidate, final OValContext context, final List<ConstraintViolation> violations)
171            {
172                    // determine the active exclusions based on the active profiles
173                    final List<CheckExclusion> activeExclusions = checks.hasExclusions()
174                                    ? _getActiveExclusions(checks.checkExclusions) : null;
175    
176                    // check the constraints
177                    for (final Check check : checks.checks)
178                    {
179                            boolean skip = false;
180    
181                            if (activeExclusions != null)
182                                    for (final CheckExclusion exclusion : activeExclusions)
183                                            if (exclusion.isActive(validatedObject, valueToValidate, this)
184                                                            && exclusion.isCheckExcluded(check, validatedObject, valueToValidate, context, this))
185                                            {
186                                                    // skip if this check should be excluded
187                                                    skip = true;
188                                                    continue;
189                                            }
190                            if (!skip) checkConstraint(violations, check, validatedObject, valueToValidate, context, null, false);
191                    }
192            }
193    
194            /**
195             * Registers constraint checks for the given constructor parameter
196             * 
197             * @param ctor
198             * @param paramIndex
199             * @param exclusions
200             * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty
201             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
202             */
203            public void addCheckExclusions(final Constructor< ? > ctor, final int paramIndex,
204                            final CheckExclusion... exclusions) throws IllegalArgumentException, InvalidConfigurationException
205            {
206                    Assert.argumentNotNull("ctor", ctor);
207                    Assert.argumentNotEmpty("exclusions", exclusions);
208    
209                    getClassChecks(ctor.getDeclaringClass()).addConstructorParameterCheckExclusions(ctor, paramIndex, exclusions);
210            }
211    
212            /**
213             * Registers constraint checks for the given method parameter
214             * 
215             * @param method
216             * @param paramIndex
217             * @param exclusions
218             * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty
219             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
220             */
221            public void addCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions)
222                            throws IllegalArgumentException, InvalidConfigurationException
223            {
224                    Assert.argumentNotNull("method", method);
225                    Assert.argumentNotEmpty("exclusions", exclusions);
226    
227                    getClassChecks(method.getDeclaringClass()).addMethodParameterCheckExclusions(method, paramIndex, exclusions);
228            }
229    
230            /**
231             * Registers constraint checks for the given constructor parameter
232             * 
233             * @param ctor
234             * @param paramIndex
235             * @param checks
236             * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is
237             *             empty
238             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
239             */
240            public void addChecks(final Constructor< ? > ctor, final int paramIndex, final Check... checks)
241                            throws IllegalArgumentException, InvalidConfigurationException
242            {
243                    Assert.argumentNotNull("ctor", ctor);
244                    Assert.argumentNotEmpty("checks", checks);
245    
246                    getClassChecks(ctor.getDeclaringClass()).addConstructorParameterChecks(ctor, paramIndex, checks);
247            }
248    
249            /**
250             * Registers constraint checks for the given method's return value
251             * 
252             * @param method
253             * @param checks
254             * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code> or checks is empty
255             * @throws InvalidConfigurationException if method does not declare a return type (void), or the declaring class is
256             *             not guarded
257             */
258            @Override
259            public void addChecks(final Method method, final Check... checks) throws IllegalArgumentException,
260                            InvalidConfigurationException
261            {
262                    Assert.argumentNotNull("method", method);
263                    Assert.argumentNotEmpty("checks", checks);
264    
265                    getClassChecks(method.getDeclaringClass()).addMethodReturnValueChecks(method, null, checks);
266            }
267    
268            /**
269             * Registers constraint checks for the given method parameter
270             * 
271             * @param method
272             * @param paramIndex
273             * @param checks
274             * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
275             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
276             */
277            public void addChecks(final Method method, final int paramIndex, final Check... checks)
278                            throws IllegalArgumentException, InvalidConfigurationException
279            {
280                    Assert.argumentNotNull("method", method);
281                    Assert.argumentNotEmpty("checks", checks);
282    
283                    getClassChecks(method.getDeclaringClass()).addMethodParameterChecks(method, paramIndex, checks);
284            }
285    
286            /**
287             * Registers post condition checks to a method's return value
288             * 
289             * @param method
290             * @param checks
291             * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
292             * @throws InvalidConfigurationException if the declaring class is not guarded
293             */
294            public void addChecks(final Method method, final PostCheck... checks) throws IllegalArgumentException,
295                            InvalidConfigurationException
296            {
297                    Assert.argumentNotNull("method", method);
298                    Assert.argumentNotEmpty("checks", checks);
299    
300                    getClassChecks(method.getDeclaringClass()).addMethodPostChecks(method, checks);
301            }
302    
303            /**
304             * Registers pre condition checks to a method's return value
305             * 
306             * @param method
307             * @param checks
308             * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
309             * @throws InvalidConfigurationException if the declaring class is not guarded
310             */
311            public void addChecks(final Method method, final PreCheck... checks) throws IllegalArgumentException,
312                            InvalidConfigurationException
313            {
314                    Assert.argumentNotNull("method", method);
315                    Assert.argumentNotEmpty("checks", checks);
316    
317                    getClassChecks(method.getDeclaringClass()).addMethodPreChecks(method, checks);
318            }
319    
320            /**
321             * Registers the given listener for <b>all</b> thrown ConstraintViolationExceptions
322             * 
323             * @param listener the listener to register
324             * @return <code>true</code> if the listener was not yet registered
325             * @throws IllegalArgumentException if <code>listener == null</code>
326             */
327            public boolean addListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException
328            {
329                    Assert.argumentNotNull("listener", listener);
330    
331                    isListenersFeatureUsed = true;
332                    return listeners.add(listener);
333            }
334    
335            /**
336             * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given class
337             * 
338             * @param listener the listener to register
339             * @param guardedClass guarded class or interface
340             * @return <code>true</code> if the listener was not yet registered
341             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
342             */
343            public boolean addListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass)
344                            throws IllegalArgumentException
345            {
346                    Assert.argumentNotNull("listener", listener);
347                    Assert.argumentNotNull("guardedClass", guardedClass);
348    
349                    isListenersFeatureUsed = true;
350    
351                    synchronized (listenersByClass)
352                    {
353                            Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedClass);
354    
355                            if (classListeners == null)
356                            {
357                                    classListeners = getCollectionFactory().createSet();
358                                    listenersByClass.put(guardedClass, classListeners);
359                            }
360                            return classListeners.add(listener);
361                    }
362            }
363    
364            /**
365             * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given object
366             * 
367             * @param listener the listener to register
368             * @param guardedObject
369             * @return <code>true</code> if the listener was not yet registered
370             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
371             */
372            public boolean addListener(final ConstraintsViolatedListener listener, final Object guardedObject)
373            {
374                    Assert.argumentNotNull("listener", listener);
375                    Assert.argumentNotNull("guardedObject", guardedObject);
376    
377                    isListenersFeatureUsed = true;
378    
379                    synchronized (listenersByObject)
380                    {
381                            Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject);
382    
383                            if (objectListeners == null)
384                            {
385                                    objectListeners = getCollectionFactory().createSet(2);
386                                    listenersByObject.put(guardedObject, objectListeners);
387                            }
388                            return objectListeners.add(listener);
389                    }
390            }
391    
392            /**
393             * Evaluates the old expression
394             * 
395             * @param validatedObject
396             * @param method
397             * @param args
398             * @return null if no violation, otherwise a list
399             * @throws ValidationFailedException
400             */
401            protected Map<PostCheck, Object> calculateMethodPostOldValues(final Object validatedObject, final Method method,
402                            final Object[] args) throws ValidationFailedException
403            {
404                    try
405                    {
406                            final ClassChecks cc = getClassChecks(method.getDeclaringClass());
407                            final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method);
408    
409                            // shortcut: check if any post checks for this method exist
410                            if (postChecks == null) return null;
411    
412                            final String[] parameterNames = parameterNameResolver.getParameterNames(method);
413                            final boolean hasParameters = parameterNames.length > 0;
414    
415                            final Map<PostCheck, Object> oldValues = getCollectionFactory().createMap(postChecks.size());
416    
417                            for (final PostCheck check : postChecks)
418                                    if (isAnyProfileEnabled(check.getProfiles(), null) && check.getOld() != null
419                                                    && check.getOld().length() > 0)
420                                    {
421                                            final ExpressionLanguage eng = expressionLanguageRegistry
422                                                            .getExpressionLanguage(check.getLanguage());
423                                            final Map<String, Object> values = getCollectionFactory().createMap();
424                                            values.put("_this", validatedObject);
425                                            if (hasParameters)
426                                            {
427                                                    values.put("_args", args);
428                                                    for (int i = 0; i < args.length; i++)
429                                                            values.put(parameterNames[i], args[i]);
430                                            }
431                                            else
432                                                    values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
433    
434                                            oldValues.put(check, eng.evaluate(check.getOld(), values));
435                                    }
436    
437                            return oldValues;
438                    }
439                    catch (final OValException ex)
440                    {
441                            throw new ValidationFailedException("Method post conditions validation failed. Method: " + method
442                                            + " Validated object: " + validatedObject, ex);
443                    }
444            }
445    
446            /**
447             * Disables the probe mode for the given object in the current thread.
448             * 
449             * @param guardedObject the object to disable the probe mode for
450             * @throws IllegalArgumentException if <code>guardedObject == null</code>
451             * @throws IllegalStateException in case probe mode was not enabled for the given object
452             */
453            public ProbeModeListener disableProbeMode(final Object guardedObject) throws IllegalArgumentException,
454                            IllegalStateException
455            {
456                    Assert.argumentNotNull("guardedObject", guardedObject);
457    
458                    return objectsInProbeMode.get().remove(guardedObject);
459            }
460    
461            /**
462             * Enables the probe mode for the given object in the current thread. In probe mode calls to methods of an
463             * object are not actually executed. OVal only validates method pre-conditions and notifies
464             * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will
465             * return null.
466             * 
467             * @param guardedObject the object to enable the probe mode for
468             * @throws IllegalArgumentException if <code>guardedObject == null</code>
469             * @throws IllegalStateException if the probe mode is already enabled
470             */
471            public void enableProbeMode(final Object guardedObject) throws IllegalArgumentException, IllegalStateException
472            {
473                    Assert.argumentNotNull("guardedObject", guardedObject);
474    
475                    if (guardedObject instanceof Class< ? >)
476                            LOG.warn("Enabling probe mode for a class looks like a programming error. Class: {1}", guardedObject);
477                    isProbeModeFeatureUsed = true;
478    
479                    if (objectsInProbeMode.get().get(guardedObject) != null)
480                            throw new IllegalStateException("The object is already in probe mode.");
481    
482                    objectsInProbeMode.get().put(guardedObject, new ProbeModeListener(guardedObject));
483            }
484    
485            /**
486             * Returns the registers constraint pre condition checks for the given method parameter
487             * 
488             * @param method
489             * @param paramIndex
490             * @throws IllegalArgumentException if <code>method == null</code>
491             */
492            public Check[] getChecks(final Method method, final int paramIndex) throws InvalidConfigurationException
493            {
494                    Assert.argumentNotNull("method", method);
495    
496                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
497    
498                    final Map<Integer, ParameterChecks> checks = cc.checksForMethodParameters.get(method);
499                    if (checks == null) return null;
500    
501                    final ParameterChecks paramChecks = checks.get(paramIndex);
502                    return paramChecks == null ? null : paramChecks.checks.toArray(new Check[checks.size()]);
503            }
504    
505            /**
506             * Returns the registered post condition checks for the given method
507             * 
508             * @param method
509             * @throws IllegalArgumentException if <code>method == null</code>
510             */
511            public PostCheck[] getChecksPost(final Method method) throws IllegalArgumentException
512            {
513                    Assert.argumentNotNull("method", method);
514    
515                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
516    
517                    final Set<PostCheck> checks = cc.checksForMethodsPostExcecution.get(method);
518                    return checks == null ? null : checks.toArray(new PostCheck[checks.size()]);
519            }
520    
521            /**
522             * Returns the registered pre condition checks for the given method.
523             * 
524             * @param method
525             * @throws IllegalArgumentException if <code>method == null</code>
526             */
527            public PreCheck[] getChecksPre(final Method method) throws IllegalArgumentException
528            {
529                    Assert.argumentNotNull("method", method);
530    
531                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
532    
533                    final Set<PreCheck> checks = cc.checksForMethodsPreExecution.get(method);
534                    return checks == null ? null : checks.toArray(new PreCheck[checks.size()]);
535            }
536    
537            /**
538             * @return the parameterNameResolver
539             */
540            public ParameterNameResolver getParameterNameResolver()
541            {
542                    return parameterNameResolver;
543            }
544    
545            /**
546             * This method is provided for use by guard aspects.
547             * 
548             * @throws ConstraintsViolatedException
549             * @throws ValidationFailedException
550             */
551            protected void guardConstructorPost(final Object guardedObject, final Constructor< ? > ctor, final Object[] args)
552                            throws ConstraintsViolatedException, ValidationFailedException
553            {
554                    if (!isActivated) return;
555    
556                    final ClassChecks cc = getClassChecks(ctor.getDeclaringClass());
557    
558                    // check invariants
559                    if (isInvariantsEnabled && cc.isCheckInvariants || cc.methodsWithCheckInvariantsPost.contains(ctor))
560                    {
561                            final List<ConstraintViolation> violations = getCollectionFactory().createList();
562                            currentViolations.get().add(violations);
563                            try
564                            {
565                                    validateInvariants(guardedObject, violations, null);
566                            }
567                            catch (final ValidationFailedException ex)
568                            {
569                                    throw translateException(ex);
570                            }
571                            finally
572                            {
573                                    currentViolations.get().removeLast();
574                            }
575    
576                            if (violations.size() > 0)
577                            {
578                                    final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
579                                    if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException);
580    
581                                    throw translateException(violationException);
582                            }
583                    }
584            }
585    
586            /**
587             * This method is provided for use by guard aspects.
588             * 
589             * @throws ConstraintsViolatedException if anything precondition is not satisfied
590             * @throws ValidationFailedException
591             */
592            protected void guardConstructorPre(final Object guardedObject, final Constructor< ? > ctor, final Object[] args)
593                            throws ConstraintsViolatedException, ValidationFailedException
594            {
595                    if (!isActivated) return;
596    
597                    // constructor parameter validation
598                    if (isPreConditionsEnabled && args.length > 0)
599                    {
600                            final List<ConstraintViolation> violations;
601                            try
602                            {
603                                    violations = validateConstructorParameters(guardedObject, ctor, args);
604                            }
605                            catch (final ValidationFailedException ex)
606                            {
607                                    throw translateException(ex);
608                            }
609    
610                            if (violations != null)
611                            {
612                                    final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
613                                    if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException);
614    
615                                    throw translateException(violationException);
616                            }
617                    }
618            }
619    
620            /**
621             * This method is provided for use by guard aspects.
622             * 
623             * @param guardedObject
624             * @param method
625             * @param args
626             * @param invocable
627             * @return The method return value or null if the guarded object is in probe mode.
628             * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe mode.
629             * @throws ValidationFailedException
630             */
631            protected Object guardMethod(Object guardedObject, final Method method, final Object[] args,
632                            final Invocable invocable) throws Throwable
633            {
634                    if (!isActivated) return invocable.invoke();
635    
636                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
637    
638                    final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants
639                                    && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method);
640    
641                    // if static method use the declaring class as guardedObject
642                    if (guardedObject == null && ReflectionUtils.isStatic(method)) guardedObject = method.getDeclaringClass();
643    
644                    final List<ConstraintViolation> violations = getCollectionFactory().createList();
645                    currentViolations.get().add(violations);
646    
647                    try
648                    {
649                            // check invariants
650                            if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method))
651                                    validateInvariants(guardedObject, violations, null);
652    
653                            if (isPreConditionsEnabled)
654                            {
655                                    // method parameter validation
656                                    if (violations.size() == 0 && args.length > 0)
657                                            validateMethodParameters(guardedObject, method, args, violations);
658    
659                                    // @Pre validation
660                                    if (violations.size() == 0) validateMethodPre(guardedObject, method, args, violations);
661                            }
662                    }
663                    catch (final ValidationFailedException ex)
664                    {
665                            throw translateException(ex);
666                    }
667                    finally
668                    {
669                            currentViolations.get().removeLast();
670                    }
671    
672                    final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null;
673                    if (pml != null) pml.onMethodCall(method, args);
674    
675                    if (violations.size() > 0)
676                    {
677                            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
678                            if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException);
679    
680                            // don't throw an exception if the method is a setter and suppressing for precondition is enabled
681                            if (pml != null)
682                            {
683                                    pml.onConstraintsViolatedException(violationException);
684                                    return null;
685                            }
686    
687                            throw translateException(violationException);
688                    }
689    
690                    // abort method execution if in probe mode
691                    if (pml != null) return null;
692    
693                    final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args);
694    
695                    final Object returnValue = invocable.invoke();
696    
697                    currentViolations.get().add(violations);
698    
699                    try
700                    {
701                            // check invariants if executed method is not private
702                            if (checkInvariants || cc.methodsWithCheckInvariantsPost.contains(method))
703                                    validateInvariants(guardedObject, violations, null);
704    
705                            if (isPostConditionsEnabled)
706                            {
707    
708                                    // method return value
709                                    if (violations.size() == 0) validateMethodReturnValue(guardedObject, method, returnValue, violations);
710    
711                                    // @Post
712                                    if (violations.size() == 0)
713                                            validateMethodPost(guardedObject, method, args, returnValue, postCheckOldValues, violations);
714                            }
715                    }
716                    catch (final ValidationFailedException ex)
717                    {
718                            throw translateException(ex);
719                    }
720                    finally
721                    {
722                            currentViolations.get().removeLast();
723                    }
724    
725                    if (violations.size() > 0)
726                    {
727                            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
728                            if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException);
729    
730                            throw translateException(violationException);
731                    }
732    
733                    return returnValue;
734            }
735    
736            /**
737             * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions, 
738             * then {@link #guardMethod(Object, Method, Object[], Invocable)} can be used instead 
739             * 
740             * This method is provided for use by guard aspects.
741             * 
742             * @param returnValue
743             * @param preResult
744             * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe
745             *             mode.
746             */
747            protected void guardMethodPost(final Object returnValue, final GuardMethodPreResult preResult)
748                            throws ConstraintsViolatedException, ValidationFailedException
749            {
750                    if (!isActivated) return;
751    
752                    try
753                    {
754                            // check invariants if executed method is not private
755                            if (preResult.checkInvariants || preResult.cc.methodsWithCheckInvariantsPost.contains(preResult.method))
756                                    validateInvariants(preResult.guardedObject, preResult.violations, null);
757    
758                            if (isPostConditionsEnabled)
759                            {
760    
761                                    // method return value
762                                    if (preResult.violations.size() == 0)
763                                            validateMethodReturnValue(preResult.guardedObject, preResult.method, returnValue,
764                                                            preResult.violations);
765    
766                                    // @Post
767                                    if (preResult.violations.size() == 0)
768                                            validateMethodPost(preResult.guardedObject, preResult.method, preResult.args, returnValue,
769                                                            preResult.postCheckOldValues, preResult.violations);
770                            }
771                    }
772                    catch (final ValidationFailedException ex)
773                    {
774                            throw translateException(ex);
775                    }
776    
777                    if (preResult.violations.size() > 0)
778                    {
779                            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(
780                                            preResult.violations);
781                            if (isListenersFeatureUsed) notifyListeners(preResult.guardedObject, violationException);
782    
783                            throw translateException(violationException);
784                    }
785            }
786    
787            /**
788             * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions, then {@link #guardMethod(Object, Method, Object[], Invocable)} can be used instead
789             * 
790             * This method is provided for use by guard aspects.
791             * 
792             * @param guardedObject
793             * @param method
794             * @param args
795             * @return Null if method guarding is deactivated or a result object that needs to be passed to {@link #guardMethodPost(Object, GuardMethodPreResult)}
796             * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe
797             *             mode.
798             */
799            protected GuardMethodPreResult guardMethodPre(Object guardedObject, final Method method, final Object[] args)
800                            throws ConstraintsViolatedException, ValidationFailedException
801            {
802                    if (!isActivated) return null;
803    
804                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
805    
806                    final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants
807                                    && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method);
808    
809                    // if static method use the declaring class as guardedObject
810                    if (guardedObject == null && ReflectionUtils.isStatic(method)) guardedObject = method.getDeclaringClass();
811    
812                    final List<ConstraintViolation> violations = getCollectionFactory().createList();
813                    currentViolations.get().add(violations);
814    
815                    try
816                    {
817                            // check invariants
818                            if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method))
819                                    validateInvariants(guardedObject, violations, null);
820    
821                            if (isPreConditionsEnabled)
822                            {
823                                    // method parameter validation
824                                    if (violations.size() == 0 && args.length > 0)
825                                            validateMethodParameters(guardedObject, method, args, violations);
826    
827                                    // @Pre validation
828                                    if (violations.size() == 0) validateMethodPre(guardedObject, method, args, violations);
829                            }
830                    }
831                    catch (final ValidationFailedException ex)
832                    {
833                            throw translateException(ex);
834                    }
835                    finally
836                    {
837                            currentViolations.get().removeLast();
838                    }
839    
840                    final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null;
841                    if (pml != null) pml.onMethodCall(method, args);
842    
843                    if (violations.size() > 0)
844                    {
845                            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
846                            if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException);
847    
848                            // don't throw an exception if the method is a setter and suppressing for precondition is enabled
849                            if (pml != null)
850                            {
851                                    pml.onConstraintsViolatedException(violationException);
852                                    return DO_NOT_PROCEED;
853                            }
854    
855                            throw translateException(violationException);
856                    }
857    
858                    // abort method execution if in probe mode
859                    if (pml != null) return DO_NOT_PROCEED;
860    
861                    final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args);
862    
863                    return new GuardMethodPreResult(guardedObject, method, args, cc, checkInvariants, postCheckOldValues,
864                                    violations);
865            }
866    
867            /**
868             * @param listener
869             * @return <code>true</code> if the listener is registered
870             * @throws IllegalArgumentException if <code>listener == null</code>
871             */
872            public boolean hasListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException
873            {
874                    Assert.argumentNotNull("listener", listener);
875    
876                    return listeners.contains(listener);
877            }
878    
879            /**
880             * @param listener
881             * @param guardedClass guarded class or interface
882             * @return <code>true</code> if the listener is registered
883             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
884             */
885            public boolean hasListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass)
886                            throws IllegalArgumentException
887            {
888                    Assert.argumentNotNull("listener", listener);
889                    Assert.argumentNotNull("guardedClass", guardedClass);
890    
891                    final Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedClass);
892    
893                    if (classListeners == null) return false;
894    
895                    return classListeners.contains(listener);
896            }
897    
898            /**
899             * @param listener
900             * @param guardedObject
901             * @return <code>true</code> if the listener is registered
902             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
903             */
904            public boolean hasListener(final ConstraintsViolatedListener listener, final Object guardedObject)
905                            throws IllegalArgumentException
906            {
907                    Assert.argumentNotNull("listener", listener);
908                    Assert.argumentNotNull("guardedObject", guardedObject);
909    
910                    final Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject);
911    
912                    if (objectListeners == null) return false;
913    
914                    return objectListeners.contains(listener);
915            }
916    
917            /**
918             * @return the isEnabled
919             */
920            public boolean isActivated()
921            {
922                    return isActivated;
923            }
924    
925            /**
926             * Determines if the probe mode is enabled for the given object in the current thread. In probe mode calls to
927             * methods of an object are not actually executed. OVal only validates method pre-conditions and notifies
928             * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will
929             * return null.
930             * 
931             * @param guardedObject
932             * @return true if exceptions are suppressed
933             */
934            public boolean isInProbeMode(final Object guardedObject)
935            {
936                    // guardedObject may be null if isInProbeMode is called when validating pre conditions of a static method
937                    if (guardedObject == null) return false;
938    
939                    return objectsInProbeMode.get().containsKey(guardedObject);
940            }
941    
942            /**
943             * Determines if invariants are checked prior and after every call to a non-private method or constructor.
944             * 
945             * @return the isInvariantChecksActivated
946             */
947            public boolean isInvariantsEnabled()
948            {
949                    return isInvariantsEnabled;
950            }
951    
952            /**
953             * Determines if invariants are checked prior and after every call to a non-private method or constructor.
954             * 
955             * @param guardedClass the guarded class
956             * @return the isInvariantChecksActivated
957             */
958            public boolean isInvariantsEnabled(final Class< ? > guardedClass)
959            {
960                    return getClassChecks(guardedClass).isCheckInvariants;
961            }
962    
963            /**
964             * @return the isPostChecksActivated
965             */
966            public boolean isPostConditionsEnabled()
967            {
968                    return isPostConditionsEnabled;
969            }
970    
971            /**
972             * @return the isPreChecksActivated
973             */
974            public boolean isPreConditionsEnabled()
975            {
976                    return isPreConditionsEnabled;
977            }
978    
979            /**
980             * notifies all registered validation listener about the occurred constraint violation exception
981             */
982            protected void notifyListeners(final Object guardedObject, final ConstraintsViolatedException ex)
983            {
984                    // happens for static methods
985                    if (guardedObject == null) return;
986    
987                    final LinkedSet<ConstraintsViolatedListener> listenersToNotify = new LinkedSet<ConstraintsViolatedListener>();
988    
989                    // get the object listeners
990                    {
991                            final Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject);
992                            if (objectListeners != null) listenersToNotify.addAll(objectListeners);
993                    }
994    
995                    // get the class listeners
996                    {
997                            final Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedObject.getClass());
998                            if (classListeners != null) listenersToNotify.addAll(classListeners);
999                    }
1000    
1001                    // get the interface listeners
1002                    {
1003                            for (final Class< ? > interfaze : guardedObject.getClass().getInterfaces())
1004                            {
1005                                    final Set<ConstraintsViolatedListener> interfaceListeners = listenersByClass.get(interfaze);
1006                                    if (interfaceListeners != null) listenersToNotify.addAll(interfaceListeners);
1007                            }
1008                    }
1009    
1010                    // get the global listeners
1011                    listenersToNotify.addAll(listeners);
1012    
1013                    // notify the listeners
1014                    for (final ConstraintsViolatedListener listener : listenersToNotify)
1015                            try
1016                            {
1017                                    listener.onConstraintsViolatedException(ex);
1018                            }
1019                            catch (final RuntimeException rex)
1020                            {
1021                                    LOG.warn("Notifying listener '{1}' failed.", listener, rex);
1022                            }
1023    
1024            }
1025    
1026            /**
1027             * Removes constraint check exclusions from the given constructor parameter
1028             * 
1029             * @param ctor
1030             * @param paramIndex
1031             * @param exclusions
1032             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
1033             */
1034            public void removeCheckExclusions(final Constructor< ? > ctor, final int paramIndex,
1035                            final CheckExclusion... exclusions) throws InvalidConfigurationException
1036            {
1037                    Assert.argumentNotNull("ctor", ctor);
1038                    Assert.argumentNotEmpty("exclusions", exclusions);
1039    
1040                    getClassChecks(ctor.getDeclaringClass())
1041                                    .removeConstructorParameterCheckExclusions(ctor, paramIndex, exclusions);
1042            }
1043    
1044            /**
1045             * Removes constraint check exclusions from the given method parameter
1046             * 
1047             * @param method
1048             * @param paramIndex
1049             * @param exclusions
1050             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
1051             */
1052            public void removeCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions)
1053                            throws InvalidConfigurationException
1054            {
1055                    Assert.argumentNotNull("method", method);
1056                    Assert.argumentNotEmpty("exclusions", exclusions);
1057    
1058                    getClassChecks(method.getDeclaringClass()).removeMethodParameterCheckExclusions(method, paramIndex, exclusions);
1059            }
1060    
1061            /**
1062             * Removes constraint checks from the given constructor parameter
1063             * 
1064             * @param ctor
1065             * @param paramIndex
1066             * @param checks
1067             * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
1068             */
1069            public void removeChecks(final Constructor< ? > ctor, final int paramIndex, final Check... checks)
1070                            throws InvalidConfigurationException
1071            {
1072                    Assert.argumentNotNull("ctor", ctor);
1073                    Assert.argumentNotEmpty("checks", checks);
1074    
1075                    getClassChecks(ctor.getDeclaringClass()).removeConstructorParameterChecks(ctor, paramIndex, checks);
1076            }
1077    
1078            /**
1079             * Removes constraint checks for the given method parameter
1080             * 
1081             * @param method
1082             * @param paramIndex
1083             * @param checks
1084             * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is
1085             *             empty
1086             * @throws InvalidConfigurationException if the parameterIndex is out of range
1087             */
1088            public void removeChecks(final Method method, final int paramIndex, final Check... checks)
1089                            throws InvalidConfigurationException
1090            {
1091                    Assert.argumentNotNull("method", method);
1092                    Assert.argumentNotEmpty("checks", checks);
1093    
1094                    getClassChecks(method.getDeclaringClass()).removeMethodParameterChecks(method, paramIndex, checks);
1095            }
1096    
1097            /**
1098             * Registers post condition checks to a method's return value
1099             * 
1100             * @param method
1101             * @param checks
1102             * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
1103             * @throws InvalidConfigurationException if the declaring class is not guarded
1104             */
1105            public void removeChecks(final Method method, final PostCheck... checks) throws InvalidConfigurationException
1106            {
1107                    Assert.argumentNotNull("method", method);
1108                    Assert.argumentNotEmpty("checks", checks);
1109    
1110                    getClassChecks(method.getDeclaringClass()).removeMethodPostChecks(method, checks);
1111            }
1112    
1113            /**
1114             * Registers pre condition checks to a method's return value
1115             * 
1116             * @param method
1117             * @param checks
1118             * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
1119             * @throws InvalidConfigurationException if the declaring class is not guarded
1120             */
1121            public void removeChecks(final Method method, final PreCheck... checks) throws InvalidConfigurationException
1122            {
1123                    Assert.argumentNotNull("method", method);
1124                    Assert.argumentNotEmpty("checks", checks);
1125    
1126                    getClassChecks(method.getDeclaringClass()).removeMethodPreChecks(method, checks);
1127            }
1128    
1129            /**
1130             * Removes the given listener
1131             * 
1132             * @param listener
1133             * @return <code>true</code> if the listener was registered
1134             * @throws IllegalArgumentException if <code>listener == null</code>
1135             */
1136            public boolean removeListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException
1137            {
1138                    Assert.argumentNotNull("listener", listener);
1139    
1140                    return listeners.remove(listener);
1141            }
1142    
1143            /**
1144             * Removes the given listener
1145             * 
1146             * @param listener
1147             * @param guardedClass guarded class or interface
1148             * @return <code>true</code> if the listener was registered
1149             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
1150             */
1151            public boolean removeListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass)
1152                            throws IllegalArgumentException
1153            {
1154                    Assert.argumentNotNull("listener", listener);
1155                    Assert.argumentNotNull("guardedClass", guardedClass);
1156    
1157                    final Set<ConstraintsViolatedListener> currentListeners = listenersByClass.get(guardedClass);
1158    
1159                    return currentListeners == null ? false : currentListeners.remove(listener);
1160            }
1161    
1162            /**
1163             * Removes the given listener
1164             * 
1165             * @param listener
1166             * @param guardedObject
1167             * @return <code>true</code> if the listener was registered
1168             * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
1169             */
1170            public boolean removeListener(final ConstraintsViolatedListener listener, final Object guardedObject)
1171                            throws IllegalArgumentException
1172            {
1173                    Assert.argumentNotNull("listener", listener);
1174                    Assert.argumentNotNull("guardedObject", guardedObject);
1175    
1176                    final Set<ConstraintsViolatedListener> currentListeners = listenersByObject.get(guardedObject);
1177    
1178                    return currentListeners == null ? false : currentListeners.remove(listener);
1179            }
1180    
1181            /**
1182             * If set to false OVal's programming by contract features are disabled and constraints are not checked
1183             * automatically during runtime.
1184             * 
1185             * @param isActivated the isActivated to set
1186             */
1187            public void setActivated(final boolean isActivated)
1188            {
1189                    this.isActivated = isActivated;
1190            }
1191    
1192            /**
1193             * Specifies if invariants are checked prior and after calls to non-private methods and constructors.
1194             * 
1195             * @param isEnabled the isInvariantsEnabled to set
1196             */
1197            public void setInvariantsEnabled(final boolean isEnabled)
1198            {
1199                    isInvariantsEnabled = isEnabled;
1200            }
1201    
1202            /**
1203             * Specifies if invariants are checked prior and after calls to non-private methods and constructors.
1204             * 
1205             * @param guardedClass the guarded class to turn on/off the invariant checking
1206             * @param isEnabled the isEnabled to set
1207             */
1208            public void setInvariantsEnabled(final Class< ? > guardedClass, final boolean isEnabled)
1209            {
1210                    getClassChecks(guardedClass).isCheckInvariants = isEnabled;
1211            }
1212    
1213            /**
1214             * @param parameterNameResolver the parameterNameResolver to set, cannot be null
1215             * @throws IllegalArgumentException if <code>parameterNameResolver == null</code>
1216             */
1217            public void setParameterNameResolver(final ParameterNameResolver parameterNameResolver)
1218                            throws IllegalArgumentException
1219            {
1220                    Assert.argumentNotNull("parameterNameResolver", parameterNameResolver);
1221    
1222                    this.parameterNameResolver.setDelegate(parameterNameResolver);
1223            }
1224    
1225            /**
1226             * @param isEnabled the isEnabled to set
1227             */
1228            public void setPostConditionsEnabled(final boolean isEnabled)
1229            {
1230                    isPostConditionsEnabled = isEnabled;
1231            }
1232    
1233            /**
1234             * @param isEnabled the isEnabled to set
1235             */
1236            public void setPreConditionsEnabled(final boolean isEnabled)
1237            {
1238                    isPreConditionsEnabled = isEnabled;
1239            }
1240    
1241            /**
1242             * Validates the give arguments against the defined constructor parameter constraints.<br>
1243             * 
1244             * @return null if no violation, otherwise a list
1245             * @throws ValidationFailedException
1246             */
1247            protected List<ConstraintViolation> validateConstructorParameters(final Object validatedObject,
1248                            final Constructor< ? > constructor, final Object[] argsToValidate) throws ValidationFailedException
1249            {
1250                    // create required objects for this validation cycle
1251                    final List<ConstraintViolation> violations = getCollectionFactory().createList();
1252                    currentViolations.get().add(violations);
1253                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1254    
1255                    try
1256                    {
1257                            final ClassChecks cc = getClassChecks(constructor.getDeclaringClass());
1258                            final Map<Integer, ParameterChecks> parameterChecks = cc.checksForConstructorParameters.get(constructor);
1259    
1260                            // if no parameter checks exist just return null
1261                            if (parameterChecks == null) return null;
1262    
1263                            final String[] parameterNames = parameterNameResolver.getParameterNames(constructor);
1264    
1265                            for (int i = 0; i < argsToValidate.length; i++)
1266                            {
1267                                    final ParameterChecks checks = parameterChecks.get(i);
1268    
1269                                    if (checks != null && checks.hasChecks())
1270                                    {
1271                                            final Object valueToValidate = argsToValidate[i];
1272                                            final ConstructorParameterContext context = new ConstructorParameterContext(constructor, i,
1273                                                            parameterNames[i]);
1274    
1275                                            _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations);
1276                                    }
1277                            }
1278                            return violations.size() == 0 ? null : violations;
1279                    }
1280                    catch (final OValException ex)
1281                    {
1282                            throw new ValidationFailedException("Validation of constructor parameters failed. Constructor: "
1283                                            + constructor + " Validated object: " + validatedObject.getClass().getName() + "@"
1284                                            + Integer.toHexString(validatedObject.hashCode()), ex);
1285                    }
1286                    finally
1287                    {
1288                            // remove the validation cycle related objects
1289                            currentViolations.get().removeLast();
1290                            currentlyValidatedObjects.get().removeLast();
1291                    }
1292            }
1293    
1294            /**
1295             * {@inheritDoc}
1296             */
1297            @Override
1298            protected void validateInvariants(final Object guardedObject, final List<ConstraintViolation> violations,
1299                            final String[] profiles) throws IllegalArgumentException, ValidationFailedException
1300            {
1301                    // create a new set for this validation cycle
1302                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1303                    try
1304                    {
1305                            super.validateInvariants(guardedObject, violations, profiles);
1306                    }
1307                    finally
1308                    {
1309                            // remove the set
1310                            currentlyValidatedObjects.get().removeLast();
1311                    }
1312            }
1313    
1314            /**
1315             * Validates the pre conditions for a method call.<br>
1316             * 
1317             * @throws ValidationFailedException
1318             */
1319            protected void validateMethodParameters(final Object validatedObject, final Method method, final Object[] args,
1320                            final List<ConstraintViolation> violations) throws ValidationFailedException
1321            {
1322                    // create a new set for this validation cycle
1323                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1324                    try
1325                    {
1326                            final ClassChecks cc = getClassChecks(method.getDeclaringClass());
1327                            final Map<Integer, ParameterChecks> parameterChecks = cc.checksForMethodParameters.get(method);
1328    
1329                            if (parameterChecks == null) return;
1330    
1331                            final String[] parameterNames = parameterNameResolver.getParameterNames(method);
1332    
1333                            /*
1334                             * parameter constraints validation
1335                             */
1336                            if (parameterNames.length > 0) for (int i = 0; i < args.length; i++)
1337                            {
1338                                    final ParameterChecks checks = parameterChecks.get(i);
1339    
1340                                    if (checks != null && checks.checks.size() > 0)
1341                                    {
1342                                            final Object valueToValidate = args[i];
1343                                            final MethodParameterContext context = new MethodParameterContext(method, i, parameterNames[i]);
1344    
1345                                            _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations);
1346                                    }
1347                            }
1348                    }
1349                    catch (final OValException ex)
1350                    {
1351                            throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method
1352                                            + " Validated object: " + validatedObject, ex);
1353                    }
1354                    finally
1355                    {
1356                            // remove the set
1357                            currentlyValidatedObjects.get().removeLast();
1358                    }
1359            }
1360    
1361            /**
1362             * Validates the post conditions for a method call.<br>
1363             * 
1364             * @throws ValidationFailedException
1365             */
1366            protected void validateMethodPost(final Object validatedObject, final Method method, final Object[] args,
1367                            final Object returnValue, final Map<PostCheck, Object> oldValues, final List<ConstraintViolation> violations)
1368                            throws ValidationFailedException
1369            {
1370                    final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);
1371    
1372                    /*
1373                     *  avoid circular references
1374                     */
1375                    if (currentlyCheckingPostConditions.get().contains(key)) return;
1376    
1377                    currentlyCheckingPostConditions.get().add(key);
1378                    try
1379                    {
1380                            final ClassChecks cc = getClassChecks(method.getDeclaringClass());
1381                            final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method);
1382    
1383                            if (postChecks == null) return;
1384    
1385                            final String[] parameterNames = parameterNameResolver.getParameterNames(method);
1386                            final boolean hasParameters = parameterNames.length > 0;
1387    
1388                            final MethodExitContext context = ContextCache.getMethodExitContext(method);
1389    
1390                            for (final PostCheck check : postChecks)
1391                            {
1392                                    if (!isAnyProfileEnabled(check.getProfiles(), null)) continue;
1393    
1394                                    final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLanguage());
1395                                    final Map<String, Object> values = getCollectionFactory().createMap();
1396                                    values.put("_this", validatedObject);
1397                                    values.put("_returns", returnValue);
1398                                    values.put("_old", oldValues.get(check));
1399                                    if (hasParameters)
1400                                    {
1401                                            values.put("_args", args);
1402                                            for (int i = 0; i < args.length; i++)
1403                                                    values.put(parameterNames[i], args[i]);
1404                                    }
1405                                    else
1406                                            values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
1407    
1408                                    if (!eng.evaluateAsBoolean(check.getExpression(), values))
1409                                    {
1410                                            final Map<String, String> messageVariables = getCollectionFactory().createMap(2);
1411                                            messageVariables.put("expression", check.getExpression());
1412                                            final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables);
1413    
1414                                            violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context));
1415                                    }
1416                            }
1417                    }
1418                    catch (final OValException ex)
1419                    {
1420                            throw new ValidationFailedException("Method post conditions validation failed. Method: " + method
1421                                            + " Validated object: " + validatedObject, ex);
1422                    }
1423                    finally
1424                    {
1425                            currentlyCheckingPostConditions.get().remove(key);
1426                    }
1427            }
1428    
1429            /**
1430             * Validates the @Pre conditions for a method call.<br>
1431             * 
1432             * @throws ValidationFailedException
1433             */
1434            protected void validateMethodPre(final Object validatedObject, final Method method, final Object[] args,
1435                            final List<ConstraintViolation> violations) throws ValidationFailedException
1436            {
1437                    final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);
1438    
1439                    /*
1440                     *  avoid circular references
1441                     */
1442                    if (currentlyCheckingPreConditions.get().contains(key)) return;
1443    
1444                    currentlyCheckingPreConditions.get().add(key);
1445                    try
1446                    {
1447                            final ClassChecks cc = getClassChecks(method.getDeclaringClass());
1448                            final Set<PreCheck> preChecks = cc.checksForMethodsPreExecution.get(method);
1449    
1450                            if (preChecks == null) return;
1451    
1452                            final String[] parameterNames = parameterNameResolver.getParameterNames(method);
1453                            final boolean hasParameters = parameterNames.length > 0;
1454    
1455                            final MethodEntryContext context = ContextCache.getMethodEntryContext(method);
1456    
1457                            for (final PreCheck check : preChecks)
1458                            {
1459                                    if (!isAnyProfileEnabled(check.getProfiles(), null)) continue;
1460    
1461                                    final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLanguage());
1462                                    final Map<String, Object> values = getCollectionFactory().createMap();
1463                                    values.put("_this", validatedObject);
1464                                    if (hasParameters)
1465                                    {
1466                                            values.put("_args", args);
1467                                            for (int i = 0; i < args.length; i++)
1468                                                    values.put(parameterNames[i], args[i]);
1469                                    }
1470                                    else
1471                                            values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
1472    
1473                                    if (!eng.evaluateAsBoolean(check.getExpression(), values))
1474                                    {
1475                                            final Map<String, String> messageVariables = getCollectionFactory().createMap(2);
1476                                            messageVariables.put("expression", check.getExpression());
1477                                            final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables);
1478    
1479                                            violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context));
1480                                    }
1481                            }
1482                    }
1483                    catch (final OValException ex)
1484                    {
1485                            throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method
1486                                            + " Validated object: " + validatedObject, ex);
1487                    }
1488                    finally
1489                    {
1490                            currentlyCheckingPreConditions.get().remove(key);
1491                    }
1492            }
1493    
1494            /**
1495             * Validates the return value checks for a method call.<br>
1496             * 
1497             * @throws ValidationFailedException
1498             */
1499            protected void validateMethodReturnValue(final Object validatedObject, final Method method,
1500                            final Object returnValue, final List<ConstraintViolation> violations) throws ValidationFailedException
1501            {
1502                    final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);
1503    
1504                    /*
1505                     *  avoid circular references, e.g.
1506                     *
1507                     *  private String name;
1508                     *  
1509                     *  @Assert("_this.name != null", lang="groovy")
1510                     *  public String getName { return name; }
1511                     *  
1512                     *  => Groovy will invoke the getter to return the value, invocations of the getter will trigger the validation of the method return values again, including the @Assert constraint
1513                     */
1514                    if (currentlyCheckingMethodReturnValues.get().contains(key)) return;
1515    
1516                    currentlyCheckingMethodReturnValues.get().add(key);
1517                    // create a new set for this validation cycle
1518                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1519                    try
1520                    {
1521                            final ClassChecks cc = getClassChecks(method.getDeclaringClass());
1522                            final Collection<Check> returnValueChecks = cc.checksForMethodReturnValues.get(method);
1523    
1524                            if (returnValueChecks == null || returnValueChecks.size() == 0) return;
1525    
1526                            final MethodReturnValueContext context = ContextCache.getMethodReturnValueContext(method);
1527    
1528                            for (final Check check : returnValueChecks)
1529                                    checkConstraint(violations, check, validatedObject, returnValue, context, null, false);
1530                    }
1531                    catch (final OValException ex)
1532                    {
1533                            throw new ValidationFailedException("Method post conditions validation failed. Method: " + method
1534                                            + " Validated object: " + validatedObject, ex);
1535                    }
1536                    finally
1537                    {
1538                            currentlyCheckingMethodReturnValues.get().remove(key);
1539    
1540                            // remove the set
1541                            currentlyValidatedObjects.get().removeLast();
1542                    }
1543            }
1544    }