torsdag 14 april 2016

Immutable Pojo

Annotation used to mark that a Pojo is immutable, in my case an immutable Pojo will have always private construtors, a builder and none setters, only getters.
       
package nap.pojo.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.TYPE})
public @interface ImmutablePojo{

}

       
 
Now, here is my Immutable Pojo:
       

package nap.pojo;

import java.io.Serializable;

@ImmutablePojo
public class TestBean implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 4811204609263660419L;
	private final String name;
	private final Integer id;
	private final String code;
	
	private TestBean(){
		this.code = null;
		this.id = null;
		this.name = null;
	}
	private TestBean(Builder builder){
		this.code = builder.code;
		this.id = builder.id;
		this.name = builder.name;
	}
	
	public static Builder getBuilder(){
		
		return new Builder();
	}
	
	public String getName() {
		return name;
	}
	
	public Integer getId() {
		return id;
	}

	public String getCode() {
		return code;
	}
	
	public static class Builder{
		
		private String name;
		private Integer id;
		private String code;
		
		public Builder withId(Integer id){
			this.id = id;
			return this;
		}
		public Builder withName(String name){
			this.name = name;
			return this;
		}
		public Builder withCode(String code){
			this.code = code;
			return this;
		}
		
		public TestBean build(){
			return new TestBean(this);
		}
	}
}

  
 
And now I need an util that can test all my Immutable Pojo:
       



package nap.pojo.immutable.utils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;

import java.beans.IntrospectionException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import atg.pojo.immutable.annotations.ImmutablePojo;
import atg.pojo.immutable.exceptions.ImmutableConventionException;


public class ImmutablePojoTester {

	private static Logger LOGGER = Logger.getLogger(ImmutablePojoTester.class);

	public static  void testPackage(final String packageName, String... skips) throws Exception {
		// Tests Endorsement of JavaBeans Convention
		List classLoadersList = new LinkedList();
		classLoadersList.add(ClasspathHelper.contextClassLoader());
		classLoadersList.add(ClasspathHelper.staticClassLoader());
		Reflections reflections = new Reflections(new ConfigurationBuilder()
				.setScanners(new SubTypesScanner(
						false /* don't exclude Object.class */), new ResourcesScanner())
				.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
				.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix(packageName))));
		Set> clazzes = reflections.getSubTypesOf(Object.class);
		for (Class clazz : clazzes) {
			test(clazz);
		}
	}

	public static  void test(final Class clazz) throws ImmutableConventionException {
		if (clazz.isAnnotationPresent(ImmutablePojo.class)) {
			try {
				// Tests bean which is BuilderCreated
				testImmutablePojo(clazz, Arrays.asList(new String[] {}));
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
				fail("Exception occured!");
			}
		} else if (clazz.getName().contains("Builder")) {
			if (LOGGER.isInfoEnabled()) {
				LOGGER.info(String.format("Builder (%s) will not be tested", clazz.getCanonicalName()));
			}
		} else {
			throw new ImmutableConventionException(String.format(
					"Pojo %s is not annoted with ImmutablePojo and probably doesn't follow Immutable Pojo Convention!",
					clazz.getCanonicalName()));
		}
	}

	private static  void testImmutablePojo(Class clazz, List skips) {
		try {
			Method method = clazz.getMethod("getBuilder", (Class[]) null);
			method.setAccessible(true);
			Object builder = method.invoke(null, (Object[]) null);
			assertNotNull(builder);
			Method build = null;
			List fieldNames = new ArrayList();
			Map fieldToValue = new HashMap();
			for (Method m : builder.getClass().getMethods()) {

				if (m.getName().startsWith("with")) {
					String fieldName = m.getName().replace("with", "").toLowerCase();
					fieldNames.add(fieldName);
					Parameter[] params = m.getParameters();
					for (Parameter p : params) {
						Object value = buildValue(p.getType());
						fieldToValue.put(fieldName, value);
						m.invoke(builder, value);
					}
				}
				if (m.getName().startsWith("build")) {
					build = m;
				}
			}
			if (build != null) {

				Object bean = build.invoke(builder, (Object[]) null);
				assertNotNull(bean);
				for (String fieldName : fieldNames) {
					if (!skips.contains(fieldName)) {
						Field field = bean.getClass().getDeclaredField(fieldName);
						field.setAccessible(true);
						Object value = field.get(bean);
						assertNotNull(value);
						Object exp = fieldToValue.get(fieldName);
						assertEquals(value, exp);
					}
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static  void test(final Class clazz, final String... skipThese) throws IntrospectionException {
		testImmutablePojo(clazz, Arrays.asList(skipThese));
	}

	private static Object buildMockValue(Class clazz) {
		if (!Modifier.isFinal(clazz.getModifiers())) {
			// Call your mocking framework here
			return mock(clazz);
		} else {
			return null;
		}
	}

	private static Object buildValue(Class clazz) throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, SecurityException, InvocationTargetException {
		// Try mocking framework first
		final Object mockedObject = buildMockValue(clazz);
		if (mockedObject != null) {
			return mockedObject;
		}
		final Constructor[] ctrs = clazz.getConstructors();
		for (Constructor ctr : ctrs) {
			if (ctr.getParameterTypes().length == 0) {
				return ctr.newInstance();
			}
		}
		if (clazz.isArray()) {
			return Array.newInstance(clazz.getComponentType(), 1);
		} else if (clazz == boolean.class || clazz == Boolean.class) {
			return true;
		} else if (clazz == int.class || clazz == Integer.class) {
			return 1;
		} else if (clazz == long.class || clazz == Long.class) {
			return 1L;
		} else if (clazz == double.class || clazz == Double.class) {
			return 1.0D;
		} else if (clazz == float.class || clazz == Float.class) {
			return 1.0F;
		} else if (clazz == char.class || clazz == Character.class) {
			return 'Y';
		} else if (clazz.isEnum()) {
			return clazz.getEnumConstants()[0];
		} else {
			return null; // for the compiler
		}
	}
}
  
 
Finally, my unit-test:
       
package nap.test.utils;

import java.beans.IntrospectionException;

import org.apache.log4j.BasicConfigurator;
import org.junit.Before;
import org.junit.Test;

import atg.pojo.immutable.exceptions.ImmutableConventionException;
import atg.test.domain.TestBean;
import atg.test.pojo.immutable.utils.ImmutablePojoTester;
import atg.test.pojo.utils.PojoTester;
import se.atg.service.racinginfo.api.calendar.CalendarDay;
import se.atg.service.racinginfo.api.game.Game;

/**
 * Unit test for simple App.
 */
public class DomainTest 
{
	@Before
	public void setUp(){
		BasicConfigurator.configure();
	}
   
          
    
    @Test
    public void testImmutablePojo() throws IntrospectionException, ImmutableConventionException
    {
    	ImmutablePojoTester.test(TestBean.class);	
    }
    
    @Test
    public void testImmutablePojoInPackage() throws Exception
    {
    	ImmutablePojoTester.testPackage("nap.test.domain");	
    }
}