package eu.dnetlib.iis.wf.export.actionmanager.sequencefile; import java.lang.reflect.Array; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; /** * Accesses object fields using getter methods of field names encoded in properties file as * keys. Nested field names are separated with dots. * * Notice: for getter method defined as `getFieldName()` expected field name defined in field path is `fieldName`. * * All fields requiring decoder should be marked with '$' prefix. * * @author mhorst * */ public class FieldAccessor { private static final char FIELD_PATH_SEPARATOR = '.'; private static final char DECODER_PREFIX = '$'; private final Map<String, FieldDecoder> decoders = new HashMap<String, FieldDecoder>(); //------------------------ LOGIC --------------------------------- /** * Registers field decoder. * * @param field field name for which decoder should be activated * @param decoder decoder to be registered */ public void registerDecoder(String field, FieldDecoder decoder) { decoders.put(field, decoder); } /** * Extracts value from given object based on fieldPath. * @param fieldPath field names accessible vie getter methods, nested fields are separated with dots * @param object object value should be extracted from * @return extracted value * @throws FieldAccessorException */ public Object getValue(String fieldPath, Object object) throws FieldAccessorException { if (object != null && fieldPath != null) { try { Object currentObject = object; String[] tokens = StringUtils.split(fieldPath, FIELD_PATH_SEPARATOR); for (final String token : tokens) { String parsedToken = token; boolean useDecoder = false; Integer arrayOrListPosition = null; if (parsedToken.charAt(0) == DECODER_PREFIX) { useDecoder = true; parsedToken = parsedToken.substring(1); } if (parsedToken.indexOf('[') > 0) { arrayOrListPosition = getArrayPositionFromToken(parsedToken); parsedToken = parsedToken.substring(0, parsedToken.indexOf('[')); } currentObject = PropertyUtils.getProperty(currentObject, parsedToken); if (useDecoder) { currentObject = decode(parsedToken, currentObject); } if (arrayOrListPosition!=null) { currentObject = handleArrayOrList(arrayOrListPosition, currentObject); } } return currentObject; } catch (Exception e) { // we are extremely verbose to make log analysis process easier throw new FieldAccessorException("unable to resolve field path '" + fieldPath + "' on object " + object, e); } } else { throw new FieldAccessorException("Neither fieldPath nor object can be null! " + "Got fieldPath: " + fieldPath + ", object: " + object); } } //------------------------ PRIVATE ------------------------------- /** * Decodes source object using decoder defined in token. * @param decoderName decoder name * @param currentObject source object to be decoded * @return decoded object * @throws Exception */ private Object decode(String decoderName, Object currentObject) throws Exception { FieldDecoder decoder = decoders.get(decoderName); if (decoder == null) { throw new FieldAccessorException("no decoder registered for " + decoderName); } if (decoder.canHandle(currentObject)) { return decoder.decode(currentObject); } else { throw new FieldAccessorException(currentObject!=null? decoder.getClass().getName() + " decoder is not capable of handling object: " + currentObject.toString() + ", which is " + currentObject.getClass().getName() + " class instance" :decoder.getClass().getName() + " decoder is not capable of handling null object"); } } /** * Handles array or list element. * @param position subelement position in array or list * @param currentObject source object * @return field array or list subelement * @throws Exception */ private static Object handleArrayOrList(int position, Object currentObject) throws Exception { if (currentObject!=null) { if (currentObject.getClass().isArray()) { return Array.get(currentObject, position); } else if (List.class.isAssignableFrom(currentObject.getClass())) { return ((List<?>)currentObject).get(position); } else { throw new FieldAccessorException("unable to extract array or list element " + "on position: " + position + ", " + "object is neither an array nor list:" + currentObject.getClass().getName()); } } else { throw new FieldAccessorException("unable to extract array or list element " + "on position: " + position + ", object is null!"); } } /** * Extracts array/list position from given token * @param token field path token including field name and element position markup * @return extracted position */ private static int getArrayPositionFromToken(String token) { return Integer.parseInt(StringUtils.substringBetween(token, "[", "]")); } }