package nl.bstoi.poiparser.core.strategy.annotation; import nl.bstoi.poiparser.api.strategy.annotations.Cell; import nl.bstoi.poiparser.api.strategy.annotations.Embedded; import nl.bstoi.poiparser.core.exception.PoiParserRuntimeException; import nl.bstoi.poiparser.core.strategy.CellDescriptor; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; /** * Hylke Stapersma * hylke.stapersma@gmail.com */ public class AnnotatedClassDescriber implements nl.bstoi.poiparser.api.ClassDescriber { private final static String[] DEFAULT_IGNORED_PROPERTYNAMES = {"class"}; private String[] ignorePropertyName = DEFAULT_IGNORED_PROPERTYNAMES; private static AnnotatedClassDescriber instance; private AnnotatedClassDescriber() { } public static AnnotatedClassDescriber getInstance() { if (null == instance) instance = new AnnotatedClassDescriber(); return instance; } @Override public Set<CellDescriptor> getCellDescriptorsForClass(final Class clazz) { return getCellDescriptorsForClass(null, clazz); } private Set<CellDescriptor> getCellDescriptorsForClass(final String propertyName, final Class clazz) { final Set<CellDescriptor> cellDescriptors = new HashSet<CellDescriptor>(); if (clazz.getSuperclass() != Object.class) { // Check superclasses first addEmbeddedCellDescriptors(cellDescriptors, getCellDescriptorsForClass(propertyName, clazz.getSuperclass())); } for (final PropertyDescriptor propertyDescriptor : PropertyUtils.getPropertyDescriptors(clazz)) { if (isNotIgnoredProperty(propertyDescriptor)) { Cell cell = null; Class type = null; try { final Field field = clazz.getDeclaredField(propertyDescriptor.getDisplayName()); cell = field.getAnnotation(Cell.class); if (isCorrectEmbeddedField(field) && hasNoTypeRecursion(clazz, field.getType())) { // Call method recursive when correct embedded type addEmbeddedCellDescriptors(cellDescriptors, getCellDescriptorsForClass(field.getName(), field.getType())); } if (cell == null) { // When field has no annotation then check method final Method method = propertyDescriptor.getReadMethod(); if (null != method && method.getDeclaringClass().equals(clazz)) { cell = method.getAnnotation(Cell.class); type = method.getReturnType(); } } else { type = field.getType(); } } catch (final SecurityException e) { throw e; // Rethrow security exception } catch (final NoSuchFieldException e) { // If field does not exist try to get value from getter(method) final Method method = propertyDescriptor.getReadMethod(); if (null != method && method.getDeclaringClass().equals(clazz)) { cell = method.getAnnotation(Cell.class); type = method.getReturnType(); } } if (null != cell) { addCellDescriptor(cellDescriptors, createCellDescriptor(getPropertyName(propertyName, propertyDescriptor.getDisplayName()), cell, type)); } } } return cellDescriptors; } private void addEmbeddedCellDescriptors(Set<CellDescriptor> cellDescriptors, Set<CellDescriptor> embeddedCellDescriptors) { for (final CellDescriptor embeddedCellDescriptor : embeddedCellDescriptors) { addCellDescriptor(cellDescriptors, embeddedCellDescriptor); } } private void addCellDescriptor(final Set<CellDescriptor> cellDescriptors,final CellDescriptor embeddedCellDescriptor) { if (!cellDescriptors.contains(embeddedCellDescriptor)) { cellDescriptors.add(embeddedCellDescriptor); } else { throw new PoiParserRuntimeException(String.format("Duplicate column definition found column %s is defined more than once", embeddedCellDescriptor.getColumnNumber())); } } private CellDescriptor createCellDescriptor(final String propertyName, final Cell cell, final Class type) { final CellDescriptor cellDescriptor = new CellDescriptor(propertyName, cell.columnNumber(), type); cellDescriptor.setRequired(cell.required()); cellDescriptor.setReadIgnore(cell.readIgnore()); cellDescriptor.setWriteIgnore(cell.writeIgnore()); cellDescriptor.setRegex(cell.regex()); return cellDescriptor; } private String getPropertyName(final String propertyName, final String fieldName) { if (StringUtils.isEmpty(propertyName)) { return fieldName; } return propertyName + "." + fieldName; } private boolean isNotIgnoredProperty(final PropertyDescriptor propertyDescriptor) { return !ArrayUtils.contains(ignorePropertyName, propertyDescriptor.getDisplayName()); } private boolean isEmbeddedField(final Field field) { if (null != field) { return (null != field.getAnnotation(Embedded.class)); } return false; } private boolean hasCellAnnotationOnField(final Field field) { if (null != field) { return (null != field.getAnnotation(Cell.class)); } return false; } private boolean isCorrectEmbeddedField(final Field field) { if (isEmbeddedField(field)) { if (!hasCellAnnotationOnField(field)) { return true; } else { throw new PoiParserRuntimeException("A field cannot be annotated with @Cell and @Embedded"); } } return false; } private boolean hasNoTypeRecursion(final Class declaringClass, final Class fieldType) { if (!declaringClass.equals(fieldType)) { return true; } throw new PoiParserRuntimeException("Declaring class cannot be the same as the field type (recursion is not supported)"); } }