/*********************************************************************
 * Copyright 2005-2020 by Sebastian Thomschke and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *********************************************************************/
package net.sf.oval.guard;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.oval.Check;
import net.sf.oval.CheckExclusion;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import net.sf.oval.configuration.Configurer;
import net.sf.oval.context.ConstructorParameterContext;
import net.sf.oval.context.MethodEntryContext;
import net.sf.oval.context.MethodExitContext;
import net.sf.oval.context.MethodParameterContext;
import net.sf.oval.context.MethodReturnValueContext;
import net.sf.oval.context.OValContext;
import net.sf.oval.exception.ConstraintsViolatedException;
import net.sf.oval.exception.InvalidConfigurationException;
import net.sf.oval.exception.OValException;
import net.sf.oval.exception.ValidationFailedException;
import net.sf.oval.expression.ExpressionLanguage;
import net.sf.oval.internal.ClassChecks;
import net.sf.oval.internal.ContextCache;
import net.sf.oval.internal.Log;
import net.sf.oval.internal.ParameterChecks;
import net.sf.oval.internal.util.ArrayUtils;
import net.sf.oval.internal.util.Assert;
import net.sf.oval.internal.util.ConcurrentMultiValueMap;
import net.sf.oval.internal.util.IdentitySet;
import net.sf.oval.internal.util.Invocable;
import net.sf.oval.internal.util.ReflectionUtils;
import net.sf.oval.internal.util.ThreadLocalList;
import net.sf.oval.internal.util.ThreadLocalWeakHashMap;

/**
 * Extended version of the validator to realize programming by contract.
 *
 * @author Sebastian Thomschke
 */
public class Guard extends Validator {
   /**
    * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions
    */
   protected static final class GuardMethodPreResult {
      protected final boolean checkInvariants;
      protected final Method method;
      protected final Object[] args;
      protected final ClassChecks cc;
      protected final List<ConstraintViolation> violations;
      protected final Map<PostCheck, Object> postCheckOldValues;
      protected final Object guardedObject;

      protected GuardMethodPreResult( //
         final Object guardedObject, //
         final Method method, //
         final Object[] args, //
         final ClassChecks cc, //
         final boolean checkInvariants, //
         final Map<PostCheck, Object> postCheckOldValues, //
         final List<ConstraintViolation> violations //
      ) {
         this.guardedObject = guardedObject;
         this.method = method;
         this.args = args;
         this.cc = cc;
         this.checkInvariants = checkInvariants;
         this.postCheckOldValues = postCheckOldValues;
         this.violations = violations;
      }
   }

   /**
    * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions
    */
   protected static final GuardMethodPreResult DO_NOT_PROCEED = new GuardMethodPreResult(null, null, null, null, false, null, null);

   private static final Log LOG = Log.getLog(Guard.class);

   /**
    * string based on validated object hashcode + method hashcode for currently validated method return values
    */
   private static final ThreadLocalList<String> CURRENTLY_CHECKED_METHOD_RETURN_VALUES = new ThreadLocalList<>();

   /**
    * string based on validated object hashcode + method hashcode for currently validated method pre-conditions
    */
   private static final ThreadLocalList<String> CURRENTLY_CHECKED_PRE_CONDITIONS = new ThreadLocalList<>();

   /**
    * string based on validated object hashcode + method hashcode for currently validated method post-conditions
    */
   private static final ThreadLocalList<String> CURRENTLY_CHECKED_POST_CONDITIONS = new ThreadLocalList<>();

   private boolean isActivated = true;
   private boolean isInvariantsEnabled = true;
   private boolean isPreConditionsEnabled = true;
   private boolean isPostConditionsEnabled = true;

   /**
    * Flag that indicates if any listeners were registered at any time. Used for improved performance.
    */
   private boolean isListenersFeatureUsed = false;
   /**
    * Flag that indicates if exception suppressing was used at any time. Used for improved performance.
    */
   private boolean isProbeModeFeatureUsed = false;

   private final Set<ConstraintsViolatedListener> listeners = new IdentitySet<>(4);
   private final ConcurrentMultiValueMap<Class<?>, ConstraintsViolatedListener> listenersByClass = ConcurrentMultiValueMap.create();
   private final ConcurrentMultiValueMap<Object, ConstraintsViolatedListener> listenersByObject = ConcurrentMultiValueMap.create();

   /**
    * Objects for OVal suppresses occurring ConstraintViolationExceptions for pre-condition violations on setter methods
    * for the current thread.
    */
   private final ThreadLocalWeakHashMap<Object, ProbeModeListener> objectsInProbeMode = new ThreadLocalWeakHashMap<>();

   /**
    * Constructs a new guard object and uses a new instance of AnnotationsConfigurer
    */
   public Guard() {
   }

   public Guard(final Collection<Configurer> configurers) {
      super(configurers);
   }

   public Guard(final Configurer... configurers) {
      super(configurers);
   }

