Package ma.glasnost.orika.impl.generator.specification

Source Code of ma.glasnost.orika.impl.generator.specification.MultiOccurrenceToMultiOccurrence

package ma.glasnost.orika.impl.generator.specification;

import static java.lang.String.format;
import static ma.glasnost.orika.impl.generator.SourceCodeContext.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.generator.AggregateSpecification;
import ma.glasnost.orika.impl.generator.Node;
import ma.glasnost.orika.impl.generator.Node.NodeList;
import ma.glasnost.orika.impl.generator.SourceCodeContext;
import ma.glasnost.orika.impl.generator.VariableRef;
import ma.glasnost.orika.impl.util.ClassUtil;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import ma.glasnost.orika.metadata.FieldMap;
import ma.glasnost.orika.metadata.MapperKey;
import ma.glasnost.orika.metadata.Property;
import ma.glasnost.orika.metadata.Type;

/**
* @author mattdeboer
*
*/
public class MultiOccurrenceToMultiOccurrence implements AggregateSpecification {
   
    protected MapperFactory mapperFactory;
   
    /**
     * Generates the code to support a (potentially parallel) mapping from one
     * or more multi-occurrence fields in the source type to one or more
     * multi-occurrence fields in the destination type.
     *
     * @param fieldMappings
     *            the field mappings to be applied
     * @param code
     * @param logDetails
     *            a StringBuilder to accept debug logging information
     * @return a reference to <code>this</code> SourceCodeBuilder
     */
    public String fromMultiOccurrenceToMultiOccurrence(List<FieldMap> fieldMappings, SourceCodeContext code) {
       
        StringBuilder out = new StringBuilder();
        while (!fieldMappings.isEmpty()) {
            Set<FieldMap> associated = code.getAssociatedMappings(fieldMappings, fieldMappings.get(0));
            fieldMappings.removeAll(associated);
           
            NodeList sourceNodes = new NodeList();
            NodeList destNodes = new NodeList();
           
            for (FieldMap map : associated) {

                Node.addFieldMap(map, sourceNodes, true);
                Node.addFieldMap(map, destNodes, false);
            }   
             
            registerClassMaps(sourceNodes, destNodes);
           
            out.append(generateMultiOccurrenceMapping(sourceNodes, destNodes, associated, code));
        }
        return out.toString();
    }
   
