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.SetterProperty; 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.SelfPropertyMeta; import org.simpleflatmapper.reflect.setter.NullSetter; import org.simpleflatmapper.util.BiConsumer; import org.simpleflatmapper.util.Consumer; import org.simpleflatmapper.util.ForEachCallBack; import org.simpleflatmapper.util.Function; import org.simpleflatmapper.util.NullConsumer; 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<T, K extends FieldKey<K>, D extends ColumnDefinition<K, D>> { protected final PropertyFinder<T> propertyFinder; protected final List<PropertyMapping<T, ?, K, D>> properties = new ArrayList<PropertyMapping<T, ?, K, D>>(); protected final PropertyNameMatcherFactory propertyNameMatcherFactory; private final MapperBuilderErrorHandler mapperBuilderErrorHandler; private final ClassMeta<T> classMeta; protected boolean modifiable = true; private Consumer<K> propertyNotFoundConsumer; private List<ExtendPropertyFinder.CustomProperty<?, ?>> customProperties; private PropertyMappingsBuilder(final ClassMeta<T> classMeta, final PropertyNameMatcherFactory propertyNameMatcherFactory, final MapperBuilderErrorHandler mapperBuilderErrorHandler, final Predicate<PropertyMeta<?, ?>> isValidPropertyMeta, final PropertyFinder<T> propertyFinder, List<ExtendPropertyFinder.CustomProperty<?, ?>> customProperties) throws MapperBuildingException { this.mapperBuilderErrorHandler = mapperBuilderErrorHandler; this.customProperties = customProperties; this.propertyFinder = propertyFinder != null ? propertyFinder : classMeta.newPropertyFinder(isValidPropertyMeta); this.propertyNameMatcherFactory = propertyNameMatcherFactory; this.classMeta = classMeta; this.propertyNotFoundConsumer = new Consumer<K>() { @Override public void accept(K k) { mapperBuilderErrorHandler.propertyNotFound(classMeta.getType(), k.getName()); } }; } public <P> PropertyMapping<T, P, K, D> addProperty(final K key, final D columnDefinition) { return _addProperty(key, columnDefinition, propertyNotFoundConsumer); } public <P> PropertyMapping<T, P, K, D> addPropertyIfPresent(final K key, final D columnDefinition) { return _addProperty(key, columnDefinition, NullConsumer.INSTANCE); } @SuppressWarnings("unchecked") private <P> PropertyMapping<T, P, K, D> _addProperty(final K key, final D columnDefinition, Consumer<? super K> propertyNotFound) { if (!modifiable) throw new IllegalStateException("Builder not modifiable"); if (columnDefinition.ignore()) { properties.add(null); return null; } PropertyFinder<T> effectivePropertyFinder = wrapPropertyFinder(this.propertyFinder); final PropertyMeta<T, P> prop = (PropertyMeta<T, P>) effectivePropertyFinder .findProperty(propertyNameMatcherFactory.newInstance(key)); if (prop == null) { propertyNotFound.accept(key); properties.add(null); return null; } else { PropertyMapping<T, P, K, D> propertyMapping = addProperty(key, columnDefinition, prop); handleSelfPropertyMetaInvalidation(propertyNotFound); return propertyMapping; } } private <T> PropertyFinder<T> wrapPropertyFinder(PropertyFinder<T> propertyFinder) { if (!customProperties.isEmpty()) { return new ExtendPropertyFinder<T>(propertyFinder, customProperties, new Function<PropertyFinder.PropertyFinderTransformer, PropertyFinder.PropertyFinderTransformer>() { @Override public PropertyFinder.PropertyFinderTransformer apply(PropertyFinder.PropertyFinderTransformer propertyFinderTransformer) { return new ExtendPropertyFinder.ExtendPropertyFinderTransformer(propertyFinderTransformer, customProperties); } }); } return propertyFinder; } private void handleSelfPropertyMetaInvalidation(Consumer<? super K> propertyNotFound) { List<K> invalidateKeys = new ArrayList<K>(); for(ListIterator<PropertyMapping<T, ?, K, D>> iterator = properties.listIterator(); iterator.hasNext();) { PropertyMapping<T, ?, K, D> propertyMapping = iterator.next(); if (propertyMapping != null && !propertyMapping.getPropertyMeta().isValid()) { iterator.set(null); invalidateKeys.add(propertyMapping.getColumnKey()); } } for(K k : invalidateKeys) { propertyNotFound.accept(k); } } public <P> PropertyMapping<T, P, K, D> addProperty(final K key, final D columnDefinition, final PropertyMeta<T, P> 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(); D mergeColumnDefinition = definedProperties != null ? columnDefinition.add(definedProperties) : columnDefinition; PropertyMapping<T, P, K, D> propertyMapping = new PropertyMapping<T, P, K, D>(prop, key, mergeColumnDefinition); properties.add(propertyMapping); 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<K> getKeys() { modifiable = false; List<K> keys = new ArrayList<K>(properties.size()); for (PropertyMapping<T, ?, K, D> propMapping : properties) { if (propMapping != null) { keys.add(propMapping.getColumnKey()); } else { keys.add(null); } } return keys; } public void forEachConstructorProperties(ForEachCallBack<PropertyMapping<T, ?, K, D>> handler) { modifiable = false; for (PropertyMapping<T, ?, K, D> property : properties) { if (property != null) { PropertyMeta<T, ?> propertyMeta = property.getPropertyMeta(); if (propertyMeta != null && propertyMeta.isConstructorProperty() && ! propertyMeta.isSubProperty()) { handler.handle(property); } } } } public List<PropertyMapping<T, ?, K, D>> currentProperties() { return new ArrayList<PropertyMapping<T, ?, K, D>>(properties); } public <H extends ForEachCallBack<PropertyMapping<T, ?, K, D>>> H forEachProperties(H handler) { return forEachProperties(handler, -1 ); } public <F extends ForEachCallBack<PropertyMapping<T, ?, K, D>>> F forEachProperties(F handler, int start) { return forEachProperties(handler, start, -1 ); } public <F extends ForEachCallBack<PropertyMapping<T, ?, K, D>>> F forEachProperties(F handler, int start, int end) { modifiable = false; for (PropertyMapping<T, ?, K, D> prop : properties) { if (prop != null && (prop.getColumnKey().getIndex() >= start || start == -1) && (prop.getColumnKey().getIndex() < end || end == -1)) { handler.handle(prop); } } return handler; } public PropertyFinder<T> 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<T, ?, K, D> prop : properties) { if (prop != null) { i = Math.max(i, prop.getColumnKey().getIndex()); } } return i; } public boolean hasKey(Predicate<? super K> predicate) { for (PropertyMapping<T, ?, K, D> propMapping : properties) { if (propMapping != null && predicate.test(propMapping.getColumnKey())) { return true; } } return false; } public ClassMeta<T> getClassMeta() { return classMeta; } public static <T, K extends FieldKey<K>, D extends ColumnDefinition<K, D>> PropertyMappingsBuilder<T, K, D> of( ClassMeta<T> classMeta, MapperConfig<K, D> mapperConfig, Predicate<PropertyMeta<?, ?>> propertyPredicate) { return of(classMeta, mapperConfig, propertyPredicate, null); } public static <T, K extends FieldKey<K>, D extends ColumnDefinition<K, D>> PropertyMappingsBuilder<T, K, D> of( final ClassMeta<T> classMeta, final MapperConfig<K, D> mapperConfig, final Predicate<PropertyMeta<?, ?>> propertyPredicate, final PropertyFinder<T> propertyFinder) { final List<ExtendPropertyFinder.CustomProperty<?, ?>> customProperties = new ArrayList<ExtendPropertyFinder.CustomProperty<?, ?>>(); // setter mapperConfig.columnDefinitions().forEach(SetterProperty.class, new BiConsumer<Predicate<? super K>, 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<Predicate<? super K>, 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<T, K, D>( classMeta, mapperConfig.propertyNameMatcherFactory(), mapperConfig.mapperBuilderErrorHandler(), propertyPredicate, propertyFinder, customProperties); } }