
/*
 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.validation;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

/**
 * Provides additional context for {@link javax.validation.ConstraintValidator} implementations.
 *
 * <p>
 * {@link ValidationContext} gives {@link javax.validation.ConstraintValidator} implementations access to the root object
 * being validated. This breaks the usual principle of locality for validation (i.e., that validation of a specific bean
 * proceeds unaware of that bean's parents) but it can make custom validators more convenient to implement.
 * Subclasses are encouraged to provide additional application-specific information.
 *
 * <p>
 * Validation must be performed via {@link #validate(Validator, ValidationContext) validate()} for this class to work.
 */
public class ValidationContext<T> {

    static final ThreadLocal<ValidationContext> CURRENT = new ThreadLocal<ValidationContext>();

    private final T root;

    /**
     * Construct a new validation context configured to validate the given root object.
     *
     * @param root root object to be validated
     * @throws IllegalArgumentException if {@code root} is null
     */
    public ValidationContext(T root) {
        if (root == null)
            throw new IllegalArgumentException("null root");
        this.root = root;
    }

    /**
     * Get the root object associated with this instance.
     */
    public final T getRoot() {
        return this.root;
    }

    /**
     * Convenience method. Equivalent to:
     *  <blockquote>
     *  <code>{@link #validate ValidationContext.validate}(Validation.buildDefaultValidatorFactory().getValidator(), context)</code>
     *  <blockquote>
     *
     * @throws IllegalStateException if this method is invoked re-entrantly
     * @throws IllegalArgumentException if {@code context} is null
     */
    public static <T> Set<ConstraintViolation<T>> validate(ValidationContext<T> context) {
        return ValidationContext.validate(Validation.buildDefaultValidatorFactory().getValidator(), context);
    }

    /**
     * Validate the given context's root object using the given {@link Validator}, making the context
     * available to the current thread during the validation process via {@link #getCurrentContext}.
     *
     * @throws IllegalStateException if this method is invoked re-entrantly
     * @throws IllegalArgumentException if {@code context} is null
     */
    public static <T> Set<ConstraintViolation<T>> validate(Validator validator, ValidationContext<T> context) {

        // Sanity check
        if (ValidationContext.CURRENT.get() != null)
            throw new IllegalStateException("re-entrant invocation is not allowed");
        if (context == null)
            throw new IllegalStateException("null context");

        // Set context, then validate
        ValidationContext.CURRENT.set(context);
        try {
            return validator.validate(context.getRoot());
        } finally {
            ValidationContext.CURRENT.remove();
        }
    }

    /**
     * Get the {@link ValidationContext} associated with the current thread, cast to the desired type.
     * This method is only valid during invocations of {@link #validate(Validator, ValidationContext) validate()}.
     *
     * @param type required type
     * @return current {@link ValidationContext}
     * @throws IllegalStateException if {@link #validate(Validator, ValidationContext) validate()} is not currently executing
     * @throws ClassCastException if the current {@link ValidationContext} is not of type {@code type}
     */
    public static <T extends ValidationContext<?>> T getCurrentContext(Class<T> type) {
        ValidationContext<?> context = ValidationContext.CURRENT.get();
        if (context == null)
            throw new IllegalStateException("current thread is not executing validate()");
        return type.cast(context);
    }

    /**
     * Convenience method to get the root object being validated by the current thread.
     * This method is only valid during invocations of {@link #validate(Validator, ValidationContext) validate()}.
     * Subclasses may want to override to narrow the return type.
     *
     * @return current validation root object
     * @throws IllegalStateException if {@link #validate(Validator, ValidationContext) validate()} is not currently executing
     */
    public static Object getCurrentRoot() {
        return ValidationContext.getCurrentContext(ValidationContext.class).getRoot();
    }
}


@@ -1620,33 +1620,81 @@
 %0A    /**%0A     * 
-C
+Validate this instance's root object. This is a c
 onvenience metho
@@ -1698,11 +1698,11 @@
 thod
-. E
+, e
 quiv
@@ -1763,34 +1763,16 @@
 alidate 
-ValidationContext.
 validate
@@ -1829,25 +1829,16 @@
 idator()
-, context
 )%3C/code%3E
@@ -1946,75 +1946,8 @@
 tly%0A
-     * @throws IllegalArgumentException if %7B@code context%7D is null%0A
     
@@ -1956,35 +1956,24 @@
 /%0A    public
- static %3CT%3E
  Set%3CConstra
@@ -2002,36 +2002,8 @@
 ate(
-ValidationContext%3CT%3E context
 ) %7B%0A
@@ -2017,33 +2017,20 @@
  return 
-ValidationContext
+this
 .validat
@@ -2091,17 +2091,8 @@
 or()
-, context
 );%0A 
@@ -2127,23 +2127,19 @@
 e th
-e given context
+is instance
 's r
@@ -2391,75 +2391,8 @@
 tly%0A
-     * @throws IllegalArgumentException if %7B@code context%7D is null%0A
     
@@ -2409,19 +2409,8 @@
 blic
- static %3CT%3E
  Set
@@ -2466,38 +2466,8 @@
 ator
-, ValidationContext%3CT%3E context
 ) %7B%0A
@@ -2632,98 +2632,8 @@
 d%22);
-%0A        if (context == null)%0A            throw new IllegalStateException(%22null context%22);
 %0A%0A  
@@ -2706,23 +2706,20 @@
 ENT.set(
-context
+this
 );%0A     
@@ -2769,25 +2769,17 @@
 ate(
-context.getR
+this.r
 oot
-()
 );%0A 

Convert static methods that should be instance methods.