package com.codepoetics.phantompojo.impl;
import com.codepoetics.phantompojo.*;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.stream.Stream;
public final class PhantomBuilderClassPair<P extends PhantomPojo<B>, B extends Supplier<P>> {
private static final ConcurrentMap<Class<?>, PhantomBuilderClassPair<?, ?>> cache = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public static <P extends PhantomPojo<B>, B extends Supplier<P>> PhantomBuilderClassPair<P, B> forPhantomClass(Class<? extends P> phantomClass) {
return (PhantomBuilderClassPair) cache.computeIfAbsent(phantomClass, cls -> forPhantomClassUncached((Class) cls));
}
private static <P extends PhantomPojo<B>, B extends Supplier<P>> PhantomBuilderClassPair<P, B> forPhantomClassUncached(Class<? extends P> phantomClass) {
Class<? extends B> builderClass = getBuilderClass(phantomClass);
MethodSet methodSet = MethodSet.forClasses(phantomClass, builderClass);
return new PhantomBuilderClassPair<>(phantomClass, builderClass, PropertySchema.forPhantomClass(phantomClass, methodSet));
}
private static <B extends Supplier<T>, T extends PhantomPojo<B>> Class<? extends B> getBuilderClass(Class<? extends T> targetClass) {
return Stream.of(targetClass.getGenericInterfaces())
.filter(t -> PhantomPojo.class.equals(ReflectionUtils.rawTypeOf(t)))
.map(PhantomBuilderClassPair::<B>getClassOfFirstTypeArgument)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Cannot infer builder class from class " + targetClass));
}
private static <B> Class<? extends B> getClassOfFirstTypeArgument(Type t) {
return (Class<? extends B>) ReflectionUtils.rawTypeOf(ReflectionUtils.getFirstTypeArgument(t));
}
private final Class<? extends P> phantomClass;
private final Class<? extends B> builderClass;
private final PropertySchema schema;
private PhantomBuilderClassPair(Class<? extends P> phantomClass, Class<? extends B> builderClass, PropertySchema schema) {
this.phantomClass = phantomClass;
this.builderClass = builderClass;
this.schema = schema;
}
public PropertySchema getPropertySchema() {
return schema;
}
public P createPhantom(PropertyStore store) {
return (P) Proxy.newProxyInstance(phantomClass.getClassLoader(),
new Class<?>[]{phantomClass},
new PhantomPojoProxy<>(this, store));
}
public B createBuilder(PropertyStore store) {
return (B) Proxy.newProxyInstance(builderClass.getClassLoader(),
new Class<?>[]{builderClass, Supplier.class},
new PhantomBuilderProxy(this, store));
}
}