package be.selckin.swu.pmodel;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
public class PModel {
private PModel() {
}
private static final Logger log = LoggerFactory.getLogger(PModel.class);
private static final ImmutableSet<Class<?>> NO_FINAL_WARN = ImmutableSet.<Class<?>>of(String.class, Integer.class, Double.class, Long.class, Float.class, Boolean.class, Byte.class);
private static final Joiner propertyJoiner = Joiner.on(".");
private static final ProxyFactory proxyFactory = new ProxyFactory();
private static final Map<Class<?>, Object> proxyCache = Maps.newConcurrentMap();
private static final ThreadLocal<IModel<?>> model = new ThreadLocal<IModel<?>>();
private static final ThreadLocal<List<String>> expression = new ThreadLocal<List<String>>();
@SuppressWarnings("UnusedParameters")
public static <T> IModel<T> model(@Nullable T object) {
log.trace("model called");
try {
if (model.get() == null)
throw new PModelException("No model owner, did you forget bean()?");
if (expression.get() == null)
throw new PModelException("No model expression, did you forget the expression on bean()'s return value? Model owner is: " + model.get());
return new PropertyModel<T>(model.get(), propertyJoiner.join(expression.get()));
} finally {
reset();
}
}
@SuppressWarnings("unchecked")
public static <T> T bean(Class<T> type, IModel<? extends T> object) {
log.trace("bean called: {}", type);
if (type == null)
throw new PModelException("Missing type");
if (model.get() != null) {
reset(); // otherwise all future calls will fail
throw new PModelException("Model owner already set, did you forget model() for a previous of()? Previous owner is: " + model.get());
}
reset();
model.set(object);
expression.set(Lists.<String>newArrayList());
return (T) createProxy(type);
}
protected static void reset() {
model.remove();
expression.remove();
}
@Nullable
private static <T> T createProxy(Class<T> type) {
if (type.isPrimitive())
return null;
if (Modifier.isFinal(type.getModifiers())) {
final Class<?> warnType;
if (type.isArray())
warnType = type.getComponentType();
else
warnType = type;
if (!NO_FINAL_WARN.contains(warnType) && !warnType.isEnum())
log.warn("Can't proxy {} because the class is final.", type);
return null;
}
T proxy = (T) proxyCache.get(type);
if (proxy == null) {
proxy = proxyFactory.createProxy(type, new PModelInvocationHandler());
proxyCache.put(type, proxy);
}
return proxy;
}
// placeholder
public static <T> T read(Class<T> type, IModel<? extends T> object) {
return bean(type, object);
}
// placeholder
public static <T> T write(Class<T> type, IModel<? extends T> object) {
return bean(type, object);
}
protected static String getPropertyName(Method method) {
String name = method.getName();
if (name.startsWith("get") && name.length() > 3) {
return name.substring(3);
} else if (name.startsWith("is") && name.length() > 2) {
return name.substring(2);
}
throw new PModelException("Not a standard javabean-type method: " + method);
}
public static <T> PModeler<T> modeler(Class<T> type, IModel<? extends T> object) {
return new PModeler<T>(type, object);
}
private static class PModelInvocationHandler implements InvocationHandler {
@Override
@SuppressWarnings("ProhibitedExceptionDeclared")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (model.get() == null || expression.get() == null)
throw new PModelException("Proxy called outside of normal cycle");
expression.get().add(getPropertyName(method));
return createProxy(method.getReturnType());
}
}
public static class PModeler<T> implements Serializable {
private final Class<T> type;
private final IModel<? extends T> model;
public PModeler(Class<T> type, IModel<? extends T> model) {
this.type = type;
this.model = model;
}
@SuppressWarnings("unchecked")
public T bean() {
return PModel.bean(type, model);
}
public T read() {
return PModel.bean(type, model);
}
public T write() {
return PModel.bean(type, model);
}
}
}