package org.simpleflatmapper.reflect.meta; import org.simpleflatmapper.reflect.InstantiatorDefinition; import org.simpleflatmapper.reflect.Parameter; import org.simpleflatmapper.util.BooleanProvider; import org.simpleflatmapper.util.Function; import org.simpleflatmapper.util.Predicate; import java.lang.reflect.Type; import java.util.*; final class ObjectPropertyFinder<T> extends PropertyFinder<T> { enum State { NONE, SELF, PROPERTIES } private final List<InstantiatorDefinition> eligibleInstantiatorDefinitions; private final ObjectClassMeta<T> classMeta; private final Map<PropertyMeta<?, ?>, PropertyFinder<?>> subPropertyFinders = new HashMap<PropertyMeta<?, ?>, PropertyFinder<?>>(); private State state = State.NONE; private String selfName; ObjectPropertyFinder(ObjectClassMeta<T> classMeta, Predicate<PropertyMeta<?, ?>> propertyFilter) { super(propertyFilter); this.classMeta = classMeta; this.eligibleInstantiatorDefinitions = classMeta.getInstantiatorDefinitions() != null ? new ArrayList<InstantiatorDefinition>(classMeta.getInstantiatorDefinitions()) : null; } @SuppressWarnings("unchecked") @Override public void lookForProperties(final PropertyNameMatcher propertyNameMatcher, FoundProperty<T> matchingProperties, PropertyMatchingScore score, boolean allowSelfReference, PropertyFinderTransformer propertyFinderTransform) { lookForConstructor(propertyNameMatcher, matchingProperties, score, propertyFinderTransform); lookForProperty(propertyNameMatcher, matchingProperties, score, propertyFinderTransform); final String propName = propertyNameMatcher.toString(); if (allowSelfReference && (state == State.NONE || (state == State.SELF && propName.equals(selfName)))) { matchingProperties.found(new SelfPropertyMeta(classMeta.getReflectionService(), classMeta.getType(), new BooleanProvider() { @Override public boolean getBoolean() { return state != State.PROPERTIES; } }), selfPropertySelectionCallback(propName), PropertyMatchingScore.MINIMUM); } } private void lookForConstructor(final PropertyNameMatcher propertyNameMatcher, final FoundProperty<T> matchingProperties, final PropertyMatchingScore score, final PropertyFinderTransformer propertyFinderTransformer) { if (classMeta.getConstructorProperties() != null) { for (final ConstructorPropertyMeta<T, ?> prop : classMeta.getConstructorProperties()) { final String columnName = getColumnName(prop); if (propertyNameMatcher.matches(columnName) && hasConstructorMatching(prop.getParameter())) { matchingProperties.found(prop, propertiesRemoveNonMatchingCallBack(prop), score); } PropertyNameMatcher subPropMatcher = propertyNameMatcher.partialMatch(columnName); if (subPropMatcher != null && hasConstructorMatching(prop.getParameter())) { lookForSubProperty(subPropMatcher, prop, new FoundProperty() { @Override public void found(final PropertyMeta propertyMeta, final Runnable selectionCallback, final PropertyMatchingScore score) { matchingProperties.found( new SubPropertyMeta(classMeta.getReflectionService(), prop, propertyMeta), propertiesDelegateAndRemoveNonMatchingCallBack(selectionCallback, prop), score.shift()); } }, score.shift(), propertyFinderTransformer); } } } } private void lookForProperty(final PropertyNameMatcher propertyNameMatcher, final FoundProperty<T> matchingProperties, final PropertyMatchingScore score, final PropertyFinderTransformer propertyFinderTransformer) { for (final PropertyMeta<T, ?> prop : classMeta.getProperties()) { final String columnName = getColumnName(prop); if (propertyNameMatcher.matches(columnName)) { matchingProperties.found(prop, propertiesCallBack(), score.decrease(1)); } final PropertyNameMatcher subPropMatcher = propertyNameMatcher.partialMatch(columnName); if (subPropMatcher != null) { lookForSubProperty(subPropMatcher, prop, new FoundProperty() { @Override public void found(final PropertyMeta propertyMeta, final Runnable selectionCallback, final PropertyMatchingScore score) { matchingProperties.found(new SubPropertyMeta(classMeta.getReflectionService(), prop, propertyMeta), propertiesDelegateCallBack(selectionCallback), score); } }, score.shift().decrease( -1), propertyFinderTransformer); } } } private void lookForSubProperty( final PropertyNameMatcher propertyNameMatcher, final PropertyMeta<T, ?> prop, final FoundProperty foundProperty, final PropertyMatchingScore score, final PropertyFinderTransformer propertyFinderTransformer) { PropertyFinder<?> subPropertyFinder = subPropertyFinders.get(prop); final PropertyFinder<?> newPropertyFinder; if (subPropertyFinder == null) { subPropertyFinder = prop.getPropertyClassMeta().newPropertyFinder(propertyFilter); newPropertyFinder = subPropertyFinder; } else { newPropertyFinder = null; } propertyFinderTransformer .apply(subPropertyFinder) .lookForProperties(propertyNameMatcher, new FoundProperty() { @Override public void found(final PropertyMeta propertyMeta, final Runnable selectionCallback, final PropertyMatchingScore score) { if (newPropertyFinder != null) { subPropertyFinders.put(prop, newPropertyFinder); } foundProperty.found(propertyMeta, selectionCallback, score); } }, score, false, propertyFinderTransformer); } private String getColumnName(PropertyMeta<T, ?> prop) { return this.classMeta.getAlias(prop.getName()); } private void removeNonMatching(Parameter param) { ListIterator<InstantiatorDefinition> li = eligibleInstantiatorDefinitions.listIterator(); while(li.hasNext()){ InstantiatorDefinition cd = li.next(); if (!cd.hasParam(param)) { li.remove(); } } } private boolean hasConstructorMatching(Parameter param) { for(InstantiatorDefinition cd : eligibleInstantiatorDefinitions) { if (cd.hasParam(param)) { return true; } } return false; } private Runnable compose(final Runnable r1, final Runnable r2) { return new Runnable() { @Override public void run() { r1.run(); r2.run(); } }; } private Runnable propertiesDelegateAndRemoveNonMatchingCallBack(final Runnable selectionCallback, final ConstructorPropertyMeta<T, ?> prop) { return compose(selectionCallback, propertiesRemoveNonMatchingCallBack(prop)); } private Runnable propertiesRemoveNonMatchingCallBack(final ConstructorPropertyMeta<T, ?> prop) { return compose(removeNonMatchingCallBack(prop), propertiesCallBack()); } private Runnable removeNonMatchingCallBack(final ConstructorPropertyMeta<T, ?> prop) { return new Runnable() { @Override public void run() { removeNonMatching(prop.getParameter()); } }; } private Runnable propertiesDelegateCallBack(final Runnable selectionCallback) { return compose(selectionCallback, propertiesCallBack()); } private Runnable propertiesCallBack() { return new Runnable() { @Override public void run() { state = State.PROPERTIES; } }; } private Runnable selfPropertySelectionCallback(final String propName) { return new Runnable() { @Override public void run() { state = State.SELF; selfName = propName; } }; } @Override public List<InstantiatorDefinition> getEligibleInstantiatorDefinitions() { return eligibleInstantiatorDefinitions; } @Override public PropertyFinder<?> getSubPropertyFinder(PropertyMeta<?, ?> owner) { return subPropertyFinders.get(owner); } @Override public Type getOwnerType() { return classMeta.getType(); } }