package io.joj.reflect.annotation;

import static java.util.Objects.requireNonNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.Proxy;
import java.util.Map;

import org.pcollections.HashPMap;
import org.pcollections.IntTreePMap;
import org.pcollections.PMap;

import io.joj.reflect.MethodReference0;
import io.joj.reflect.MethodReferences;

/**
 * Main entry point for building synthetic {@link Annotation} instances.
 * 
 * @author findepi
 */
public class AnnotationBuilder {
	private AnnotationBuilder() {
	}

	/**
	 * Low level, type-unsafe, annotation synthesizer. Whenever possible, use {@link #builderFor(Class)} instead.
	 * 
	 * @param annotationClass
	 *            desired annotation type
	 * @param values
	 *            map from annotation method name to value
	 * @return synthesized annotation
	 */
	public static <A extends Annotation> A buildFromMap(Class<A> annotationClass, Map<String, ?> values) {
		Object syntheticAnnotation = Proxy.newProxyInstance(
				Thread.currentThread().getContextClassLoader(),
				new Class<?>[] { annotationClass },
				new SyntheticAnnotationInvocationHandler<A>(annotationClass, values));

		return annotationClass.cast(syntheticAnnotation);
	}

	/**
	 * Returns type-safe, reusable (functional) builder for {@code A}. {@code A} instances produced by this builder
	 * conform to general contract of {@link Annotation}-s.
	 */
	public static <A extends Annotation> Builder<A> builderFor(Class<A> annotationClass) {
		return new Builder<>(annotationClass);
	}

	public static final class Builder<A extends Annotation> {
		private final Class<A> clazz;
		private final PMap<String, Object> values;

		private Builder(Class<A> clazz) {
			this(clazz, HashPMap.empty(IntTreePMap.empty()));
		}

		private Builder(Class<A> clazz, PMap<String, Object> values) {
			this.clazz = requireNonNull(clazz, "clazz");
			this.values = requireNonNull(values, "values");
		}

		public <R> OngoingMethodSpec<R> with(MethodReference0<A> methodReference) {
			String specedMethodName = MethodReferences.getMethod(clazz, methodReference).getName();
			return new OngoingMethodSpec<>(specedMethodName);
		}

		public A build() {
			return buildFromMap(clazz, values);
		}

		public final class OngoingMethodSpec<R> {
			private final String specedMethodName;

			public OngoingMethodSpec(String specedMethodName) {
				this.specedMethodName = requireNonNull(specedMethodName, "specedMethodName");
			}

			public Builder<A> returning(R value) {
				return new Builder<>(clazz, values.plus(specedMethodName, value));
			}
		}
	}
}

@@ -433,16 +433,777 @@
 tances.%0A
+ * %3Cp%3E%0A * Usually one does not need to create annotation instances at run-time. Annotations were designed, and are mainly used,%0A * to annotate source code and as such are generally determined at compile time. However, at times, in dark corners of%0A * software, one can face desire, or even urgent need, to make up an annotation instance that is not bound to source%0A * element or, even worse, cannot be determined at compile time. %7B@link AnnotationBuilder%7D is here to help!%0A * %3Cp%3E%0A * Incidentally, annotations are designed as immutable, type-safe, null-safe (no %7B@code null%7D-s), value-based (in terms%0A * of %7B@link Object#hashCode()%7D and %7B@link Object#equals(Object)%7D) data structures. Perfect!... if only we could%0A * instantiate them at run-time! And now we can.%0A
  * %0A * @
@@ -2148,16 +2148,317 @@
 ion%7D-s.%0A
+%09 * %3Cp%3E%0A%09 * Example:%0A%09 *%0A%09 * %3Ccode%3E%3Cpre%3E%0A%09 * import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;%0A%09 *%0A%09 * XmlJavaTypeAdapter generated = AnnotationBuilder.builderFor(XmlJavaTypeAdapter.class)%0A%09 *   .with(XmlJavaTypeAdapter::value).returning(MyAdapter.class)%0A%09 *   .build();%0A%09 * %3C/pre%3E%3C/code%3E%0A
 %09 */%0A%09pu

Improve AnnotationBuilder javadoc: rationale, example