PojoUtil.java
package org.microspace.util;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Properties;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.microspace.specific.MethodBasedGetter;
import org.microspace.specific.MethodBasedSetter;
import org.microspace.specific.MethodUtils;
import org.microspace.specific.MicroSpaceAccessor;
import org.microspace.specific.MicroSpaceAccessorGenerator;
import org.microspace.specific.MicroSpaceNullValueExtractor;
import org.microspace.specific.NullValueExtractor;
import org.microspace.specific.UnannotatedAccessorGenerator;
import org.microspace.table.column.Accessor;
import org.microspace.table.column.GetSetPair;
import org.microspace.table.column.Getter;
import org.microspace.table.column.Setter;
/**
* Utility for POJOs.
*
* @author Gaspar Sinai - {@literal gaspar.sinai@microspace.org}
* @version 2016-06-26
*/
public class PojoUtil {
static final MicroLogger log = new MicroLogger(PojoUtil.class);
/**
* Copy a POJO.
* @param from is the POJO to be copied.
* @param <T> is the input/output Type.
* @return a new POJO.
*/
@SuppressWarnings("unchecked")
public static <T> T copy (T from) {
Accessor<T> accessor = getUnannotateAccessor ((Class<T>)from.getClass());
T to = accessor.getBlankObject();
for (GetSetPair<T> p : accessor.getGetSetPairs()) {
Object o = p.get(from);
p.set(to, o);
}
return to;
}
/**
* Copy a POJO to an existing POJO.
* @param from is the source
* @param to is the destination.
*/
public static void copy (Object from, Object to ) {
NullValueExtractor extractor = new MicroSpaceNullValueExtractor();
// Collect the public getXX methods.
@SuppressWarnings("unchecked")
TreeMap<String, MethodBasedGetter<Object>> getters = MethodUtils.scanGetters ((Class<Object>)from.getClass(), extractor);
@SuppressWarnings("unchecked")
TreeMap<String, MethodBasedSetter<Object>> setters = MethodUtils.scanSetters ((Class<Object>)to.getClass());
@SuppressWarnings("unchecked")
TreeMap<String, MethodBasedGetter<Object>> togetters = MethodUtils.scanGetters ((Class<Object>)to.getClass(), extractor);
for (String key : getters.keySet()) {
Setter<Object> setter = setters.get(key);
if (setter == null) continue;
Getter<Object> togetter = togetters.get(key);
if (togetter == null) continue;
Getter<Object> getter = getters.get(key);
Object o = getter.get(from);
if (togetter.getReturnType().isAssignableFrom(getter.getReturnType())) {
setter.set(to, o);
}
}
}
/**
* Read a POJO configuration from a stream.
* <p>
* POJO should have a default constructor.
* <p>
* The fields must be reconstructable from String. Patters.class will
* have a Pattter.compile(), and if String can not be used to construct
* the field, a new class is loaded with empty constructor.
*
* @param clazz is the POJO class
* @param resource is the resource name of the property file.
* @param <T> is the input type.
* @return the POJO with fields initialized from resource.
*/
public static <T> T read (Class<T> clazz, String resource) {
Properties prop = new Properties();
InputStream inputStream = clazz.getClassLoader().getResourceAsStream(resource);
if (inputStream != null) {
try {
log.debug("Loading $* from $*", clazz.getName(), resource);
prop.load(inputStream);
} catch (IOException e) {
log.error("Can not read POJO from resource $*", e, resource);
}
return read (clazz, prop);
}
return null;
}
/**
* Read a POJO configuration from properites.
* <p>
* POJO should have a default constructor.
* <p>
* The fields must be reconstructable from String. Patters.class will
* have a Pattter.compile(), and if String can not be used to construct
* the field, a new class is loaded with empty constructor.
*
* @param clazz is the POJO class
* @param properties are the properties of the POJO
* @param <T> is the input type.
* @return the initialized class.
*/
public static <T> T read (Class<T> clazz, Properties properties) {
MicroSpaceAccessor<T> accessor = new MicroSpaceAccessor<T>(clazz, true);
T ret = accessor.getBlankObject();
for (GetSetPair<T> p : accessor.getGetSetPairs()) {
String str = properties.getProperty(p.getName());
if (str == null) {
log.debug("Can not get property $*", p.getName());
continue;
}
//log.trace("Creating $* from property $*", p.getName(), str);
Object o = createObjectFromString (str, p.getReturnType());
if (o == null) continue;
p.set(ret, o);
}
return ret;
}
/**
* Create an object using a string.
*
* @param property is used in the construtor, or used a a class name if there
* is no string constructor. Pattern classes are obtained using Patter.compile().
* @param <T> is the input type.
* @param clazz is the POJO Classs.
* @return the initialized object or null.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> T createObjectFromString (String property, Class<T> clazz) {
//log.info("Trying to create $* from $*",clazz.getName(), property);
//System.err.println ("Trying to create " + property);
String trimmed = property.trim();
if (trimmed.length()==0) {
return null;
}
if (Enum.class.isAssignableFrom(clazz)) {
T ret = (T) Enum.valueOf((Class<? extends Enum>)clazz, trimmed);
//System.err.println ("Created " + ret);
return ret;
}
if (clazz == String.class) return (T) trimmed;
if (clazz == char.class) return (T) Character.valueOf(trimmed.charAt(0));
if (clazz == int.class) return (T) Integer.valueOf(trimmed);
if (clazz == long.class) return (T) Long.valueOf (trimmed);
if (clazz == boolean.class) return (T) Boolean.valueOf (trimmed);
if (clazz == double.class) return (T) Double.valueOf (trimmed);
if (clazz.equals(Pattern.class)) {
return (T) Pattern.compile(trimmed);
}
// look for a string based constructor first
// Removed support.
Constructor<?>[] cs = clazz.getConstructors();
for (Constructor<?> c : cs) {
// log.trace("Constructor $*", c);
if (c.getDeclaringClass() != clazz) continue;
if (c.getModifiers() != Modifier.PUBLIC) continue;
Class<?>[] types = c.getParameterTypes();
if (types.length != 1) continue;
Class<?> p = types[0];
// log.trace("Assignable $*", p.getName());
if (p.isAssignableFrom(String.class)) {
try {
return (T) c.newInstance(trimmed);
} catch (Exception e) {
log.warn("Can not create $* from String",e, trimmed);
}
}
}
log.info("Can not create object $* from String $*", clazz.getName());
return (T) createObject (trimmed);
}
/**
* Create an Object with an empty constructor using className.
* @param className is the name of the Object.
* @return the create object or null on failure.
*/
public static Object createObject (String className) {
// Create an object with empty constructor.
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e1) {
log.warn("Can find class $0", e1, className);
return null;
}
Constructor<?>[] cs = clazz.getConstructors();
for (Constructor<?> c : cs) {
if (c.getDeclaringClass() != clazz) continue;
if (c.getModifiers() != Modifier.PUBLIC) continue;
Class<?>[] types1 = c.getParameterTypes();
if (types1.length != 0) continue;
try {
return c.newInstance();
} catch (Exception e) {
log.warn("Can find make new instance $0", e, className);
}
}
return null;
}
static final AccessorCache accessorCache = new AccessorCache(new MicroSpaceAccessorGenerator());
/**
* Compares the given template object to the given object and checks whether they match.
* This version uses MicroSpace accessors.
* @param template The template object to compare to
* @param object The object to compare
* @param <T> is the template type.
* @return true if the template matches the object, else false.
*/
public static <T>boolean match (T template, T object) {
@SuppressWarnings("unchecked")
Accessor<T> templateAccessor = accessorCache.get((Class<T>)template.getClass());
return match (template, object, templateAccessor);
}
/**
* Compares the given template object to the given object and checks whether they match.
* This version uses MicroSpace accessors.
* @param template The template object to compare to
* @param object The object to compare
* @param templateAccessor The accessor for T.
* @param <T> is the template type.
* @return true if the template matches the object, else false.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T>boolean match (T template, T object, Accessor<T> templateAccessor) {
for(GetSetPair<T> getSetPair : templateAccessor.getGetSetPairs()) {
Getter<T> getter = getSetPair;
Object templateValue = getter.get(template);
Object objectValue = getter.get(object);
if(getter.isNull(templateValue))
continue;
if(getter.isNull(objectValue))
return false;
if(getter.getIndexType() == null){
if(!getter.isNull(getter.get(template)))
throw new RuntimeException("Non indexed field set in template");
continue;
}
switch (getter.getIndexType()){
case HASHED:
if(!templateValue.equals(objectValue))
return false;
break;
case SORTED:
if(((Comparable)templateValue).compareTo((Comparable)objectValue) != 0)
return false;
default:
}
}
return true;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> boolean keyEquals (T o1, T o2, Accessor<T> tableAccessor) {
Getter<T> getter = tableAccessor.getPrimaryKeyGetSetPair();
Object o1Key = getter.get(o1);
Object o2Key = getter.get(o2);
switch (getter.getIndexType()) {
case HASHED:
return (o1Key.equals(o2Key));
case SORTED:
return (((Comparable) o1Key)
.compareTo((Comparable) o2Key) == 0);
default:
}
return false;
}
static final AccessorCache unannotatedAccessorCache = new AccessorCache(new UnannotatedAccessorGenerator());
public static <T> Accessor<T> getUnannotateAccessor (Class<T> tableClass) {
return unannotatedAccessorCache.get(tableClass);
}
public static <T> GetSetPair<T> getColumnGetSetPair (Class<T> tableClass, String columnName) {
return getUnannotateAccessor(tableClass).getGetSetPair(columnName);
}
public static <T> Class<?> getColumnClass (Class<T> tableClass, String columnName) {
GetSetPair<T> pair = getUnannotateAccessor(tableClass).getGetSetPair(columnName);
if (pair == null) return null;
return pair.getReturnType();
}
/**
* Format a Pojo with null values skipped.
* @param object The object.
* @return The formatted string.
*/
public static String formatShort (final Object object) {
if (object == null) return "null";
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(
object, ToStringStyle.SHORT_PREFIX_STYLE) {
@Override
protected boolean accept(Field field) {
try {
return super.accept(field) && field.get(object) != null;
} catch (IllegalAccessException e) {
return super.accept(field);
}
}
};
return builder.toString();
}
}