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<S, K> { private final Counter counter; private final int currentIndex; private final MappingContextFactoryBuilder<S, K> parent; private final List<K> keys; private final KeySourceGetter<K, S> keySourceGetter; private final List<MappingContextFactoryBuilder<S, K>> children = new ArrayList<MappingContextFactoryBuilder<S, K>>(); private final List<Supplier<?>> suppliers = new ArrayList<Supplier<?>>(); private final PropertyMeta<?, ?> owner; public MappingContextFactoryBuilder(KeySourceGetter<K, S> keySourceGetter) { this(new Counter(), new ArrayList<K>(), keySourceGetter, null, null); } protected MappingContextFactoryBuilder(Counter counter, List<K> keys, KeySourceGetter<K, S> keySourceGetter, MappingContextFactoryBuilder<S, K> 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<S> nullChecker() { return new NullChecker<S, K>(keys, keySourceGetter); } public MappingContextFactoryBuilder<S, K> newBuilder(List<K> subKeys, PropertyMeta<?, ?> owner) { MappingContextFactoryBuilder<S, K> subBuilder = new MappingContextFactoryBuilder<S, K>(counter, subKeys, keySourceGetter, this, owner); children.add(subBuilder); return subBuilder; } @SuppressWarnings("unchecked") public MappingContextFactory<S> newFactory() { if (parent != null) { throw new IllegalStateException(); } MappingContextFactory<S> context; if (suppliers.isEmpty()) { context = MappingContext.EMPTY_FACTORY; } else { context = new ValuedMappingContextFactory<S>(suppliers); } ArrayList<MappingContextFactoryBuilder<S, K>> builders = new ArrayList<MappingContextFactoryBuilder<S, K>>(); addAllBuilders(builders); if (hasKeys(builders)) { KeyDefinitionBuilder<S, K>[] keyDefinitionsBuilder = new KeyDefinitionBuilder[builders.get(builders.size() - 1).currentIndex + 1]; for (int i = 0; i < builders.size(); i++) { MappingContextFactoryBuilder<S, K> builder = builders.get(i); populateKey(keyDefinitionsBuilder, builders, builder); } KeyDefinition<S, K>[] keyDefinitions = KeyDefinitionBuilder.<S, K>toKeyDefinitions(keyDefinitionsBuilder); KeyDefinition<S, K> rootKeyDefinition = keyDefinitions[0]; context = new BreakDetectorMappingContextFactory<S>(rootKeyDefinition, keyDefinitions, context); } return context; } private KeyDefinitionBuilder<S, K> populateKey(KeyDefinitionBuilder<S, K>[] keyDefinitions, ArrayList<MappingContextFactoryBuilder<S, K>> builders, MappingContextFactoryBuilder<S, K> builder) { if (keyDefinitions[builder.currentIndex] != null) { return keyDefinitions[builder.currentIndex]; } int parentIndex = builder.getNonEmptyParentIndex(); KeyDefinitionBuilder<S, K> 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<S, K> 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<S, K> 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<K> keys = new ArrayList<K>(builder.effectiveKeys()); // ignore root parent if (parentIndex >0 && parent != null) { appendParentKeys(parent, keys); } keyDefinition = new KeyDefinitionBuilder<S, K>(keys, builder.keySourceGetter, builder.currentIndex); } keyDefinitions[builder.currentIndex] = keyDefinition; return keyDefinition; } private void appendParentKeys(KeyDefinitionBuilder<S, K> parent, List<K> 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<K> effectiveKeys() { if (!keys.isEmpty()) { return keys; } List<K> keys = new ArrayList<K>(); for(MappingContextFactoryBuilder<S, K> 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 <S, K> boolean hasKeys(ArrayList<MappingContextFactoryBuilder<S, K>> builders) { for(int i = 0; i < builders.size(); i++) { if (!builders.get(i).hasNoKeys()) return true; } return false; } private int getRootDetector(List<MappingContextFactoryBuilder<S, K>> builders) { int rootDetector = -1; // calculate rootDetector for (int i = 0; i < builders.size(); i++) { final MappingContextFactoryBuilder<S, K> 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<MappingContextFactoryBuilder<S, K>> builders) { builders.add(this); for(MappingContextFactoryBuilder<S, K> child : children) { child.addAllBuilders(builders); } } public boolean hasNoKeys() { return effectiveKeys().isEmpty(); } public boolean hasNoDependentKeys() { if (!hasNoKeys()) { return false; } for(MappingContextFactoryBuilder<S, K> 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 + '}'; } }