diff --git a/build.gradle b/build.gradle index ddde7ee..a08ddcb 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok:1.18.12' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.3' - testImplementation dep(':DAL-java', '0.3.22') + testImplementation dep(':DAL-java', '0.4.2') testImplementation dep(':java-compiler-util', '0.0.3') testImplementation "io.cucumber:cucumber-java:6.10.4" testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.0.0' diff --git a/src/main/java/com/github/leeonky/jfactory/CollectionExpression.java b/src/main/java/com/github/leeonky/jfactory/CollectionExpression.java index 3518629..cc37add 100644 --- a/src/main/java/com/github/leeonky/jfactory/CollectionExpression.java +++ b/src/main/java/com/github/leeonky/jfactory/CollectionExpression.java @@ -3,6 +3,7 @@ import com.github.leeonky.util.CollectionHelper; import com.github.leeonky.util.Property; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -34,11 +35,32 @@ private boolean isMatch(Expression expression, Object value) { @Override @SuppressWarnings("unchecked") public Producer buildProducer(JFactory jFactory, Producer

parent) { - CollectionProducer collectionProducer = cast(parent.childOrDefault(property.getName()), + CollectionProducer producer = cast(parent.childOrDefault(property.getName()), CollectionProducer.class).orElseThrow(IllegalArgumentException::new); - children.forEach((k, v) -> - collectionProducer.setChild(k.toString(), v.buildProducer(jFactory, collectionProducer))); - return collectionProducer; + groupByAdjustedPositiveAndNegativeIndexExpression(producer).forEach((index, expressions) -> + producer.setChild(index.toString(), merge(expressions).buildProducer(jFactory, producer))); + return producer; + } + + private Map>> groupByAdjustedPositiveAndNegativeIndexExpression( + CollectionProducer collectionProducer) { + Map>> result = new LinkedHashMap<>(); + for (Map.Entry> entry : children.entrySet()) { + int index = entry.getKey(); + int addedProducerCount = collectionProducer.fillCollectionWithDefaultValue(index); + if (index < 0) { + index = collectionProducer.childrenCount() + index; + result = adjustIndexByInserted(result, addedProducerCount); + } + result.computeIfAbsent(index, k -> new ArrayList<>()).add(entry.getValue()); + } + return result; + } + + private LinkedHashMap>> adjustIndexByInserted( + Map>> result, int addedProducerCount) { + return result.entrySet().stream().collect(LinkedHashMap::new, + (m, e) -> m.put(e.getKey() + addedProducerCount, e.getValue()), LinkedHashMap::putAll); } @Override @@ -49,13 +71,10 @@ public Expression

mergeTo(Expression

newExpression) { @Override @SuppressWarnings("unchecked") protected Expression

mergeFrom(CollectionExpression origin) { - origin.children.forEach((index, expression) -> - children.put(index, mergeFromOrAssign(index, (Expression) expression))); - return this; - } - - private Expression mergeFromOrAssign(Integer index, Expression expression) { - return children.containsKey(index) ? expression.mergeTo(children.get(index)) : expression; + children.forEach((index, expression) -> + origin.children.put(index, origin.children.containsKey(index) ? + origin.children.get(index).mergeTo((Expression) expression) : (Expression) expression)); + return origin; } @Override diff --git a/src/main/java/com/github/leeonky/jfactory/CollectionProducer.java b/src/main/java/com/github/leeonky/jfactory/CollectionProducer.java index 943ff27..d0ad181 100644 --- a/src/main/java/com/github/leeonky/jfactory/CollectionProducer.java +++ b/src/main/java/com/github/leeonky/jfactory/CollectionProducer.java @@ -50,15 +50,17 @@ public void setChild(String property, Producer producer) { children.set(intIndex, producer); } - private void fillCollectionWithDefaultValue(int index) { + public int fillCollectionWithDefaultValue(int index) { + int changed = 0; if (index >= 0) { - for (int i = children.size(); i <= index; i++) + for (int i = children.size(); i <= index; i++, changed++) children.add(placeholderFactory.apply(i)); } else { int count = max(children.size(), -index) - children.size(); - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++, changed++) children.add(i, placeholderFactory.apply(i)); } + return changed; } @Override @@ -90,4 +92,8 @@ protected void setupAssociation(String association, RootInstance instance objectProducer.setupAssociation(association, instance, cachedChildren)); } + + public int childrenCount() { + return children.size(); + } } diff --git a/src/main/java/com/github/leeonky/jfactory/Expression.java b/src/main/java/com/github/leeonky/jfactory/Expression.java index 8674c36..e0a6c7f 100644 --- a/src/main/java/com/github/leeonky/jfactory/Expression.java +++ b/src/main/java/com/github/leeonky/jfactory/Expression.java @@ -2,6 +2,8 @@ import com.github.leeonky.util.Property; +import java.util.List; + abstract class Expression

