/**
* =============================================================================
*
* ORCID (R) Open Source
* http://orcid.org
*
* Copyright (c) 2012-2014 ORCID, Inc.
* Licensed under an MIT-Style License (MIT)
* http://orcid.org/open-source-license
*
* This copyright and license information (including a link to the full license)
* shall be included in its entirety in all copies or substantial portion of
* the software.
*
* =============================================================================
*/
package org.orcid.core.tree;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TreeCleaner {
private static final Logger LOGGER = LoggerFactory.getLogger(TreeCleaner.class);
public static final ConcurrentHashMap<Class<?>, Class<?>> CANDIDATE_CLASSES = new ConcurrentHashMap<>();
private boolean removeEmptyObjects = true;
public boolean isRemoveEmptyObjects() {
return removeEmptyObjects;
}
public void setRemoveEmptyObjects(boolean removeEmptyObjects) {
this.removeEmptyObjects = removeEmptyObjects;
}
public void clean(Object obj, TreeCleaningStrategy decisionMaker) {
if (obj == null) {
return;
}
if (Collection.class.isAssignableFrom(obj.getClass())) {
processCollection((Collection<?>) obj, decisionMaker);
} else {
processGetters(obj, decisionMaker);
}
}
private void processCollection(Collection<?> coll, TreeCleaningStrategy decisionMaker) {
Iterator<?> iterator = coll.iterator();
while (iterator.hasNext()) {
Object objInCollection = iterator.next();
TreeCleaningDecision decision = decisionMaker.needsStripping(objInCollection);
if (decision.isNeedingCleaning()) {
iterator.remove();
} else {
if (decision.isNeedingPropertyChecking()) {
clean(objInCollection, decisionMaker);
}
if (removeEmptyObjects && hasNoActiveProperties(objInCollection)) {
iterator.remove();
}
}
}
}
private void processGetters(Object obj, TreeCleaningStrategy decisionMaker) {
Map<Method, Method> gettersAndSetters = getGetterAndCorrespondingSetter(obj.getClass().getMethods());
Set<Method> getters = gettersAndSetters.keySet();
for (Method getter : getters) {
try {
Object returnedObj = getter.invoke(obj);
TreeCleaningDecision decision = decisionMaker.needsStripping(returnedObj);
if (decision.isNeedingCleaning()) {
nullify(obj, gettersAndSetters, getter);
} else {
if (decision.isNeedingPropertyChecking()) {
clean(returnedObj, decisionMaker);
}
if (removeEmptyObjects && hasNoActiveProperties(returnedObj)) {
nullify(obj, gettersAndSetters, getter);
}
}
} catch (IllegalAccessException e) {
LOGGER.error("Cannot execute method", e);
} catch (InvocationTargetException e) {
LOGGER.error("Cannot execute method", e);
}
}
}
private void nullify(Object obj, Map<Method, Method> gettersAndSetters, Method getter) throws IllegalAccessException, InvocationTargetException {
Method setter = gettersAndSetters.get(getter);
Type[] params = setter.getGenericParameterTypes();
if (params.length > 0) {
Object[] empty = new Object[params.length];
setter.invoke(obj, empty);
}
}
private boolean hasNoActiveProperties(Object ob) {
if (ob == null) {
return true;
}
Map<Method, Method> orcidGettersAndSetters = getGetterAndCorrespondingSetter(ob.getClass().getMethods());
Set<Method> getters = orcidGettersAndSetters.keySet();
int inactiveCount = 0;
for (Method getter : getters) {
try {
Object returned = getter.invoke(ob);
if (returned == null || org.orcid.jaxb.model.message.Visibility.class.isAssignableFrom(returned.getClass())) {
inactiveCount++;
} else if (Collection.class.isAssignableFrom(returned.getClass())) {
Collection<?> coll = (Collection<?>) returned;
if (coll.isEmpty()) {
inactiveCount++;
}
}
} catch (IllegalAccessException e) {
LOGGER.error("Cannot execute method", e);
} catch (InvocationTargetException e) {
LOGGER.error("Cannot execute method", e);
}
}
return (getters != null && getters.size() > 0 && getters.size() == inactiveCount);
}
private Map<Method, Method> getGetterAndCorrespondingSetter(Method[] methods) {
Map<Method, Method> methodMap = new HashMap<Method, Method>();
for (Method m : methods) {
if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) {
if (isCandidate(m)) {
methodMap.put(m, getCorrespondingSetter(m.getName(), methods));
}
}
}
return methodMap;
}
private boolean isCandidate(Method method) {
if (method != null && method.getName().startsWith("get")) {
Class<?> returnType = method.getReturnType();
if (CANDIDATE_CLASSES.contains(returnType)) {
return true;
}
Package aPackage = returnType.getPackage();
String packageName = aPackage != null ? aPackage.getName() : "";
if (packageName.startsWith("org.orcid") || Collection.class.isAssignableFrom(returnType) || String.class.isAssignableFrom(returnType)
|| "long".equals(returnType.getName())) {
CANDIDATE_CLASSES.put(returnType, returnType);
return true;
}
}
return false;
}
private Method getCorrespondingSetter(String methodName, Method[] methods) {
String setterName = methodName.replace("get", "set");
for (Method m : methods) {
if (setterName.equals(m.getName())) {
return m;
}
}
return null;
}
}