All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simpleflatmapper.map.context.MappingContextFactoryBuilder Maven / Gradle / Ivy

Go to download

Java library to map flat record - ResultSet, csv - to java object with minimum configuration and low footprint.

There is a newer version: 9.0.2
Show newest version
package org.simpleflatmapper.map.context;


import org.simpleflatmapper.converter.ContextFactoryBuilder;
import org.simpleflatmapper.map.MappingContext;
import org.simpleflatmapper.map.context.impl.KeyDefinitionBuilder;
import org.simpleflatmapper.map.impl.JoinUtils;
import org.simpleflatmapper.reflect.meta.ArrayElementPropertyMeta;
import org.simpleflatmapper.reflect.meta.MapKeyValueElementPropertyMeta;
import org.simpleflatmapper.reflect.meta.PropertyMeta;
import org.simpleflatmapper.map.context.impl.BreakDetectorMappingContextFactory;
import org.simpleflatmapper.map.context.impl.NullChecker;
import org.simpleflatmapper.map.context.impl.ValuedMappingContextFactory;
import org.simpleflatmapper.reflect.setter.AppendCollectionSetter;
import org.simpleflatmapper.util.Predicate;
import org.simpleflatmapper.util.Supplier;

import java.util.ArrayList;
import java.util.List;

public class MappingContextFactoryBuilder implements ContextFactoryBuilder {

    private final Counter counter;
    private final int currentIndex;
    private final MappingContextFactoryBuilder parent;
    private final List> keys;
    private final List> inferNullColumns;
    private final KeySourceGetter keySourceGetter;
    private final List> children = new ArrayList>();
    private final List> suppliers = new ArrayList>();
    private final PropertyMeta owner;
    private final boolean ignoreRootKey;

    public MappingContextFactoryBuilder(KeySourceGetter keySourceGetter, boolean ignoreRootKey) {
        this(new Counter(), new ArrayList>(), new ArrayList>(), keySourceGetter, null, null, ignoreRootKey);
    }

    protected MappingContextFactoryBuilder(Counter counter, List> keys, List> inferNullColumns, KeySourceGetter keySourceGetter, MappingContextFactoryBuilder parent, PropertyMeta owner, boolean ignoreRootKey) {
        this.counter = counter;
        this.currentIndex = counter.value;
        this.keys = keys;
        this.inferNullColumns = inferNullColumns;
        this.keySourceGetter = keySourceGetter;
        this.parent = parent;
        this.ignoreRootKey = ignoreRootKey;
        this.counter.value++;
        this.owner = owner;
    }


    public void addKey(KeyAndPredicate keyAndPredicate) {
        addKeyTo(keyAndPredicate, keys);
        addKeyTo(keyAndPredicate, inferNullColumns);
    }

    public void addInferNull(KeyAndPredicate keyAndPredicate) {
        addKeyTo(keyAndPredicate, inferNullColumns);
    }


    private void addKeyTo(KeyAndPredicate keyAndPredicate, List> keyAndPredicates) {
        for(int i = 0; i < keyAndPredicates.size(); i++) {
            KeyAndPredicate kp = keyAndPredicates.get(i);
            if (kp.key.equals(keyAndPredicate.key)) {
                keyAndPredicates.set(i, kp.mergeWith(keyAndPredicate));
                return;
            }
        }
        keyAndPredicates.add(keyAndPredicate);
    }

    @Override
    public int addSupplier(Supplier supplier) {
        if (parent == null) {
            int index = suppliers.size();
            suppliers.add(index, supplier);
            return index;
        } else {
            return parent.addSupplier(supplier);
        }
    }

    public Predicate nullChecker() {
        return new NullChecker(inferNullColumns, keySourceGetter);
    }

    public MappingContextFactoryBuilder newBuilder(List> subKeys, List> inferNullColumns, PropertyMeta owner) {
        // look for duplicate 
        for(MappingContextFactoryBuilder child : children) {
            if ((child.owner.getPath().equals(owner.getPath())
                        && child.owner.getPropertyClassMeta().equals(owner.getPropertyClassMeta()))
            ) {
                return child;
            }
        }
        
        MappingContextFactoryBuilder subBuilder = new MappingContextFactoryBuilder(counter, subKeys, inferNullColumns, keySourceGetter, this, owner, ignoreRootKey);
        children.add(subBuilder);
        return subBuilder;
    }

    @SuppressWarnings("unchecked")
    @Override
    public MappingContextFactory build() {
        if (parent != null)  {
            throw new IllegalStateException();
        }


        MappingContextFactory context;

        ArrayList> builders = new ArrayList>();
        addAllBuilders(builders);
        
        if (suppliers.isEmpty()) {
            context = MappingContext.EMPTY_FACTORY;
        } else {
            context = new ValuedMappingContextFactory(suppliers);
        }

        if (hasKeys(builders)) {
            KeyDefinitionBuilder[] keyDefinitionsBuilder = new KeyDefinitionBuilder[builders.get(builders.size() - 1).currentIndex + 1];

            for (int i = 0; i < builders.size(); i++) {
                MappingContextFactoryBuilder builder = builders.get(i);

                populateKey(keyDefinitionsBuilder, builders, builder);
            }

            KeyDefinition[] keyDefinitions = KeyDefinitionBuilder.toKeyDefinitions(keyDefinitionsBuilder);
            KeyDefinition rootKeyDefinition = keyDefinitions[0];

            context = new BreakDetectorMappingContextFactory(rootKeyDefinition, keyDefinitions, context);
        }

        return context;
    }



