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.map.MappingContext;
import org.simpleflatmapper.map.context.impl.KeyDefinitionBuilder;
import org.simpleflatmapper.reflect.meta.ArrayElementPropertyMeta;
import org.simpleflatmapper.reflect.meta.MapElementPropertyMeta;
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 {

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

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

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


    public void addKey(K key) {
        if (!keys.contains(key)) {
            keys.add(key);
        }
    }

    public void addSupplier(int index, Supplier supplier) {
        while(suppliers.size() <= index) {
            suppliers.add(null);
        }
        suppliers.set(index, supplier);
    }

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

    public MappingContextFactoryBuilder newBuilder(List subKeys, PropertyMeta owner) {
        MappingContextFactoryBuilder subBuilder = new MappingContextFactoryBuilder(counter, subKeys, keySourceGetter, this, owner);
        children.add(subBuilder);
        return subBuilder;
    }

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


        MappingContextFactory context;

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

        ArrayList> builders = new ArrayList>();
        addAllBuilders(builders);

        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 (builder.effectiveKeys().isEmpty() && parent != null && ! builder.newObjectOnEachRow()) {
             keyDefinition = parent.asChild(builder.currentIndex);
        } else {
            List keys = new ArrayList(builder.effectiveKeys());

            // ignore root parent
            if (parentIndex >0 && parent != null) {
                appendParentKeys(parent, keys);
            }

            keyDefinition = new KeyDefinitionBuilder(keys, builder.keySourceGetter, builder.currentIndex);
        }

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

    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(K k : parent.getKeys()) {
                if (!keys.contains(k)) {
                    keys.add(k);
                }
            }
        }
    }

    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() {
        if (owner instanceof ArrayElementPropertyMeta) {
            ArrayElementPropertyMeta elementPropertyMeta = (ArrayElementPropertyMeta) owner;
            if (elementPropertyMeta.getSetter() instanceof AppendCollectionSetter) {
                return true;
            }
        }
        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 int getRootDetector(List> builders) {
        int rootDetector = -1;
        // calculate rootDetector
        for (int i = 0; i < builders.size(); i++) {
            final MappingContextFactoryBuilder builder = builders.get(i);
            if (!builder.effectiveKeys().isEmpty()) {
                if (builder.currentIndex == 0 || (rootDetector == -1 && builder.isEligibleAsRootKey())) {
                    rootDetector = builder.currentIndex;
                }
            }
        }
        return rootDetector;
    }

    private boolean isEligibleAsRootKey() {
        return isEligibleAsSubstituteKey()
                && (parent == null || parent.isEligibleAsRootKey());
    }

    private boolean isEligibleAsSubstituteKey() {
        return !(owner instanceof ArrayElementPropertyMeta)
                && !(owner instanceof MapElementPropertyMeta);
    }

    // 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;
    }

    private static class Counter {
        int value;
    }

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