net.sf.morph.util.TransformerUtils.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.morph.util.TransformerUtils.java

Source

/*
 * Copyright 2004-2005, 2007-2008 the original author or authors.
 *
 * 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 net.sf.morph.util;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import net.sf.composite.util.CompositeUtils;
import net.sf.composite.util.ObjectUtils;
import net.sf.morph.transform.Converter;
import net.sf.morph.transform.Copier;
import net.sf.morph.transform.ExplicitTransformer;
import net.sf.morph.transform.ImpreciseTransformer;
import net.sf.morph.transform.NestingAwareTransformer;
import net.sf.morph.transform.TransformationException;
import net.sf.morph.transform.Transformer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Utility functions for implementing and using Transformers.
 *
 * @author Matt Sgarlata
 * @since Nov 26, 2004
 */
public abstract class TransformerUtils {

    private static abstract class ClassStrategy {
        abstract Class[] get(Transformer t);
    }

    private static final ClassStrategy SOURCE = new ClassStrategy() {
        Class[] get(Transformer t) {
            return t.getSourceClasses();
        }
    };

    private static final ClassStrategy DEST = new ClassStrategy() {
        Class[] get(Transformer t) {
            return t.getDestinationClasses();
        }
    };

    private static final Log log = LogFactory.getLog(TransformerUtils.class);
    private static final Class[] CLASS_NONE = new Class[0];

    /**
     * Learn whether <code>sourceClass</code> is transformable to <code>destinationClass</code>
     * by <code>transformer</code> considering only source and destination types.
     * @param transformer
     * @param destinationClass
     * @param sourceClass
     * @return boolean
     */
    public static boolean isImplicitlyTransformable(Transformer transformer, Class destinationClass,
            Class sourceClass) {
        return ClassUtils.inheritanceContains(transformer.getDestinationClasses(), destinationClass)
                && ClassUtils.inheritanceContains(transformer.getSourceClasses(), sourceClass);
    }

    /**
     * Learn whether <code>sourceClass</code> is transformable to <code>destinationClass</code>
     * by <code>transformer</code> by implicit or explicit rules.
     * @param transformer
     * @param destinationClass
     * @param sourceClass
     * @return boolean
     * @see #isImplicitlyTransformable(Transformer, Class, Class)
     * @see ExplicitTransformer
     */
    public static boolean isTransformable(Transformer transformer, Class destinationClass, Class sourceClass) {
        if (transformer instanceof ExplicitTransformer) {
            return ((ExplicitTransformer) transformer).isTransformable(destinationClass, sourceClass);
        }
        return isImplicitlyTransformable(transformer, destinationClass, sourceClass);
    }

    /**
     * Learn whether <code>transformer</code>'s transformation
     * of <code>sourceClass</code> to <code>destinationClass</code> might yield an imprecise result.
     * @param transformer
     * @param destinationClass
     * @param sourceClass
     * @return boolean
     * @see ImpreciseTransformer
     * @since Morph 1.1
     */
    public static boolean isImpreciseTransformation(Transformer transformer, Class destinationClass,
            Class sourceClass) {
        if (transformer instanceof ImpreciseTransformer) {
            return ((ImpreciseTransformer) transformer).isImpreciseTransformation(destinationClass, sourceClass);
        }
        return destinationClass == null && sourceClass != null;
    }

