org.simpleflatmapper.map.context.MappingContextFactoryBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sfm-map Show documentation
Show all versions of sfm-map Show documentation
Java library to map flat record - ResultSet, csv - to java object with minimum configuration and low footprint.
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 +
'}';
}
}