    /**
     * Generates the code to support a (potentially parallel) mapping from one
     * or more multi-occurrence fields in the source type to one or more
     * multi-occurrence fields in the destination type.
     * @param sourceNodes
     * @param destNodes
     *
     * @param sources
     *            the associated source variables
     * @param destinations
     *            the associated destination variables
     * @param subFields
     *            the nested properties of the individual field maps
     * @param code
     * @param logDetails
     *            a StringBuilder to accept debug logging information
     * @return a reference to <code>this</code> CodeSourceBuilder
     */
    public String generateMultiOccurrenceMapping(NodeList sourceNodes, NodeList destNodes,
            Set<FieldMap> subFields, SourceCodeContext code) {
       
        StringBuilder out = new StringBuilder();
       
        /*
         * Construct size/length expressions used to limit the parallel iteration
         * of multiple source variables; only keep iterating so long as all variables
         * in a parallel set are non-empty
         */
        List<String> sourceSizes = new ArrayList<String>();
        for (Node ref : sourceNodes) {
            if (!ref.isLeaf()) {
                sourceSizes.add(ref.multiOccurrenceVar.size());
            }
        }
       
        String sizeExpr = join(sourceSizes, ", ");
        if (!"".equals(sizeExpr)) {
            sizeExpr = "min(new int[]{" + sizeExpr + "})";
        }
       
        /*
         * Declare "collector" elements and their iterators; used for aggregating
         * results which are finally assigned/copied back into their final destination(s)
         */
        for (Node destRef : destNodes) {
           
            if (!destRef.isLeaf()) {
                out.append(statement(destRef.newDestination.declare(destRef.newDestination.newInstance(sizeExpr))));
                if (destRef.newDestination.isArray()) {
                    out.append(statement(destRef.newDestination.declareIterator()));
                }
                List<Node> children = new ArrayList<Node>();
                children.add(destRef);
                while (!children.isEmpty()) {
                    Node child = children.remove(0);
                    children.addAll(child.children);
                    if (child.elementRef != null) {
                        out.append(statement(child.elementRef.declare()));
                        if (child.multiOccurrenceVar.isArray()) {
                            out.append(statement(child.multiOccurrenceVar.declareIterator()));
                        }
                        if (child.elementRef.isPrimitive()) {
                            out.append(statement(child.nullCheckFlag.declare("true")));
                        }
                        out.append(statement(child.shouldAddToCollectorFlag.declare("false")));
                    }
                }
            }
        }
       
        // TODO: need to create a flag variable to mark whether destination has been added
        // to it's collector; it should be set to false upon new destination element creation
       
       
//        MapperFactory mapperFactory = (MapperFactory) code.getMappingContext().getProperty(Properties.MAPPER_FACTORY);
       
        StringBuilder endWhiles = new StringBuilder();
        StringBuilder addLastElement = new StringBuilder();
       
        iterateSources(sourceNodes, destNodes, out, endWhiles);
       
        LinkedList<Node> stack = new LinkedList<Node>(destNodes);
        while (!stack.isEmpty()) {
           
            Node currentNode = stack.removeFirst();
            stack.addAll(0, currentNode.children);
            Node srcNode = null;
            if (currentNode.value != null) {
                srcNode = Node.findFieldMap(currentNode.value, sourceNodes, true);
            } else {
                FieldMap fieldMap = currentNode.getMap();
                if (fieldMap != null) {
                    srcNode = Node.findFieldMap(fieldMap, sourceNodes, true).parent;
                }
            }
           
            if (!currentNode.isLeaf() && srcNode != null) {
                /*
                 * Add a comparison for the next source element; if it is "different" than
                 * it's destination (determined by custom comparator we've generated), then
                 * we create a new element and add it to the destination collector.
                 */
                String currentElementNull = currentNode.elementRef.isPrimitive() ? currentNode.nullCheckFlag.toString() : currentNode.elementRef.isNull();
                String currentElementComparator = code.currentElementComparator(srcNode, currentNode, sourceNodes, destNodes);
                String or = (!"".equals(currentElementNull) && !"".equals(currentElementComparator)) ? " || " : "";
               
                if (mapperFactory.getConverterFactory().canConvert(srcNode.elementRef.type(), currentNode.elementRef.type()) //) {
                        || ClassUtil.isImmutable(currentNode.elementRef.type())) {
               
                    append(out,
                            (currentNode.elementRef.isPrimitive() ? currentNode.nullCheckFlag.assign("false") : ""),
                            currentNode.shouldAddToCollectorFlag.assign("true")
                            );
                   
               
                } else {
               
                    append(out,
                            "if ( " + currentElementNull + or + currentElementComparator + ") {\n",
                            currentNode.elementRef.assign(code.newObject(srcNode.elementRef, currentNode.elementRef.type())),
                            currentNode.shouldAddToCollectorFlag.assign("true"),
                            "}");
                }
               
            }
           
            if (currentNode.value != null) {
               
                /*
                 * If we have a fieldMap for the current node, attempt to map the fields
                 */
                boolean wasConverted = mapFields(currentNode, srcNode, out, code);
                if (!currentNode.parent.addedToCollector) {
                    String assignNull = (currentNode.parent.elementRef.isPrimitive() ? currentNode.parent.nullCheckFlag.assign("true") : currentNode.parent.elementRef.assign("null"));
                    if (mapperFactory.getConverterFactory().canConvert(srcNode.parent.elementRef.type(), currentNode.parent.elementRef.type())) {
                        append(out,
                                (currentNode.parent.isRoot() ? currentNode.parent.newDestination.add(currentNode.parent.elementRef) : currentNode.parent.multiOccurrenceVar.add(currentNode.parent.elementRef)),
                                assignNull
                                );
                    } else {
                        append(out,
                                format("if (%s) {", currentNode.parent.shouldAddToCollectorFlag),
                                (currentNode.parent.isRoot() ? currentNode.parent.newDestination.add(currentNode.parent.elementRef) : currentNode.parent.multiOccurrenceVar.add(currentNode.parent.elementRef)),
                                currentNode.parent.shouldAddToCollectorFlag.assign("false"),
                                (wasConverted ? assignNull : ""),
                                "}");
                    }
                    currentNode.parent.addedToCollector = true;
                }
            }
        } 
       
        out.append(endWhiles.toString());
        out.append(addLastElement.toString());
       
        /*
         * Finally, we loop over the destination nodes and assign/copy all of the temporary
         * "collector" variables back into their final destination
         */
        for (Node destRef : destNodes) {
            if (destRef.isRoot() && !destRef.isLeaf()) {
                if (destRef.multiOccurrenceVar.isArray() || destRef.multiOccurrenceVar.isMap()) {
                    /*
                     * We use a List as the temporary collector element for Arrays and Maps
                     */
                    append(out,
                            format("if (%s && %s) {",destRef.newDestination.notNull(), destRef.newDestination.notEmpty()),
                            destRef.multiOccurrenceVar.addAll(destRef.newDestination),
                            "}\n");
                } else {
                    append(out,
                            format("if (%s && %s) {",destRef.newDestination.notNull(), destRef.newDestination.notEmpty()),
                            format("if (%s) {", destRef.multiOccurrenceVar.isNull()),
                            destRef.multiOccurrenceVar.assignIfPossible(destRef.multiOccurrenceVar.newInstance(sizeExpr)),
                            "} else {\n",
                            destRef.multiOccurrenceVar + ".clear()",
                            "}\n",
                            destRef.multiOccurrenceVar.addAll(destRef.newDestination),
                            "}\n");
                }
            }
        }
       
        return out.toString();
    }
   