    /**
     * Performs a transformation of one object graph into another object graph.
     *
     * @param destinationType
     *            the type of the root node of the destination object graph
     * @param the
     *            optional root node of the destination object graph. If this
     *            parameter is specified, it will be possible to copy
     *            information to an existing object graph rather than requiring
     *            a new object graph be created
     * @param source
     *            the root node of the source object graph
     * @param locale
     *            the locale in which any needed transformations should take
     *            place
     * @param preferredTransformationType
     *            the preferred type of transformation to be performed
     * @return the transformed object graph
     * @throws TransformationException
     *             if the graph could not be transformed for some reason
     * @see Converter#TRANSFORMATION_TYPE_CONVERT
     * @see Copier#TRANSFORMATION_TYPE_COPY
     */
    public static Object transform(Transformer transformer, Class destinationType, Object destination,
            Object source, Locale locale, Integer preferredTransformationType) throws TransformationException {

        //default to preferredTransformationType if specified, else by Transformer type
        Integer xform = preferredTransformationType != null ? preferredTransformationType
                : transformer instanceof Copier ? Transformer.TRANSFORMATION_TYPE_COPY
                        : Transformer.TRANSFORMATION_TYPE_CONVERT;

        boolean mutableDest = !ClassUtils.isImmutableObject(destination);

        // next, override impossible operations with possible ones
        // (this block is somewhat more verbose than necessary but
        // should be proof against possible additional Transformer types):
        if (Transformer.TRANSFORMATION_TYPE_COPY.equals(xform)) {
            if (!(transformer instanceof Copier && mutableDest) && transformer instanceof Converter) {
                xform = Transformer.TRANSFORMATION_TYPE_CONVERT;
            }
        } else if (Transformer.TRANSFORMATION_TYPE_CONVERT.equals(xform)) {
            if (!(transformer instanceof Converter) && transformer instanceof Copier && mutableDest) {
                xform = Transformer.TRANSFORMATION_TYPE_COPY;
            }
        }

        Exception copyException = null;
        if (Transformer.TRANSFORMATION_TYPE_COPY.equals(xform)) {
            if (log.isTraceEnabled()) {
                log.trace("Performing nested copy of " + ObjectUtils.getObjectDescription(source)
                        + " to destination " + ObjectUtils.getObjectDescription(destination));
            }
            try {
                ((Copier) transformer).copy(destination, source, locale);
                return destination;
            } catch (Exception e) {
                /* if copy fails, try to fall back to conversion. This can
                 * only happen if the choice to copy was externally specified,
                 * so we assume the failing transformation was nested,
                 * further evidenced by the fact that this method should
                 * usually be called by the framework itself. :)
                 */
                if (CompositeUtils.isSpecializable(transformer, Converter.class)) {
                    transformer = (Transformer) CompositeUtils.specialize(transformer, Converter.class);
                    //make sure the transformation we're looking for didn't fall out during specialization:
                    if (TransformerUtils.isTransformable(transformer, destinationType,
                            ClassUtils.getClass(source))) {
                        if (log.isInfoEnabled()) {
                            log.info("Trying to fall back on conversion due to copy failure", e);
                        }
                        xform = Transformer.TRANSFORMATION_TYPE_CONVERT;
                        copyException = e;
                    }
                }
                if (copyException != null) {
                    throw e instanceof TransformationException ? (TransformationException) e
                            : new TransformationException("Unable to perform graph transformation", e);
                }
            }
        }
        if (Transformer.TRANSFORMATION_TYPE_CONVERT.equals(xform)) {
            if (log.isTraceEnabled()) {
                log.trace("Performing nested conversion of " + ObjectUtils.getObjectDescription(source)
                        + " to destination type " + ObjectUtils.getObjectDescription(destinationType));
            }
            try {
                return ((Converter) transformer).convert(destinationType, source, locale);
            } catch (Exception e) {
                //if this was originally an attempted copy, throw the original exception:
                if (copyException != null) {
                    e = copyException;
                }
                throw e instanceof TransformationException ? (TransformationException) e
                        : new TransformationException("Unable to perform transformation", e);
            }
        }
        // shouldn't happen unless a new transformer type is introduced
        // and this class has not yet been updated to handle it
        throw new TransformationException("Unable to perform transformation using transformer "
                + ObjectUtils.getObjectDescription(transformer));
    }

    /**
     * Get the mapped destination type from the specified typemap.
     * @param typeMapping Map
     * @param requestedType Class
     * @return Class
     */
    public static Class getMappedDestinationType(Map typeMapping, Class requestedType) {
        if (typeMapping == null) {
            return null;
        }
        // see if the requested type has been directly mapped to some other type
        Class mappedDestinationType = (Class) typeMapping.get(requestedType);
        // see if the requested type has been indirectly mapped to some other type
        if (mappedDestinationType == null) {
            Set keys = typeMapping.keySet();
            for (Iterator i = keys.iterator(); i.hasNext();) {
                Class type = (Class) i.next();
                if (type.isAssignableFrom(requestedType)) {
                    mappedDestinationType = (Class) typeMapping.get(type);
                    break;
                }
            }
        }
        return mappedDestinationType;
    }

    /**
     * Get the set of source classes available from the specified Transformer for the specified destination type.
     * @param transformer
     * @param destinationType
     * @return Class[]
     */
    public static Class[] getSourceClasses(Transformer transformer, Class destinationType) {
        if (!ClassUtils.inheritanceContains(transformer.getDestinationClasses(), destinationType)) {
            return CLASS_NONE;
        }
        Class[] sourceTypes = transformer.getSourceClasses();
        if (transformer instanceof ExplicitTransformer) {
            Set result = ContainerUtils.createOrderedSet();
            for (int i = 0; i < sourceTypes.length; i++) {
                if (((ExplicitTransformer) transformer).isTransformable(destinationType, sourceTypes[i])) {
                    result.add(sourceTypes[i]);
                }
            }
            return result.isEmpty() ? CLASS_NONE : (Class[]) result.toArray(new Class[result.size()]);
        }
        return sourceTypes;
    }

