org.androidtransfuse.processor.Merger.java Source code

Java tutorial

Introduction

Here is the source code for org.androidtransfuse.processor.Merger.java

Source

/**
 * Copyright 2011-2015 John Ericksen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.androidtransfuse.processor;

import org.androidtransfuse.TransfuseAnalysisException;
import org.androidtransfuse.model.Identified;
import org.androidtransfuse.model.Mergeable;
import org.apache.commons.beanutils.PropertyUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author John Ericksen
 */
public class Merger {

    public <T> T merge(Class<? extends T> targetClass, T target, T source) throws MergerException {

        if (target == null) {
            return source;
        } else if (source == null) {
            return target;
        }

        if (!Mergeable.class.isAssignableFrom(targetClass)) {
            return target;
        }

        return (T) mergeMergeable((Class<? extends Mergeable>) targetClass, (Mergeable) target, (Mergeable) source);
    }

    private <T extends Mergeable> T mergeMergeable(Class<? extends T> targetClass, T target, T source)
            throws MergerException {

        try {

            BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);

            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                Method getter = propertyDescriptor.getReadMethod();
                Method setter = propertyDescriptor.getWriteMethod();

                String propertyName = propertyDescriptor.getDisplayName();

                if (PropertyUtils.isWriteable(target, propertyName)) {

                    //check for mergeCollection
                    MergeCollection mergeCollection = findAnnotation(MergeCollection.class, getter, setter);
                    if (Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                        PropertyUtils.setProperty(target, propertyName,
                                mergeList(mergeCollection, propertyName, target, source));
                    }

                    //check for merge
                    Merge mergeAnnotation = findAnnotation(Merge.class, getter, setter);
                    PropertyUtils.setProperty(target, propertyName,
                            mergeProperties(mergeAnnotation, propertyName, target, source));
                }
            }
        } catch (NoSuchMethodException e) {
            throw new MergerException("NoSuchMethodException while trying to merge", e);
        } catch (IntrospectionException e) {
            throw new MergerException("IntrospectionException while trying to merge", e);
        } catch (IllegalAccessException e) {
            throw new MergerException("IllegalAccessException while trying to merge", e);
        } catch (InvocationTargetException e) {
            throw new MergerException("InvocationTargetException while trying to merge", e);
        }

        return target;
    }

    private <T extends Annotation> T findAnnotation(Class<T> annotationClass, Method... methods) {
        T annotation = null;
        if (methods != null) {
            for (Method method : methods) {
                if (annotation == null && method != null && method.isAnnotationPresent(annotationClass)) {
                    annotation = method.getAnnotation(annotationClass);
                }
            }
        }
        return annotation;
    }

    private <T extends Mergeable> Object mergeProperties(Merge mergeAnnotation, String propertyName, T target,
            T source) throws MergerException {

        try {

            String tag = null;
            if (mergeAnnotation != null) {
                tag = mergeAnnotation.value();
            }

            Object targetProperty = PropertyUtils.getProperty(target, propertyName);
            Object sourceProperty = PropertyUtils.getProperty(source, propertyName);
            Class propertyType = PropertyUtils.getPropertyType(target, propertyName);

            Object merged;
            if (tag != null && target.isGenerated() && target.containsTag(tag)) {
                merged = sourceProperty;
            } else {
                merged = merge(propertyType, targetProperty, sourceProperty);
            }

            updateTag(target, tag, merged == null);
            return merged;

        } catch (NoSuchMethodException e) {
            throw new MergerException("NoSuchMethodException while trying to merge", e);
        } catch (IllegalAccessException e) {
            throw new MergerException("IllegalAccessException while trying to merge", e);
        } catch (InvocationTargetException e) {
            throw new MergerException("InvocationTargetException while trying to merge", e);
        }
    }

    private <T extends Mergeable> void updateTag(T target, String tag, boolean remove) {
        if (tag != null) {
            if (remove) {
                target.removeMergeTag(tag);
            } else {
                target.addMergeTag(tag);
            }
        }
    }

    private <T extends Mergeable> List mergeList(MergeCollection mergeCollectionAnnotation, String propertyName,
            T target, T source) throws MergerException {

        try {

            List targetCollection = (List) PropertyUtils.getProperty(target, propertyName);
            List sourceCollection = (List) PropertyUtils.getProperty(source, propertyName);

            if (mergeCollectionAnnotation == null) {
                return (List) merge(PropertyUtils.getPropertyType(target, propertyName), targetCollection,
                        sourceCollection);
            }

            //update collection from source
            Collection<Mergeable> merged = updateFromSource(targetCollection, sourceCollection,
                    mergeCollectionAnnotation.type());

            List targetResult = makeCollection(targetCollection, mergeCollectionAnnotation.collectionType(), target,
                    propertyName);

            targetResult.clear();
            targetResult.addAll(merged);

            return targetResult;

        } catch (NoSuchMethodException e) {
            throw new MergerException("NoSuchMethodException while trying to merge", e);
        } catch (IllegalAccessException e) {
            throw new MergerException("IllegalAccessException while trying to merge", e);
        } catch (InvocationTargetException e) {
            throw new MergerException("InvocationTargetException while trying to merge", e);
        }
    }

    private <T extends Mergeable> List makeCollection(List targetList, Class<? extends List> listType, T target,
            String propertyName) throws MergerException {

        try {
            //merger only supports Lists
            if (targetList == null) {
                //first look for specific impl in annotation
                if (listType != List.class) {
                    return listType.newInstance();
                } else {
                    //try to instantiate field type
                    return (List) PropertyUtils.getPropertyType(target, propertyName).newInstance();
                }
            }

            return targetList;
        } catch (NoSuchMethodException e) {
            throw new MergerException("NoSuchMethodException while trying to merge", e);
        } catch (IllegalAccessException e) {
            throw new MergerException("IllegalAccessException while trying to merge", e);
        } catch (InstantiationException e) {
            throw new MergerException("InstantiationException while trying to merge", e);
        } catch (InvocationTargetException e) {
            throw new MergerException("InvocationTargetException while trying to merge", e);
        }
    }

    private List<Mergeable> updateFromSource(List targetList, List sourceList, Class<? extends Mergeable> type)
            throws MergerException {

        Map<Object, Mergeable> targetMap = buildIdentifierMap(targetList);
        Map<Object, Mergeable> sourceMap = buildIdentifierMap(sourceList);
        Set<Object> originalTargetKeys = new HashSet<Object>(targetMap.keySet());

        try {
            //update
            for (Map.Entry<Object, Mergeable> mergeableSourceEntry : sourceMap.entrySet()) {

                Object sourceKey = mergeableSourceEntry.getKey();

                if (targetMap.containsKey(sourceKey)) {
                    //replace
                    Mergeable targetValue = targetMap.get(sourceKey);
                    if (targetValue.isGenerated()) {
                        targetMap.put(sourceKey, merge(type, targetValue, mergeableSourceEntry.getValue()));
                    }
                } else {
                    targetMap.put(sourceKey, merge(type, type.newInstance(), mergeableSourceEntry.getValue()));
                }
                originalTargetKeys.remove(sourceKey);
            }

            //remove the targets were not updated
            for (Object targetKey : originalTargetKeys) {
                Mergeable mergeable = targetMap.get(targetKey);

                if (mergeable.isGenerated()) {
                    targetMap.remove(targetKey);
                }
            }
        } catch (IllegalAccessException e) {
            throw new MergerException("IllegalAccessException while trying to merge", e);
        } catch (InstantiationException e) {
            throw new MergerException("InstantiationException while trying to merge!", e);
        }

        //order should e preserved by LinkedHashMap
        return new ArrayList<Mergeable>(targetMap.values());
    }

    private Map<Object, Mergeable> buildIdentifierMap(List input) {
        Map<Object, Mergeable> mergeable = new LinkedHashMap<Object, Mergeable>();

        if (input != null) {
            int i = 0;
            for (Object o : input) {
                //validate all instance are of type Mergeable
                if (!(o instanceof Mergeable)) {
                    throw new TransfuseAnalysisException(
                            "Merge collection failed on type: " + o.getClass().getName());
                }
                Mergeable t = (Mergeable) o;
                Object key;
                if (t instanceof Identified) {
                    key = ((Identified) t).getIdentifier();
                } else {
                    key = i;
                }
                mergeable.put(key, t);
                i++;
            }
        }

        return mergeable;
    }
}