org.simpleflatmapper.map.mapper.PropertyMappingsBuilder Maven / Gradle / Ivy
Show all versions of sfm-map Show documentation
package org.simpleflatmapper.map.mapper;
import org.simpleflatmapper.map.CaseInsensitiveFieldKeyNamePredicate;
import org.simpleflatmapper.map.FieldKey;
import org.simpleflatmapper.map.MapperBuilderErrorHandler;
import org.simpleflatmapper.map.MapperBuildingException;
import org.simpleflatmapper.map.MapperConfig;
import org.simpleflatmapper.map.impl.ExtendPropertyFinder;
import org.simpleflatmapper.map.property.GetterProperty;
import org.simpleflatmapper.map.property.OptionalProperty;
import org.simpleflatmapper.map.property.SetterProperty;
import org.simpleflatmapper.reflect.TypeAffinity;
import org.simpleflatmapper.reflect.getter.NullGetter;
import org.simpleflatmapper.reflect.meta.ClassMeta;
import org.simpleflatmapper.reflect.meta.PropertyFinder;
import org.simpleflatmapper.reflect.meta.PropertyMeta;
import org.simpleflatmapper.map.PropertyNameMatcherFactory;
import org.simpleflatmapper.reflect.meta.PropertyNameMatcher;
import org.simpleflatmapper.reflect.meta.SelfPropertyMeta;
import org.simpleflatmapper.reflect.setter.NullSetter;
import org.simpleflatmapper.util.BiConsumer;
import org.simpleflatmapper.util.ErrorDoc;
import org.simpleflatmapper.util.ForEachCallBack;
import org.simpleflatmapper.util.Function;
import org.simpleflatmapper.util.Predicate;
import org.simpleflatmapper.util.TypeHelper;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public final class PropertyMappingsBuilder> {
protected final PropertyFinder propertyFinder;
protected final List> properties = new ArrayList>();
protected final PropertyNameMatcherFactory propertyNameMatcherFactory;
private final MapperBuilderErrorHandler _mapperBuilderErrorHandler;
private final ClassMeta classMeta;
private final PropertyMappingsBuilderProbe propertyMappingsBuilderProbe;
private final PropertyPredicateFactory isValidPropertyMeta;
protected boolean modifiable = true;
private List> customProperties;
private PropertyMappingsBuilder(final ClassMeta classMeta,
final PropertyNameMatcherFactory propertyNameMatcherFactory,
final MapperBuilderErrorHandler mapperBuilderErrorHandler,
final PropertyPredicateFactory isValidPropertyMetaFactory,
final PropertyFinder propertyFinder,
List> customProperties,
PropertyMappingsBuilderProbe propertyMappingsBuilderProbe) throws MapperBuildingException {
this.isValidPropertyMeta = isValidPropertyMetaFactory;
this._mapperBuilderErrorHandler = mapperBuilderErrorHandler;
this.customProperties = customProperties;
this.propertyMappingsBuilderProbe = propertyMappingsBuilderProbe;
this.propertyFinder = propertyFinder != null ? propertyFinder : classMeta.newPropertyFinder();
this.propertyNameMatcherFactory = propertyNameMatcherFactory;
this.classMeta = classMeta;
}
public PropertyMapping addProperty(final K key, final ColumnDefinition columnDefinition) {
return
_addProperty(key, columnDefinition, _mapperBuilderErrorHandler);
}
public PropertyMapping addPropertyIfPresent(final K key, final ColumnDefinition columnDefinition) {
return _addProperty(key, columnDefinition, MapperBuilderErrorHandler.NULL);
}
@SuppressWarnings("unchecked")
private PropertyMapping _addProperty(final K key, final ColumnDefinition columnDefinition, MapperBuilderErrorHandler mapperBuilderErrorHandler) {
if (!modifiable) throw new IllegalStateException("Builder not modifiable");
if (columnDefinition.ignore()) {
propertyMappingsBuilderProbe.ignore(key, columnDefinition);
properties.add(null);
return null;
}
PropertyFinder effectivePropertyFinder = wrapPropertyFinder(this.propertyFinder);
PropertyNameMatcher propertyNameMatcher = propertyNameMatcherFactory.newInstance(key);
List accessorNotFounds = new ArrayList();
Predicate> propertyFilter = isValidPropertyMeta.predicate(key, columnDefinition.properties(), accessorNotFounds);
final PropertyMeta prop =
(PropertyMeta) effectivePropertyFinder
.findProperty(propertyNameMatcher, columnDefinition.properties(), toTypeAffinity(key),
propertyMappingsBuilderProbe.propertyFinderProbe(propertyNameMatcher),
propertyFilter);
if (prop == null) {
if (!columnDefinition.has(OptionalProperty.class)) {
AccessorNotFound accessorNotFound = selectAccessotNotFound(accessorNotFounds);
if (accessorNotFound == null) {
mapperBuilderErrorHandler.propertyNotFound(classMeta.getType(), key.getName());
} else {
mapperBuilderErrorHandler.accessorNotFound(accessorNotFounds.iterator().next().toString());
}
}
properties.add(null);
return null;
} else {
PropertyMapping propertyMapping = addProperty(key, columnDefinition, prop);
propertyMappingsBuilderProbe.map(key, columnDefinition, prop);
handleSelfPropertyMetaInvalidation(mapperBuilderErrorHandler);
return propertyMapping;
}
}
private AccessorNotFound selectAccessotNotFound(List accessorNotFounds) {
for(AccessorNotFound accessorNotFound : accessorNotFounds) {
if (!accessorNotFound.propertyMeta.isSelf()) return accessorNotFound;
}
return null;
}
private TypeAffinity toTypeAffinity(K key) {
if (key instanceof TypeAffinity) {
return (TypeAffinity) key;
}
return null;
}
private PropertyFinder wrapPropertyFinder(PropertyFinder propertyFinder) {
if (!customProperties.isEmpty()) {
return new ExtendPropertyFinder(propertyFinder, customProperties, new Function() {
@Override
public PropertyFinder.PropertyFinderTransformer apply(PropertyFinder.PropertyFinderTransformer propertyFinderTransformer) {
return new ExtendPropertyFinder.ExtendPropertyFinderTransformer(propertyFinderTransformer, customProperties);
}
});
}
return propertyFinder;
}
private void handleSelfPropertyMetaInvalidation(MapperBuilderErrorHandler errorHandler) {
List invalidateKeys = new ArrayList();
for(ListIterator> iterator = properties.listIterator(); iterator.hasNext();) {
PropertyMapping propertyMapping = iterator.next();
if (propertyMapping != null && !propertyMapping.getPropertyMeta().isValid()) {
iterator.set(null);
if (!propertyMapping.getColumnDefinition().has(OptionalProperty.class)) {
invalidateKeys.add(propertyMapping.getColumnKey());
}
}
}
for(K k : invalidateKeys) {
errorHandler.propertyNotFound(classMeta.getType(), k.getName());
}
}
public PropertyMapping addProperty(final K key, final ColumnDefinition columnDefinition, final PropertyMeta prop) {
if (columnDefinition.hasCustomSourceFrom(prop.getOwnerType())) {
Type type = prop.getPropertyType();
if (!checkTypeCompatibility(key, columnDefinition.getCustomSourceReturnTypeFrom(prop.getOwnerType()), type)) {
properties.add(null);
return null;
}
}
Object[] definedProperties = prop.getDefinedProperties();
ColumnDefinition mergeColumnDefinition = definedProperties != null ? columnDefinition.add(definedProperties) : columnDefinition;
PropertyMapping propertyMapping = new PropertyMapping(prop, key, mergeColumnDefinition);
properties.add(propertyMapping);
propertyFinder.manualMatch(prop);
return propertyMapping;
}
private boolean checkTypeCompatibility(K key, Type customSourceReturnType, Type propertyMetaType) {
if (customSourceReturnType == null) {
// cannot determine type
return true;
} else if(!areCompatible(propertyMetaType, customSourceReturnType)) {
_mapperBuilderErrorHandler.customFieldError(key, "Incompatible customReader on '" + key.getName()+ "' type " + customSourceReturnType + " expected " + propertyMetaType );
return false;
}
return true;
}
private boolean areCompatible(Type propertyMetaType, Type customSourceReturnType) {
Class> propertyMetaClass = TypeHelper.toBoxedClass(TypeHelper.toClass(propertyMetaType));
Class> customSourceReturnClass = TypeHelper.toBoxedClass(TypeHelper.toClass(customSourceReturnType));
return propertyMetaClass.isAssignableFrom(customSourceReturnClass);
}
public List getKeys() {
modifiable = false;
List keys = new ArrayList(properties.size());
for (PropertyMapping propMapping : properties) {
if (propMapping != null) {
keys.add(propMapping.getColumnKey());
} else {
keys.add(null);
}
}
return keys;
}
public void forEachConstructorProperties(ForEachCallBack> handler) {
modifiable = false;
for (PropertyMapping property : properties) {
if (property != null) {
PropertyMeta propertyMeta = property.getPropertyMeta();
if (propertyMeta != null && propertyMeta.isConstructorProperty() && ! propertyMeta.isSubProperty()) {
handler.handle(property);
}
}
}
}
public List> currentProperties() {
return new ArrayList>(properties);
}
public >> H forEachProperties(H handler) {
return forEachProperties(handler, -1 );
}
public >> F forEachProperties(F handler, int start) {
return forEachProperties(handler, start, -1 );
}
public >> F forEachProperties(F handler, int start, int end) {
modifiable = false;
for (PropertyMapping prop : properties) {
if (prop != null
&& (prop.getColumnKey().getIndex() >= start || start == -1)
&& (prop.getColumnKey().getIndex() < end || end == -1)) {
handler.handle(prop);
}
}
return handler;
}
public PropertyFinder getPropertyFinder() {
modifiable = false;
return propertyFinder;
}
public int size() {
return properties.size();
}
public boolean isSelfProperty() {
return (properties.size() == 1 && properties.get(0) != null && properties.get(0).getPropertyMeta() instanceof SelfPropertyMeta);
}
public int maxIndex() {
int i = -1;
for (PropertyMapping prop : properties) {
if (prop != null) {
i = Math.max(i, prop.getColumnKey().getIndex());
}
}
return i;
}
public boolean hasKey(Predicate super K> predicate) {
for (PropertyMapping propMapping : properties) {
if (propMapping != null && predicate.test(propMapping.getColumnKey())) {
return true;
}
}
return false;
}
public ClassMeta getClassMeta() {
return classMeta;
}
public static , S> PropertyMappingsBuilder of(
ClassMeta classMeta,
MapperConfig mapperConfig,
PropertyPredicateFactory propertyPredicateFactory) {
return of(classMeta, mapperConfig, propertyPredicateFactory, null);
}
public static , S> PropertyMappingsBuilder of(
final ClassMeta classMeta,
final MapperConfig mapperConfig,
final PropertyPredicateFactory propertyPredicateFactory,
final PropertyFinder propertyFinder) {
final List> customProperties = new ArrayList>();
final Predicate> propertyPredicate = propertyPredicateFactory.predicate(null, null, new ArrayList());
// setter
mapperConfig.columnDefinitions().forEach(SetterProperty.class, new BiConsumer, SetterProperty>() {
@Override
public void accept(Predicate super K> predicate, SetterProperty setterProperty) {
if (predicate instanceof CaseInsensitiveFieldKeyNamePredicate) {
CaseInsensitiveFieldKeyNamePredicate p = (CaseInsensitiveFieldKeyNamePredicate) predicate;
ExtendPropertyFinder.CustomProperty cp = new ExtendPropertyFinder.CustomProperty(setterProperty.getTargetType(), classMeta.getReflectionService(), p.getName(), setterProperty.getPropertyType(), setterProperty.getSetter(), NullGetter.getter());
if (propertyPredicate.test(cp)) {
customProperties.add(cp);
}
}
}
});
// getter
mapperConfig.columnDefinitions().forEach(GetterProperty.class, new BiConsumer, GetterProperty>() {
@Override
public void accept(Predicate super K> predicate, GetterProperty getterProperty) {
if (predicate instanceof CaseInsensitiveFieldKeyNamePredicate) {
CaseInsensitiveFieldKeyNamePredicate p = (CaseInsensitiveFieldKeyNamePredicate) predicate;
ExtendPropertyFinder.CustomProperty cp = new ExtendPropertyFinder.CustomProperty(getterProperty.getSourceType(), classMeta.getReflectionService(), p.getName(), getterProperty.getReturnType(), NullSetter.NULL_SETTER, getterProperty.getGetter());
if (propertyPredicate.test(cp)) {
customProperties.add(cp);
}
}
}
});
return
new PropertyMappingsBuilder(
classMeta,
mapperConfig.propertyNameMatcherFactory(),
mapperConfig.mapperBuilderErrorHandler(),
propertyPredicateFactory,
propertyFinder,
customProperties,
DefaultPropertyMappingsBuilderProbe.INSTANCE);
}
public interface PropertyMappingsBuilderProbe {
void ignore(FieldKey key, ColumnDefinition columnDefinition);
void map(FieldKey key, ColumnDefinition columnDefinition, PropertyMeta, ?> prop);
PropertyFinder.PropertyFinderProbe propertyFinderProbe(PropertyNameMatcher matcher);
}
private static class DefaultPropertyMappingsBuilderProbe implements PropertyMappingsBuilderProbe {
static final DefaultPropertyMappingsBuilderProbe INSTANCE = new DefaultPropertyMappingsBuilderProbe();
private static final boolean DEBUG = Boolean.getBoolean("org.simpleflatmapper.probe.propertyMappingsBuilder");
@Override
public void ignore(FieldKey key, ColumnDefinition columnDefinition) {
if (DEBUG) {
System.out.println("PropertyMappingsBuilder - ignore " + key);
}
}
@Override
public void map(FieldKey key, ColumnDefinition columnDefinition, PropertyMeta, ?> prop) {
if (DEBUG) {
String path = prop.getPath();
System.out.println("PropertyMappingsBuilder - map " + key.getName() + " to " + path);
}
}
@Override
public PropertyFinder.PropertyFinderProbe propertyFinderProbe(PropertyNameMatcher matcher) {
return new PropertyFinder.DefaultPropertyFinderProbe(matcher);
}
}
public interface PropertyPredicateFactory> {
Predicate> predicate(K key, Object[] properties, List accessorNotFounds);
}
public static final class AccessorNotFound {
public final FieldKey> key;
public final String path;
public final Type to;
public final ErrorDoc errorDoc;
public final PropertyMeta, ?> propertyMeta;
public AccessorNotFound(FieldKey> key, String path, Type to, ErrorDoc errorDoc, PropertyMeta, ?> propertyMeta) {
this.key = key;
this.path = path;
this.to = to;
this.errorDoc = errorDoc;
this.propertyMeta = propertyMeta;
}
public String toString() {
String accessor = "NA";
switch (errorDoc) {
case CSFM_GETTER_NOT_FOUND: accessor = "Getter"; break;
case PROPERTY_NOT_FOUND: accessor = "property"; break;
case CTFM_SETTER_NOT_FOUND: accessor = "Setter"; break;
case CTFM_GETTER_NOT_FOUND: accessor = "Getter"; break;
}
return "Could not find " + accessor + " for " + key + " returning type "
+ to
+ " path " + path
+ ". See " + errorDoc.toUrl();
}
}
}