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