    private Property innermostElement(final Property p) {
        Property result = p;
        while (result.getElement() != null) {
            result = result.getElement();
        }
        return result;
    }
   
    private boolean mapFields(Node currentNode, Node srcNode, StringBuilder out, SourceCodeContext code) {
       
        String srcName = srcNode.parent != null ? srcNode.parent.elementRef.name() : "source";
       
        Property sp = innermostElement(currentNode.value.getSource());
        Property srcProp = new Property.Builder().merge(sp).expression(innermostElement(currentNode.value.getSource()).getExpression()).build();
        VariableRef s = new VariableRef(srcProp, srcName);
       
        Property dp = innermostElement(currentNode.value.getDestination());
        Property dstProp = new Property.Builder().merge(dp).expression(innermostElement(currentNode.value.getDestination()).getExpression()).build();
        String dstName =  "destination";
        if (currentNode.parent != null ) {
            dstName = currentNode.parent.elementRef.name();
        }
       
        VariableRef d = new VariableRef(dstProp, dstName);
       
        Type<?> destType = currentNode.parent != null ? currentNode.parent.elementRef.type() : null;
       
        out.append(statement(code.mapFields(currentNode.value, s, d, destType, null)));
       
        return d.type().equals(currentNode.parent.elementRef.type()) && mapperFactory.getConverterFactory().canConvert(s.type(), d.type());
    }
   
   
    /**
     * Register the ClassMaps needed to map this pair of source and
     * destination nodes.
     *
     * @param sourceNodes
     * @param destNodes
     */
    private void registerClassMaps(NodeList sourceNodes, NodeList destNodes) {
        /*
         * Register all of the subordinate ClassMaps needed by this multi-occurrence
         * mapping
         */
        Map<MapperKey, ClassMapBuilder<?,?>> builders = new HashMap<MapperKey, ClassMapBuilder<?,?>>();
       
        LinkedList<Node> stack = new LinkedList<Node>(destNodes);
        while (!stack.isEmpty()) {
           
            Node currentNode = stack.removeFirst();
            stack.addAll(0, currentNode.children);
            Node srcNode = null;
            if (currentNode.value != null) {
                srcNode = Node.findFieldMap(currentNode.value, sourceNodes, true);
            } else {
                FieldMap fieldMap = currentNode.getMap();
                if (fieldMap != null) {
                    srcNode = Node.findFieldMap(fieldMap, sourceNodes, true).parent;
                }
            }
       
            if (srcNode.parent != null
                    && srcNode.parent.elementRef != null
                    && currentNode.parent != null
                    && currentNode.parent.elementRef != null) {
               
                MapperKey key = new MapperKey(srcNode.parent.elementRef.type(), currentNode.parent.elementRef.type());
                if (!ClassUtil.isImmutable(key.getAType())
                        && !ClassUtil.isImmutable(key.getBType())
                        && !mapperFactory.existsRegisteredMapper(key.getAType(), key.getBType(), true)) {
                    ClassMapBuilder<?,?> builder = builders.get(key);
                    if (builder == null) {
                        builder = mapperFactory.classMap(key.getAType(), key.getBType());
                        builders.put(key, builder);
                    }
                    Property sp = innermostElement(currentNode.value.getSource());
                    Property dp = innermostElement(currentNode.value.getDestination());
                    builder.fieldMap(sp.getExpression(), dp.getExpression()).add();
                }
            }
        }
       
       
        for (ClassMapBuilder<?,?> builder: builders.values()) {
            builder.register();
        }
    }
   
