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();
	}
}