    private KeyDefinitionBuilder populateKey(KeyDefinitionBuilder[] keyDefinitions, ArrayList> builders, MappingContextFactoryBuilder builder) {

        if (keyDefinitions[builder.currentIndex] != null) {
            return keyDefinitions[builder.currentIndex];
        }

        int parentIndex = builder.getNonEmptyParentIndex();

        KeyDefinitionBuilder parent = null;
        if (parentIndex != -1) {
            parent = keyDefinitions[parentIndex];
            if (parent == null) {
                // not yet define look for parent and create key
                for(int i = 0; i < builders.size(); i++) {
                    MappingContextFactoryBuilder potentialParent = builders.get(i);
                    if (potentialParent.currentIndex == parentIndex) {
                        parent = populateKey(keyDefinitions, builders, potentialParent);
                        break;
                    }
                }
                if (parent == null) {
                    throw new IllegalArgumentException("Could not find parent for builder " + builder);
                }
            }
        }

        KeyDefinitionBuilder keyDefinition;

        // empty key use parent key except for child of appendsetter
        if (parent != null && builder.inheritKeys(parentIndex)) {
             keyDefinition = parent.asChild(builder.currentIndex);
        } else {
            List> keys = new ArrayList>(builder.effectiveKeys());

            // ignore root parent
            if (parent != null && (parentIndex >0 || !ignoreRootKey)) {
                appendParentKeys(parent, keys);
            }
            
            keyDefinition = new KeyDefinitionBuilder(keys, builder.keySourceGetter, builder.currentIndex);
        }

        keyDefinitions[builder.currentIndex] = keyDefinition;
        return keyDefinition;
    }

    private boolean inheritKeys(int parentIndex) {
        return (effectiveKeys().isEmpty() && ! newObjectOnEachRow(parentIndex));
    }

    private void appendParentKeys(KeyDefinitionBuilder parent, List> keys) {
        // if keys is empty we generate a new row every time so leave empty
        if (!keys.isEmpty()) {
            for(KeyAndPredicate k : parent.getKeyAndPredicates()) {
                addKeyTo(k, keys);
            }
        }
    }

    private List> effectiveKeys() {

        if (!keys.isEmpty()) {
            return keys;
        }

        List> keys = new ArrayList>();
        for(MappingContextFactoryBuilder child : children) {
            if (child.isEligibleAsSubstituteKey()) {
                keys.addAll(child.effectiveKeys());
            }

        }
        return keys;
    }


    private boolean newObjectOnEachRow(int parentIndex) {
        if (owner instanceof ArrayElementPropertyMeta) {
            ArrayElementPropertyMeta elementPropertyMeta = (ArrayElementPropertyMeta) owner;
            if (elementPropertyMeta.getSetter() instanceof AppendCollectionSetter) {
                return true;
            }
        } else if (owner instanceof MapKeyValueElementPropertyMeta) {
            return true;
        }
        
        if (parent != null && parent.currentIndex != parentIndex ) {
            return parent.newObjectOnEachRow(parentIndex);
        }
        
        return false;
    }

    private static  boolean hasKeys(ArrayList> builders) {
        for(int i = 0; i < builders.size(); i++) {
            if (!builders.get(i).hasNoKeys()) return true;
        }
        return false;
    }

    private boolean isEligibleAsSubstituteKey() {
        return !JoinUtils.isArrayElement(owner);
    }

    // ignore empty parent useful to skip root keys
    private int getNonEmptyParentIndex() {
        return parent == null
                ? -1
                : parent.effectiveKeys().isEmpty() ? parent.getNonEmptyParentIndex() : parent.currentIndex;
    }


    private void addAllBuilders(ArrayList> builders) {
        builders.add(this);
        for(MappingContextFactoryBuilder child : children) {
            child.addAllBuilders(builders);
        }
    }

    public boolean hasNoKeys() {
        return effectiveKeys().isEmpty();
    }

    public boolean hasNoDependentKeys() {
        if (!hasNoKeys()) {
            return false;
        }

        for(MappingContextFactoryBuilder builder : children) {
            if (!builder.hasNoDependentKeys()) {
                return false;
            }
        }
        return true;
    }

    public boolean isRoot() {
        return parent == null;
    }

    public int currentIndex() {
        return currentIndex;
    }

    public boolean hasChildren() {
        return children.isEmpty();
    }


    private static class Counter {
        int value;
    }

    @Override
    public String toString() {
        return "MappingContextFactoryBuilder{" +
                "currentIndex=" + currentIndex +
                ", keys=" + keys +
                ", children=" + children +
                '}';
    }
}