    /**
     * Creates the looping constructs for nested source variables
     *
     * @param sourceNodes
     * @param destNodes
     * @param out
     * @param endWhiles
     */
    private void iterateSources(NodeList sourceNodes, NodeList destNodes, StringBuilder out, StringBuilder endWhiles) {
       
        if (!sourceNodes.isEmpty()) {
            for (Node srcRef : sourceNodes) {
                if (!srcRef.isLeaf()) {
                    out.append(statement(srcRef.multiOccurrenceVar.declareIterator()));
                }
            }
           
            StringBuilder loopSource = new StringBuilder();
            /*
             * Create while loop for the top level multi-occurrence objects
             */
            loopSource.append("while (");
            Iterator<Node> sourcesIter = sourceNodes.iterator();
            boolean atLeastOneIter = false;
            while (sourcesIter.hasNext()) {
                Node ref = sourcesIter.next();
                if (!ref.isLeaf()) {
                    if (atLeastOneIter) {
                        loopSource.append(" && ");
                    }
                    loopSource.append(ref.multiOccurrenceVar.iteratorHasNext());
                    atLeastOneIter = true;
                }
            }
            loopSource.append(") {");
           
            if (atLeastOneIter) {
                out.append("\n");
                out.append(loopSource.toString());
            }
            for (Node srcRef : sourceNodes) {
               
                if (!srcRef.isLeaf()) {
                    out.append(statement(srcRef.elementRef.declare(srcRef.multiOccurrenceVar.nextElement())));
                    iterateSources(srcRef.children, destNodes, out, endWhiles);
                }
            }
            if (atLeastOneIter) {
                endWhiles.append("}\n");
            }
        }
    }

    /* (non-Javadoc)
     * @see ma.glasnost.orika.impl.generator.AggregateSpecification#appliesTo(ma.glasnost.orika.metadata.FieldMap)
     */
    public boolean appliesTo(FieldMap fieldMap) {
        return fieldMap.getSource().getContainer() != null || fieldMap.getDestination().getContainer() != null;
    }

    /* (non-Javadoc)
     * @see ma.glasnost.orika.impl.generator.AggregateSpecification#generateMappingCode(java.util.Set, ma.glasnost.orika.impl.generator.SourceCode)
     */
    public String generateMappingCode(List<FieldMap> fieldMappings, SourceCodeContext code) {
        return this.fromMultiOccurrenceToMultiOccurrence(fieldMappings, code);
    }

    /* (non-Javadoc)
     * @see ma.glasnost.orika.impl.generator.AggregateSpecification#setMapperFactory(ma.glasnost.orika.MapperFactory)
     */
    public void setMapperFactory(MapperFactory mapperFactory) {
        this.mapperFactory = mapperFactory;
    }
}
TOP

Related Classes of ma.glasnost.orika.impl.generator.specification.MultiOccurrenceToMultiOccurrence

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.