{ protected final Property

property; protected boolean intently = false; @@ -22,6 +24,10 @@ public boolean isMatch(P object) { public abstract Producer buildProducer(JFactory jFactory, Producer

parent); + static Expression merge(List> expressions) { + return expressions.stream().reduce(Expression::mergeTo).get(); + } + protected Expression

mergeTo(Expression

newExpression) { return newExpression; } diff --git a/src/main/java/com/github/leeonky/jfactory/KeyValueCollection.java b/src/main/java/com/github/leeonky/jfactory/KeyValueCollection.java index f0838d4..568330f 100644 --- a/src/main/java/com/github/leeonky/jfactory/KeyValueCollection.java +++ b/src/main/java/com/github/leeonky/jfactory/KeyValueCollection.java @@ -40,7 +40,7 @@ Builder apply(Builder builder) { Collection> expressions(BeanClass type, ObjectFactory objectFactory) { return keyValues.values().stream().map(keyValue -> keyValue.createExpression(type, objectFactory)) .collect(Collectors.groupingBy(Expression::getProperty)).values().stream() - .map(expressions -> expressions.stream().reduce(Expression::mergeTo).get()) + .map(Expression::merge) .collect(Collectors.toList()); } diff --git a/src/test/resources/features/3-input-property.feature b/src/test/resources/features/3-input-property.feature index e35c3c2..41198d2 100644 --- a/src/test/resources/features/3-input-property.feature +++ b/src/test/resources/features/3-input-property.feature @@ -472,6 +472,7 @@ Feature: input property """ public class Bean { public String value; + public String value2; } """ And the following bean class: @@ -515,9 +516,72 @@ Feature: input property beans.value[]: [hello world] """ -# TODO -1 -2 increase by insert from left -# TODO mix [0] [-2] -# TODO merge [0] [-1] + Scenario: collection override when use both positive and negative index + When build: + """ + jFactory.type(Beans.class) + .property("beans[0].value", "world") + .property("beans[-2].value", "hello") + .create(); + """ + Then the result should: + """ + beans.value[]: [hello world] + """ + When build: + """ + jFactory.type(Beans.class) + .property("beans[-2].value", "hello") + .property("beans[0].value", "world") + .create(); + """ + Then the result should: + """ + beans: [{ + value= world + }, null] + """ + + Scenario: mixed using positive and negative index + When build: + """ + jFactory.type(Beans.class) + .property("beans[2].value", "world") + .property("beans[-2].value", "hello") + .create(); + """ + Then the result should: + """ + beans: [null {value= hello} {value= world}] + """ + + Scenario: merge same sub object when mixed using positive and negative index + Given the following bean class: + """ + public class BeanRef { + public Bean bean; + } + """ + Given the following bean class: + """ + public class BeanRefs { + public BeanRef[] beanRefs; + } + """ + When build: + """ + jFactory.type(BeanRefs.class) + .property("beanRefs[-1].bean.value", "hello") + .property("beanRefs[0].bean.value2", "world") + .create(); + """ + Then the result should: + """ + beanRefs.bean[]= [{ + value= hello + value2= world + }] + """ Rule: intently create