package com.intrbiz.bergamot.config.resolver;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* <p>
* Resolve a Java POJO / Bean, by resolving any properties which are annotated with <code>ResolveWith</code>.
* </p>
* <p>
* Place the <code>ResolveWith</code> annotation onto the getter of any property which should be resolved,
* for example:
* </p>
* <pre><code>public class MyBean {
* private String name;
*
* public MyBean(String name)
* {
* this.name = name;
* }
*
* @ResolveWith(CoalesceEmptyString.class)
* public String getName() { return this.name; }
*
* public void setName(String name) { this.name = name; }
* }
* </code></pre>
* <p>
* Then you can resolve two objects as follows:
* </p>
* <pre><code>MyBean bean = BeanResolver.fromClass(MyBean.class).resolve(new MyBean(null), new MyBean("Hello"));</code></pre>
*/
public class BeanResolver<T> implements ObjectResolver<T>
{
// cache of resolvers
private static final ConcurrentMap<Class<?>, BeanResolver<?>> RESOLVERS = new ConcurrentHashMap<Class<?>, BeanResolver<?>>();
private final Class<T> type;
private final List<PropertyResolver<T>> properties;
private BeanResolver(Class<T> type, List<PropertyResolver<T>> properties)
{
this.type = type;
this.properties = properties;
}
@Override
public T resolve(T most, T least)
{
// we need at least one
if (most == null && least == null) return null;
// if we only have one return the other
if (least == null) return most;
if (most == null) return least;
// resolve
try
{
T ret = this.type.newInstance();
for (PropertyResolver<T> pr : this.properties)
{
pr.resolve(ret, most, least);
}
return ret;
}
catch (Exception e)
{
throw new RuntimeException("Failed to resolve " + this.type.getSimpleName(), e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <TT> BeanResolver<TT> fromClass(Class<TT> cls)
{
// check the cache
BeanResolver resolver = RESOLVERS.get(cls);
if (resolver == null)
{
// build the property resolvers
List<PropertyResolver<TT>> properties = new ArrayList<PropertyResolver<TT>>();
for (Method getter : cls.getMethods())
{
ResolveWith res = getter.getAnnotation(ResolveWith.class);
if (res != null)
{
// validate the name
if (!getter.getName().startsWith("get")) throw new RuntimeException("You can only use @ResolveWith on getter methods, annotation found on: " + getter);
Method setter;
ObjectResolver or;
// find the setter method
try
{
setter = cls.getMethod("s" + getter.getName().substring(1), getter.getReturnType());
}
catch (NoSuchMethodException | SecurityException e)
{
throw new RuntimeException("Failed to find setter method correlating to " + getter);
}
// load the resolver
try
{
if (res.value() == BeanResolver.class)
{
or = BeanResolver.fromClass(getter.getReturnType());
}
else
{
or = res.value().newInstance();
}
}
catch (InstantiationException | IllegalAccessException e)
{
throw new RuntimeException("Failed to load the ObjectResolver for " + getter);
}
properties.add(new PropertyResolver<TT>(setter, getter, or));
}
}
// create the resolver
resolver = new BeanResolver(cls, properties);
RESOLVERS.put(cls, resolver);
}
return resolver;
}
private static class PropertyResolver<B>
{
public final Method setter;
public final Method getter;
@SuppressWarnings("rawtypes")
public final ObjectResolver resolver;
@SuppressWarnings("rawtypes")
public PropertyResolver(Method setter, Method getter, ObjectResolver resolver)
{
this.setter = setter;
this.getter = getter;
this.resolver = resolver;
}
@SuppressWarnings("unchecked")
public void resolve(B into, B most, B least) throws Exception
{
// resolve the property
Object resolved = (Object) this.resolver.resolve(this.getter.invoke(most), this.getter.invoke(least));
// finish the resolution
resolved = this.resolver.finish(resolved);
// set it
setter.invoke(into, resolved);
}
}
}