   private List<CheckExclusion> _getActiveExclusions(final Set<CheckExclusion> exclusions) {
      final List<CheckExclusion> activeExclusions = new LinkedList<>(exclusions);
      for (final Iterator<CheckExclusion> it = activeExclusions.iterator(); it.hasNext();) {
         final CheckExclusion exclusion = it.next();
         if (!isAnyProfileEnabled(exclusion.getProfiles(), null)) {
            it.remove();
         }
      }
      return activeExclusions.size() == 0 ? null : activeExclusions;
   }

   private void _validateParameterChecks(final ParameterChecks checks, final Object validatedObject, final Object valueToValidate, final OValContext context,
      final List<ConstraintViolation> violations) {
      // determine the active exclusions based on the active profiles
      final List<CheckExclusion> activeExclusions = checks.hasExclusions() ? _getActiveExclusions(checks.checkExclusions) : null;

      // check the constraints
      for (final Check check : checks.checks) {
         boolean skip = false;

         if (activeExclusions != null) {
            for (final CheckExclusion exclusion : activeExclusions)
               if (exclusion.isActive(validatedObject, valueToValidate, this) && exclusion.isCheckExcluded(check, validatedObject, valueToValidate, context,
                  this)) {
                  // skip if this check should be excluded
                  skip = true;
                  continue;
               }
         }
         if (!skip) {
            checkConstraint(violations, check, validatedObject, valueToValidate, context, null, false);
         }
      }
   }