    /**
     * Get the set of destination classes available from the specified Transformer for the specified source type.
     * @param transformer
     * @param sourceType
     * @return Class[]
     * @since Morph 1.1
     */
    public static Class[] getDestinationClasses(Transformer transformer, Class sourceType) {
        if (!ClassUtils.inheritanceContains(transformer.getSourceClasses(), sourceType)) {
            return CLASS_NONE;
        }
        Class[] destinationTypes = transformer.getDestinationClasses();
        if (transformer instanceof ExplicitTransformer) {
            Set result = ContainerUtils.createOrderedSet();
            for (int i = 0; i < destinationTypes.length; i++) {
                if (((ExplicitTransformer) transformer).isTransformable(destinationTypes[i], sourceType)) {
                    result.add(destinationTypes[i]);
                }
            }
            return result.isEmpty() ? CLASS_NONE : (Class[]) result.toArray(new Class[result.size()]);
        }
        return destinationTypes;
    }

    /**
     * Get the set of source classes common to all specified Transformers.
     * @param transformers
     * @return Class[]
     */
    public static Class[] getSourceClassIntersection(Transformer[] transformers) {
        return getClassIntersection(transformers, SOURCE);
    }

    /**
     * Get the set of destination classes common to all specified Transformers.
     * @param transformers
     * @return Class[]
     */
    public static Class[] getDestinationClassIntersection(Transformer[] transformers) {
        return getClassIntersection(transformers, DEST);

    }

    /**
     * If <code>nestingAware</code> implements {@link NestingAwareTransformer} and does not
     * currently have a nested {@link Transformer} set, set <code>nestedTransformer</code>
     * as its nested {@link Transformer}.
     * @param nestingAware
     * @param nestedTransformer
     * @return whether a change was made
     * @since Morph 1.1.2
     */
    public static boolean setDefaultNestedTransformer(Transformer nestingAware, Transformer nestedTransformer) {
        return replaceNestedTransformer(nestingAware, null, nestedTransformer);
    }

    /**
     * Replace <code>oldNestedTransformer</code> with <code>newNestedTransformer</code> as nested
     * {@link Transformer} of <code>nestingAware</code> if <code>nestingAware</code> implements
     * {@link NestingAwareTransformer}.
     * @param nestingAware
     * @param oldNestedTransformer
     * @param newNestedTransformer
     * @return whether a change was made
     * @since Morph 1.1.2
     */
    public static boolean replaceNestedTransformer(Transformer nestingAware, Transformer oldNestedTransformer,
            Transformer newNestedTransformer) {
        if (nestingAware instanceof NestingAwareTransformer) {
            NestingAwareTransformer nat = (NestingAwareTransformer) nestingAware;
            if (ObjectUtils.equals(nat.getNestedTransformer(), oldNestedTransformer)) {
                nat.setNestedTransformer(newNestedTransformer);
                return true;
            }
        }
        return false;
    }

    private static Class[] getClassIntersection(Transformer[] transformers, ClassStrategy strategy) {
        Set s = ContainerUtils.createOrderedSet();
        s.addAll(Arrays.asList(strategy.get(transformers[0])));

        for (int i = 1; i < transformers.length; i++) {
            Set survivors = ContainerUtils.createOrderedSet();
            Class[] c = strategy.get(transformers[i]);
            for (int j = 0; j < c.length; j++) {
                if (s.contains(c[j])) {
                    survivors.add(c[j]);
                    break;
                }
                if (c[j] == null) {
                    break;
                }
                for (Iterator it = s.iterator(); it.hasNext();) {
                    Class next = (Class) it.next();
                    if (next != null && next.isAssignableFrom(c[j])) {
                        survivors.add(c[j]);
                        break;
                    }
                }
            }
            if (!survivors.containsAll(s)) {
                for (Iterator it = s.iterator(); it.hasNext();) {
                    Class next = (Class) it.next();
                    if (survivors.contains(next) || next == null) {
                        break;
                    }
                    for (int j = 0; j < c.length; j++) {
                        if (c[j] != null && c[j].isAssignableFrom(next)) {
                            survivors.add(next);
                            break;
                        }
                    }
                }
            }
            s = survivors;
        }
        return s.isEmpty() ? CLASS_NONE : (Class[]) s.toArray(new Class[s.size()]);
    }

}