package gherkin.formatter; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class Mappable implements Serializable { private static final Integer NO_LINE = -1; public Map<String, Object> toMap() { Map<String, Object> map = new HashMap<String, Object>(); List<Field> mappableFields = getMappableFields(); for (Field field : mappableFields) { Object value; value = getValue(field); if (value != null && Mappable.class.isAssignableFrom(value.getClass())) { value = ((Mappable) value).toMap(); } if (value != null && Collection.class.isAssignableFrom(value.getClass())) { List<Object> mappedValue = new ArrayList<Object>(); for (Object o : (Collection) value) { if (Mappable.class.isAssignableFrom(o.getClass())) { mappedValue.add(((Mappable) o).toMap()); } else { mappedValue.add(o); } } value = mappedValue; } if (value != null && !Collections.EMPTY_LIST.equals(value) && !NO_LINE.equals(value)) { map.put(field.getName(), value); } } return map; } private Object getValue(Field field) { try { field.setAccessible(true); return field.get(this); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private List<Field> getMappableFields() { List<Field> fields = new ArrayList<Field>(); Class c = getClass(); while (c != null) { for (Field field : c.getDeclaredFields()) { if (isMappable(field)) { fields.add(field); } } c = c.getSuperclass(); } return fields; } private boolean isMappable(Field field) { boolean transientField = Modifier.isTransient(field.getModifiers()); boolean instanceField = !Modifier.isStatic(field.getModifiers()); boolean mappableType = isMappableType(field.getType(), field.getGenericType()); return !transientField && instanceField && mappableType; } private boolean isMappableType(Class type, Type genericType) { return String.class.equals(type) || type.isPrimitive() || Number.class.isAssignableFrom(type) || Mappable.class.isAssignableFrom(type) || genericType != null && Collection.class.isAssignableFrom(type) && isMappableCollection(genericType); } private boolean isMappableCollection(Type genericType) { if (genericType instanceof ParameterizedType) { Type[] parameters = ((ParameterizedType) genericType).getActualTypeArguments(); return parameters[0] instanceof Class && isMappableType((Class) parameters[0], null); } else { return false; } } }