   /**
    * Registers constraint checks for the given constructor parameter
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void addCheckExclusions(final Constructor<?> ctor, final int paramIndex, final CheckExclusion... exclusions) throws IllegalArgumentException,
      InvalidConfigurationException {
      Assert.argumentNotNull("ctor", ctor);
      Assert.argumentNotEmpty("exclusions", exclusions);

      getClassChecks(ctor.getDeclaringClass()).addConstructorParameterCheckExclusions(ctor, paramIndex, exclusions);
   }

   /**
    * Registers constraint checks for the given method parameter
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void addCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions) throws IllegalArgumentException,
      InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("exclusions", exclusions);

      getClassChecks(method.getDeclaringClass()).addMethodParameterCheckExclusions(method, paramIndex, exclusions);
   }

   /**
    * Registers constraint checks for the given constructor parameter
    *
    * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is
    *            empty
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void addChecks(final Constructor<?> ctor, final int paramIndex, final Check... checks) throws IllegalArgumentException,
      InvalidConfigurationException {
      Assert.argumentNotNull("ctor", ctor);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(ctor.getDeclaringClass()).addConstructorParameterChecks(ctor, paramIndex, checks);
   }

   /**
    * Registers constraint checks for the given method's return value
    *
    * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if method does not declare a return type (void), or the declaring class is
    *            not guarded
    */
   @Override
   public void addChecks(final Method method, final Check... checks) throws IllegalArgumentException, InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).addMethodReturnValueChecks(method, null, checks);
   }

   /**
    * Registers constraint checks for the given method parameter
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void addChecks(final Method method, final int paramIndex, final Check... checks) throws IllegalArgumentException, InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).addMethodParameterChecks(method, paramIndex, checks);
   }

   /**
    * Registers post condition checks to a method's return value
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded
    */
   public void addChecks(final Method method, final PostCheck... checks) throws IllegalArgumentException, InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).addMethodPostChecks(method, checks);
   }

   /**
    * Registers pre condition checks to a method's return value
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded
    */
   public void addChecks(final Method method, final PreCheck... checks) throws IllegalArgumentException, InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).addMethodPreChecks(method, checks);
   }

   /**
    * Registers the given listener for <b>all</b> thrown ConstraintViolationExceptions
    *
    * @param listener the listener to register
    * @return <code>true</code> if the listener was not yet registered
    * @throws IllegalArgumentException if <code>listener == null</code>
    */
   public boolean addListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);

      isListenersFeatureUsed = true;
      return listeners.add(listener);
   }

   /**
    * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given class
    *
    * @param listener the listener to register
    * @param guardedClass guarded class or interface
    * @return <code>true</code> if the listener was not yet registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
    */
   public boolean addListener(final ConstraintsViolatedListener listener, final Class<?> guardedClass) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedClass", guardedClass);

      isListenersFeatureUsed = true;
      return listenersByClass.add(guardedClass, listener);
   }

   /**
    * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given object
    *
    * @param listener the listener to register
    * @return <code>true</code> if the listener was not yet registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
    */
   public boolean addListener(final ConstraintsViolatedListener listener, final Object guardedObject) {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedObject", guardedObject);

      isListenersFeatureUsed = true;
      return listenersByObject.add(guardedObject, listener);
   }

   /**
    * Evaluates the old expression
    *
    * @return null if no violation, otherwise a list
    */
   protected Map<PostCheck, Object> calculateMethodPostOldValues(final Object validatedObject, final Method method, final Object[] args)
      throws ValidationFailedException {
      try {
         final ClassChecks cc = getClassChecks(method.getDeclaringClass());
         final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method);

         // shortcut: check if any post checks for this method exist
         if (postChecks == null)
            return null;

         final String[] parameterNames = parameterNameResolver.getParameterNames(method);
         final boolean hasParameters = parameterNames.length > 0;

         final Map<PostCheck, Object> oldValues = getCollectionFactory().createMap(postChecks.size());

         for (final PostCheck check : postChecks)
            if (isAnyProfileEnabled(check.getProfiles(), null) && check.getOld() != null && check.getOld().length() > 0) {
               final ExpressionLanguage el = expressionLanguageRegistry.getExpressionLanguage(check.getLang());
               final Map<String, Object> values = getCollectionFactory().createMap();
               values.put("_this", validatedObject);
               if (hasParameters) {
                  values.put("_args", args);
                  for (int i = 0; i < args.length; i++) {
                     values.put(parameterNames[i], args[i]);
                  }
               } else {
                  values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
               }

               oldValues.put(check, el.evaluate(check.getOld(), values));
            }

         return oldValues;
      } catch (final OValException ex) {
         throw new ValidationFailedException("Method post conditions validation failed. Method: " + method + " Validated object: " + validatedObject, ex);
      }
   }

   /**
    * Disables the probe mode for the given object in the current thread.
    *
    * @param guardedObject the object to disable the probe mode for
    * @throws IllegalArgumentException if <code>guardedObject == null</code>
    * @throws IllegalStateException in case probe mode was not enabled for the given object
    */
   public ProbeModeListener disableProbeMode(final Object guardedObject) throws IllegalArgumentException, IllegalStateException {
      Assert.argumentNotNull("guardedObject", guardedObject);

      return objectsInProbeMode.get().remove(guardedObject);
   }

   /**
    * Enables the probe mode for the given object in the current thread. In probe mode calls to methods of an
    * object are not actually executed. OVal only validates method pre-conditions and notifies
    * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will
    * return null.
    *
    * @param guardedObject the object to enable the probe mode for
    * @throws IllegalArgumentException if <code>guardedObject == null</code>
    * @throws IllegalStateException if the probe mode is already enabled
    */
   public void enableProbeMode(final Object guardedObject) throws IllegalArgumentException, IllegalStateException {
      Assert.argumentNotNull("guardedObject", guardedObject);

      if (guardedObject instanceof Class<?>) {
         LOG.warn("Enabling probe mode for a class looks like a programming error. Class: {1}", guardedObject);
      }
      isProbeModeFeatureUsed = true;

      if (objectsInProbeMode.get().get(guardedObject) != null)
         throw new IllegalStateException("The object is already in probe mode.");

      objectsInProbeMode.get().put(guardedObject, new ProbeModeListener(guardedObject));
   }

   /**
    * Returns the registers constraint pre condition checks for the given method parameter
    *
    * @throws IllegalArgumentException if <code>method == null</code>
    */
   public Check[] getChecks(final Method method, final int paramIndex) throws InvalidConfigurationException {
      Assert.argumentNotNull("method", method);

      final ClassChecks cc = getClassChecks(method.getDeclaringClass());

      final Map<Integer, ParameterChecks> checks = cc.checksForMethodParameters.get(method);
      if (checks == null)
         return null;

      final ParameterChecks paramChecks = checks.get(paramIndex);
      return paramChecks == null ? null : paramChecks.checks.toArray(new Check[checks.size()]);
   }

   /**
    * Returns the registered post condition checks for the given method
    *
    * @throws IllegalArgumentException if <code>method == null</code>
    */
   public PostCheck[] getChecksPost(final Method method) throws IllegalArgumentException {
      Assert.argumentNotNull("method", method);

      final ClassChecks cc = getClassChecks(method.getDeclaringClass());

      final Set<PostCheck> checks = cc.checksForMethodsPostExcecution.get(method);
      return checks == null ? null : checks.toArray(new PostCheck[checks.size()]);
   }

   /**
    * Returns the registered pre condition checks for the given method.
    *
    * @throws IllegalArgumentException if <code>method == null</code>
    */
   public PreCheck[] getChecksPre(final Method method) throws IllegalArgumentException {
      Assert.argumentNotNull("method", method);

      final ClassChecks cc = getClassChecks(method.getDeclaringClass());

      final Set<PreCheck> checks = cc.checksForMethodsPreExecution.get(method);
      return checks == null ? null : checks.toArray(new PreCheck[checks.size()]);
   }

   public ParameterNameResolver getParameterNameResolver() {
      return parameterNameResolver;
   }

   /**
    * This method is provided for use by guard aspects.
    */
   protected void guardConstructorPost(final Object guardedObject, final Constructor<?> ctor, @SuppressWarnings("unused") final Object[] args)
      throws ConstraintsViolatedException, ValidationFailedException {
      if (!isActivated)
         return;

      final ClassChecks cc = getClassChecks(ctor.getDeclaringClass());

      // check invariants
      if (isInvariantsEnabled && cc.isCheckInvariants || cc.methodsWithCheckInvariantsPost.contains(ctor)) {
         final List<ConstraintViolation> violations = getCollectionFactory().createList();
         currentViolations.get().add(violations);
         try {
            validateInvariants(guardedObject, violations, null);
         } catch (final ValidationFailedException ex) {
            throw translateException(ex);
         } finally {
            currentViolations.get().removeLast();
         }

         if (violations.size() > 0) {
            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
            if (isListenersFeatureUsed) {
               notifyListeners(guardedObject, violationException);
            }

            throw translateException(violationException);
         }
      }
   }

   /**
    * This method is provided for use by guard aspects.
    *
    * @throws ConstraintsViolatedException if anything precondition is not satisfied
    */
   protected void guardConstructorPre(final Object guardedObject, final Constructor<?> ctor, final Object[] args) throws ConstraintsViolatedException,
      ValidationFailedException {
      if (!isActivated)
         return;

      // constructor parameter validation
      if (isPreConditionsEnabled && args.length > 0) {
         final List<ConstraintViolation> violations;
         try {
            violations = validateConstructorParameters(guardedObject, ctor, args);
         } catch (final ValidationFailedException ex) {
            throw translateException(ex);
         }

         if (violations != null) {
            final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
            if (isListenersFeatureUsed) {
               notifyListeners(guardedObject, violationException);
            }

            throw translateException(violationException);
         }
      }
   }

   /**
    * This method is provided for use by guard aspects.
    *
    * @return The method return value or null if the guarded object is in probe mode.
    * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe mode.
    */
   // CHECKSTYLE:IGNORE IllegalThrow FOR NEXT LINE
   protected Object guardMethod(Object guardedObject, final Method method, final Object[] args, final Invocable invocable) throws Throwable {
      if (!isActivated)
         return invocable.invoke();

      final ClassChecks cc = getClassChecks(method.getDeclaringClass());

      final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method);

      // if static method use the declaring class as guardedObject
      if (guardedObject == null && ReflectionUtils.isStatic(method)) {
         guardedObject = method.getDeclaringClass();
      }

      final List<ConstraintViolation> violations = getCollectionFactory().createList();
      currentViolations.get().add(violations);

      try {
         // check invariants
         if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method)) {
            validateInvariants(guardedObject, violations, null);
         }

         if (isPreConditionsEnabled) {
            // method parameter validation
            if (args.length > 0 && violations.size() == 0) {
               validateMethodParameters(guardedObject, method, args, violations);
            }

            // @Pre validation
            if (violations.size() == 0) {
               validateMethodPre(guardedObject, method, args, violations);
            }
         }
      } catch (final ValidationFailedException ex) {
         throw translateException(ex);
      } finally {
         currentViolations.get().removeLast();
      }

      final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null;
      if (pml != null) {
         pml.onMethodCall(method, args);
      }

      if (violations.size() > 0) {
         final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
         if (isListenersFeatureUsed) {
            notifyListeners(guardedObject, violationException);
         }

         // don't throw an exception if the method is a setter and suppressing for precondition is enabled
         if (pml != null) {
            pml.onConstraintsViolatedException(violationException);
            return null;
         }

         throw translateException(violationException);
      }

      // abort method execution if in probe mode
      if (pml != null)
         return null;

      final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args);

      final Object returnValue = invocable.invoke();

      currentViolations.get().add(violations);

      try {
         // check invariants if executed method is not private
         if (checkInvariants || cc.methodsWithCheckInvariantsPost.contains(method)) {
            validateInvariants(guardedObject, violations, null);
         }

         if (isPostConditionsEnabled) {

            // method return value
            if (violations.size() == 0) {
               validateMethodReturnValue(guardedObject, method, returnValue, violations);
            }

            // @Post
            if (violations.size() == 0) {
               validateMethodPost(guardedObject, method, args, returnValue, postCheckOldValues, violations);
            }
         }
      } catch (final ValidationFailedException ex) {
         throw translateException(ex);
      } finally {
         currentViolations.get().removeLast();
      }

      if (violations.size() > 0) {
         final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
         if (isListenersFeatureUsed) {
            notifyListeners(guardedObject, violationException);
         }

         throw translateException(violationException);
      }

      return returnValue;
   }

   /**
    * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions,
    * then {@link #guardMethod(Object, Method, Object[], Invocable)} can be used instead
    *
    * This method is provided for use by guard aspects.
    *
    * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe mode.
    */
   protected void guardMethodPost(final Object returnValue, final GuardMethodPreResult preResult) throws ConstraintsViolatedException,
      ValidationFailedException {
      if (!isActivated)
         return;

      try {
         // check invariants if executed method is not private
         if (preResult.checkInvariants || preResult.cc.methodsWithCheckInvariantsPost.contains(preResult.method)) {
            validateInvariants(preResult.guardedObject, preResult.violations, null);
         }

         if (isPostConditionsEnabled) {

            // method return value
            if (preResult.violations.size() == 0) {
               validateMethodReturnValue(preResult.guardedObject, preResult.method, returnValue, preResult.violations);
            }

            // @Post
            if (preResult.violations.size() == 0) {
               validateMethodPost(preResult.guardedObject, preResult.method, preResult.args, returnValue, preResult.postCheckOldValues, preResult.violations);
            }
         }
      } catch (final ValidationFailedException ex) {
         throw translateException(ex);
      }

      if (preResult.violations.size() > 0) {
         final ConstraintsViolatedException violationException = new ConstraintsViolatedException(preResult.violations);
         if (isListenersFeatureUsed) {
            notifyListeners(preResult.guardedObject, violationException);
         }

         throw translateException(violationException);
      }
   }

   /**
    * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions, then {@link #guardMethod(Object, Method, Object[], Invocable)} can be
    * used instead
    *
    * This method is provided for use by guard aspects.
    *
    * @return Null if method guarding is deactivated or a result object that needs to be passed to {@link #guardMethodPost(Object, GuardMethodPreResult)}
    * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe mode.
    */
   protected GuardMethodPreResult guardMethodPre(Object guardedObject, final Method method, final Object[] args) throws ConstraintsViolatedException,
      ValidationFailedException {
      if (!isActivated)
         return null;

      final ClassChecks cc = getClassChecks(method.getDeclaringClass());

      final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method);

      // if static method use the declaring class as guardedObject
      if (guardedObject == null && ReflectionUtils.isStatic(method)) {
         guardedObject = method.getDeclaringClass();
      }

      final List<ConstraintViolation> violations = getCollectionFactory().createList();
      currentViolations.get().add(violations);

      try {
         // check invariants
         if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method)) {
            validateInvariants(guardedObject, violations, null);
         }

         if (isPreConditionsEnabled) {
            // method parameter validation
            if (args.length > 0 && violations.size() == 0) {
               validateMethodParameters(guardedObject, method, args, violations);
            }

            // @Pre validation
            if (violations.size() == 0) {
               validateMethodPre(guardedObject, method, args, violations);
            }
         }
      } catch (final ValidationFailedException ex) {
         throw translateException(ex);
      } finally {
         currentViolations.get().removeLast();
      }

      final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null;
      if (pml != null) {
         pml.onMethodCall(method, args);
      }

      if (violations.size() > 0) {
         final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations);
         if (isListenersFeatureUsed) {
            notifyListeners(guardedObject, violationException);
         }

         // don't throw an exception if the method is a setter and suppressing for precondition is enabled
         if (pml != null) {
            pml.onConstraintsViolatedException(violationException);
            return DO_NOT_PROCEED;
         }

         throw translateException(violationException);
      }

      // abort method execution if in probe mode
      if (pml != null)
         return DO_NOT_PROCEED;

      final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args);

      return new GuardMethodPreResult(guardedObject, method, args, cc, checkInvariants, postCheckOldValues, violations);
   }

   /**
    * @return <code>true</code> if the listener is registered
    * @throws IllegalArgumentException if <code>listener == null</code>
    */
   public boolean hasListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);

      return listeners.contains(listener);
   }

   /**
    * @param guardedClass guarded class or interface
    * @return <code>true</code> if the listener is registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
    */
   public boolean hasListener(final ConstraintsViolatedListener listener, final Class<?> guardedClass) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedClass", guardedClass);

      return listenersByClass.containsValue(guardedClass, listener);
   }

   /**
    * @return <code>true</code> if the listener is registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
    */
   public boolean hasListener(final ConstraintsViolatedListener listener, final Object guardedObject) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedObject", guardedObject);

      return listenersByObject.containsValue(guardedObject, listener);
   }

   public boolean isActivated() {
      return isActivated;
   }

   /**
    * Determines if the probe mode is enabled for the given object in the current thread. In probe mode calls to
    * methods of an object are not actually executed. OVal only validates method pre-conditions and notifies
    * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will
    * return null.
    *
    * @return true if exceptions are suppressed
    */
   public boolean isInProbeMode(final Object guardedObject) {
      // guardedObject may be null if isInProbeMode is called when validating pre conditions of a static method
      if (guardedObject == null)
         return false;

      return objectsInProbeMode.get().containsKey(guardedObject);
   }

   /**
    * Determines if invariants are checked prior and after every call to a non-private method or constructor.
    */
   public boolean isInvariantsEnabled() {
      return isInvariantsEnabled;
   }

   /**
    * Determines if invariants are checked prior and after every call to a non-private method or constructor.
    *
    * @param guardedClass the guarded class
    */
   public boolean isInvariantsEnabled(final Class<?> guardedClass) {
      return getClassChecks(guardedClass).isCheckInvariants;
   }

   public boolean isPostConditionsEnabled() {
      return isPostConditionsEnabled;
   }

   public boolean isPreConditionsEnabled() {
      return isPreConditionsEnabled;
   }

   /**
    * notifies all registered validation listener about the occurred constraint violation exception
    */
   protected void notifyListeners(final Object guardedObject, final ConstraintsViolatedException ex) {
      // happens for static methods
      if (guardedObject == null)
         return;

      final LinkedHashSet<ConstraintsViolatedListener> listenersToNotify = new LinkedHashSet<>();

      // get the object listeners
      listenersByObject.addAllTo(guardedObject, listenersToNotify);

      // get the class listeners
      listenersByClass.addAllTo(guardedObject.getClass(), listenersToNotify);

      // get the interface listeners
      for (final Class<?> iface : guardedObject.getClass().getInterfaces()) {
         listenersByClass.addAllTo(iface, listenersToNotify);
      }

      // get the global listeners
      listenersToNotify.addAll(listeners);

      // notify the listeners
      for (final ConstraintsViolatedListener listener : listenersToNotify) {
         try {
            listener.onConstraintsViolatedException(ex);
         } catch (final RuntimeException rex) {
            LOG.warn("Notifying listener '{1}' failed.", listener, rex);
         }
      }

   }

   /**
    * Removes constraint check exclusions from the given constructor parameter
    *
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void removeCheckExclusions(final Constructor<?> ctor, final int paramIndex, final CheckExclusion... exclusions) throws InvalidConfigurationException {
      Assert.argumentNotNull("ctor", ctor);
      Assert.argumentNotEmpty("exclusions", exclusions);

      getClassChecks(ctor.getDeclaringClass()).removeConstructorParameterCheckExclusions(ctor, paramIndex, exclusions);
   }

   /**
    * Removes constraint check exclusions from the given method parameter
    *
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void removeCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions) throws InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("exclusions", exclusions);

      getClassChecks(method.getDeclaringClass()).removeMethodParameterCheckExclusions(method, paramIndex, exclusions);
   }

   /**
    * Removes constraint checks from the given constructor parameter
    *
    * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
    */
   public void removeChecks(final Constructor<?> ctor, final int paramIndex, final Check... checks) throws InvalidConfigurationException {
      Assert.argumentNotNull("ctor", ctor);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(ctor.getDeclaringClass()).removeConstructorParameterChecks(ctor, paramIndex, checks);
   }

   /**
    * Removes constraint checks for the given method parameter
    *
    * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the parameterIndex is out of range
    */
   public void removeChecks(final Method method, final int paramIndex, final Check... checks) throws InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).removeMethodParameterChecks(method, paramIndex, checks);
   }

   /**
    * Registers post condition checks to a method's return value
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded
    */
   public void removeChecks(final Method method, final PostCheck... checks) throws InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).removeMethodPostChecks(method, checks);
   }

   /**
    * Registers pre condition checks to a method's return value
    *
    * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
    * @throws InvalidConfigurationException if the declaring class is not guarded
    */
   public void removeChecks(final Method method, final PreCheck... checks) throws InvalidConfigurationException {
      Assert.argumentNotNull("method", method);
      Assert.argumentNotEmpty("checks", checks);

      getClassChecks(method.getDeclaringClass()).removeMethodPreChecks(method, checks);
   }

   /**
    * Removes the given listener
    *
    * @return <code>true</code> if the listener was registered
    * @throws IllegalArgumentException if <code>listener == null</code>
    */
   public boolean removeListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);

      return listeners.remove(listener);
   }

   /**
    * Removes the given listener
    *
    * @param guardedClass guarded class or interface
    * @return <code>true</code> if the listener was registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
    */
   public boolean removeListener(final ConstraintsViolatedListener listener, final Class<?> guardedClass) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedClass", guardedClass);

      return listenersByClass.remove(guardedClass, listener);
   }

   /**
    * Removes the given listener
    *
    * @return <code>true</code> if the listener was registered
    * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
    */
   public boolean removeListener(final ConstraintsViolatedListener listener, final Object guardedObject) throws IllegalArgumentException {
      Assert.argumentNotNull("listener", listener);
      Assert.argumentNotNull("guardedObject", guardedObject);

      return listenersByObject.remove(guardedObject, listener);
   }

   /**
    * If set to false OVal's programming by contract features are disabled and constraints are not checked
    * automatically during runtime.
    */
   public void setActivated(final boolean isActivated) {
      this.isActivated = isActivated;
   }

   /**
    * Specifies if invariants are checked prior and after calls to non-private methods and constructors.
    */
   public void setInvariantsEnabled(final boolean isEnabled) {
      isInvariantsEnabled = isEnabled;
   }

   /**
    * Specifies if invariants are checked prior and after calls to non-private methods and constructors.
    *
    * @param guardedClass the guarded class to turn on/off the invariant checking
    */
   public void setInvariantsEnabled(final Class<?> guardedClass, final boolean isEnabled) {
      getClassChecks(guardedClass).isCheckInvariants = isEnabled;
   }

   /**
    * @param parameterNameResolver the parameterNameResolver to set, cannot be null
    * @throws IllegalArgumentException if <code>parameterNameResolver == null</code>
    */
   public void setParameterNameResolver(final ParameterNameResolver parameterNameResolver) throws IllegalArgumentException {
      Assert.argumentNotNull("parameterNameResolver", parameterNameResolver);

      this.parameterNameResolver.setDelegate(parameterNameResolver);
   }

   public void setPostConditionsEnabled(final boolean isEnabled) {
      isPostConditionsEnabled = isEnabled;
   }

   public void setPreConditionsEnabled(final boolean isEnabled) {
      isPreConditionsEnabled = isEnabled;
   }

   /**
    * Validates the give arguments against the defined constructor parameter constraints.<br>
    *
    * @return null if no violation, otherwise a list
    */
   protected List<ConstraintViolation> validateConstructorParameters(final Object validatedObject, final Constructor<?> constructor,
      final Object[] argsToValidate) throws ValidationFailedException {
      // create required objects for this validation cycle
      final List<ConstraintViolation> violations = getCollectionFactory().createList();
      currentViolations.get().add(violations);
      currentlyValidatedObjects.get().add(new IdentitySet<>(4));

      try {
         final ClassChecks cc = getClassChecks(constructor.getDeclaringClass());
         final Map<Integer, ParameterChecks> parameterChecks = cc.checksForConstructorParameters.get(constructor);

         // if no parameter checks exist just return null
         if (parameterChecks == null)
            return null;

         final String[] parameterNames = parameterNameResolver.getParameterNames(constructor);

         for (int i = 0; i < argsToValidate.length; i++) {
            final ParameterChecks checks = parameterChecks.get(i);

            if (checks != null && checks.hasChecks()) {
               final Object valueToValidate = argsToValidate[i];
               final ConstructorParameterContext context = new ConstructorParameterContext(constructor, i, parameterNames[i]);

               _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations);
            }
         }
         return violations.size() == 0 ? null : violations;
      } catch (final OValException ex) {
         throw new ValidationFailedException("Validation of constructor parameters failed. Constructor: " + constructor + " Validated object: "
            + validatedObject.getClass().getName() + "@" + Integer.toHexString(validatedObject.hashCode()), ex);
      } finally {
         // remove the validation cycle related objects
         currentViolations.get().removeLast();
         currentlyValidatedObjects.get().removeLast();
      }
   }

   @Override
   protected void validateInvariants(final Object guardedObject, final List<ConstraintViolation> violations, final String[] profiles)
      throws IllegalArgumentException, ValidationFailedException {
      if (currentlyValidatedObjects.get().size() > 0 && currentlyValidatedObjects.get().getLast().contains(guardedObject))
         // to prevent StackOverflowError
         return;

      // create a new set for this validation cycle
      currentlyValidatedObjects.get().add(new IdentitySet<>(4));
      try {
         super.validateInvariants(guardedObject, violations, profiles);
      } finally {
         // remove the set
         currentlyValidatedObjects.get().removeLast();
      }
   }

   /**
    * Validates the pre conditions for a method call.
    */
   protected void validateMethodParameters(final Object validatedObject, final Method method, final Object[] args, final List<ConstraintViolation> violations)
      throws ValidationFailedException {
      // create a new set for this validation cycle
      currentlyValidatedObjects.get().add(new IdentitySet<>(4));
      try {
         final ClassChecks cc = getClassChecks(method.getDeclaringClass());
         final Map<Integer, ParameterChecks> parameterChecks = cc.checksForMethodParameters.get(method);

         if (parameterChecks == null)
            return;

         final String[] parameterNames = parameterNameResolver.getParameterNames(method);

         /*
          * parameter constraints validation
          */
         if (parameterNames.length > 0) {
            for (int i = 0; i < args.length; i++) {
               final ParameterChecks checks = parameterChecks.get(i);

               if (checks != null && checks.checks.size() > 0) {
                  final Object valueToValidate = args[i];
                  final MethodParameterContext context = new MethodParameterContext(method, i, parameterNames[i]);

                  _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations);
               }
            }
         }
      } catch (final OValException ex) {
         throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method + " Validated object: " + validatedObject, ex);
      } finally {
         // remove the set
         currentlyValidatedObjects.get().removeLast();
      }
   }

   /**
    * Validates the post conditions for a method call.
    */
   protected void validateMethodPost(final Object validatedObject, final Method method, final Object[] args, final Object returnValue,
      final Map<PostCheck, Object> oldValues, final List<ConstraintViolation> violations) throws ValidationFailedException {
      final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);

      /*
       *  avoid circular references
       */
      if (CURRENTLY_CHECKED_POST_CONDITIONS.get().contains(key))
         return;

      CURRENTLY_CHECKED_POST_CONDITIONS.get().add(key);
      try {
         final ClassChecks cc = getClassChecks(method.getDeclaringClass());
         final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method);

         if (postChecks == null)
            return;

         final String[] parameterNames = parameterNameResolver.getParameterNames(method);
         final boolean hasParameters = parameterNames.length > 0;

         final MethodExitContext context = ContextCache.getMethodExitContext(method);

         for (final PostCheck check : postChecks) {
            if (!isAnyProfileEnabled(check.getProfiles(), null)) {
               continue;
            }
            try {
               final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLang());
               final Map<String, Object> values = getCollectionFactory().createMap();
               values.put("_this", validatedObject);
               values.put("_returns", returnValue);
               values.put("_old", oldValues.get(check));

               if (hasParameters) {
                  values.put("_args", args);
                  for (int i = 0; i < args.length; i++) {
                     values.put(parameterNames[i], args[i]);
                  }
               } else {
                  values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
               }

               if (!eng.evaluateAsBoolean(check.getExpr(), values)) {
                  final Map<String, String> messageVariables = getCollectionFactory().createMap(2);
                  messageVariables.put("expression", check.getExpr());
                  final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables);

                  violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context));
               }
            } catch (final OValException ex) {
               throw new ValidationFailedException("Executing " + check + " failed. Method: " + method + " Validated object: " + validatedObject, ex);
            }
         }
      } catch (final ValidationFailedException ex) {
         throw ex;
      } catch (final OValException ex) {
         throw new ValidationFailedException("Method post conditions validation failed. Method: " + method + " Validated object: " + validatedObject, ex);
      } finally {
         CURRENTLY_CHECKED_POST_CONDITIONS.get().remove(key);
      }
   }

   /**
    * Validates the @Pre conditions for a method call.
    */
   protected void validateMethodPre(final Object validatedObject, final Method method, final Object[] args, final List<ConstraintViolation> violations)
      throws ValidationFailedException {
      final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);

      /*
       *  avoid circular references
       */
      if (CURRENTLY_CHECKED_PRE_CONDITIONS.get().contains(key))
         return;

      CURRENTLY_CHECKED_PRE_CONDITIONS.get().add(key);
      try {
         final ClassChecks cc = getClassChecks(method.getDeclaringClass());
         final Set<PreCheck> preChecks = cc.checksForMethodsPreExecution.get(method);

         if (preChecks == null)
            return;

         final String[] parameterNames = parameterNameResolver.getParameterNames(method);
         final boolean hasParameters = parameterNames.length > 0;

         final MethodEntryContext context = ContextCache.getMethodEntryContext(method);

         for (final PreCheck check : preChecks) {
            if (!isAnyProfileEnabled(check.getProfiles(), null)) {
               continue;
            }

            final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLang());
            final Map<String, Object> values = getCollectionFactory().createMap();
            values.put("_this", validatedObject);
            if (hasParameters) {
               values.put("_args", args);
               for (int i = 0; i < args.length; i++) {
                  values.put(parameterNames[i], args[i]);
               }
            } else {
               values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
            }

            if (!eng.evaluateAsBoolean(check.getExpr(), values)) {
               final Map<String, String> messageVariables = getCollectionFactory().createMap(2);
               messageVariables.put("expression", check.getExpr());
               final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables);

               violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context));
            }
         }
      } catch (final OValException ex) {
         throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method + " Validated object: " + validatedObject, ex);
      } finally {
         CURRENTLY_CHECKED_PRE_CONDITIONS.get().remove(key);
      }
   }

   /**
    * Validates the return value checks for a method call.
    */
   protected void validateMethodReturnValue(final Object validatedObject, final Method method, final Object returnValue,
      final List<ConstraintViolation> violations) throws ValidationFailedException {
      final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method);

      /*
       *  avoid circular references, e.g.
       *
       *  private String name;
       *
       *  @Assert("_this.name != null", lang="groovy")
       *  public String getName { return name; }
       *
       *  => 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
       */
      if (CURRENTLY_CHECKED_METHOD_RETURN_VALUES.get().contains(key))
         return;

      CURRENTLY_CHECKED_METHOD_RETURN_VALUES.get().add(key);
      // create a new set for this validation cycle
      currentlyValidatedObjects.get().add(new IdentitySet<>(4));
      try {
         final ClassChecks cc = getClassChecks(method.getDeclaringClass());
         final Collection<Check> returnValueChecks = cc.checksForMethodReturnValues.get(method);

         if (returnValueChecks == null || returnValueChecks.size() == 0)
            return;

         final MethodReturnValueContext context = ContextCache.getMethodReturnValueContext(method);

         for (final Check check : returnValueChecks) {
            checkConstraint(violations, check, validatedObject, returnValue, context, null, false);
         }
      } catch (final OValException ex) {
         throw new ValidationFailedException("Method post conditions validation failed. Method: " + method + " Validated object: " + validatedObject, ex);
      } finally {
         CURRENTLY_CHECKED_METHOD_RETURN_VALUES.get().remove(key);

         // remove the set
         currentlyValidatedObjects.get().removeLast();
      }
   }
}