From e2813326560bb98e668f0cd6594a999d9712db85 Mon Sep 17 00:00:00 2001 From: Samppa Saarela Date: Thu, 15 Sep 2011 15:52:06 +0300 Subject: [PATCH] Another alternative for group by ResultTransformer --- .../com/mysema/query/support/GroupBy2.java | 256 ++++++++++++++++++ .../mysema/query/support/GroupBy2Test.java | 154 +++++++++++ .../com/mysema/query/support/GroupByTest.java | 7 +- 3 files changed, 413 insertions(+), 4 deletions(-) create mode 100644 querydsl-core/src/test/java/com/mysema/query/support/GroupBy2.java create mode 100644 querydsl-core/src/test/java/com/mysema/query/support/GroupBy2Test.java diff --git a/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2.java b/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2.java new file mode 100644 index 000000000..0861f5891 --- /dev/null +++ b/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2010 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.support; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; + +import com.mysema.commons.lang.CloseableIterator; +import com.mysema.query.Projectable; +import com.mysema.query.ResultTransformer; +import com.mysema.query.Tuple; +import com.mysema.query.types.Expression; +import com.mysema.query.types.Visitor; + +/** + * Groups results by the first expression. + *
    + *
  1. Order of groups by position of the first row of a group + *
  2. Rows belonging to a group may appear in any order + *
  3. Group of null is handled correctly + *
+ * + * @author sasa + */ +//@SuppressWarnings("unchecked") +public class GroupBy2 implements ResultTransformer> { + + private final Expression[] expressions; + + private static interface GroupFactory { + + public void add(Object o); + + public R get(); + + } + + /** + * NOTE: This expression only applies to GroupBy.transform + * + * @param + * @param + */ + public static abstract class GroupAsExpression implements Expression { + + private static final long serialVersionUID = -8164758792405567077L; + + private final Expression expr; + + public GroupAsExpression(Expression expr) { + this.expr = expr; + } + + @Override + public S accept(Visitor v, C context) { + throw new UnsupportedOperationException(); + } + + @Override + public Class getType() { + throw new UnsupportedOperationException(); + } + + public int hashCode() { + return expr.hashCode(); + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof GroupAsExpression) { + GroupAsExpression other = (GroupAsExpression) o; + return this.expr.equals(other.expr); + } else { + return false; + } + } + + public abstract GroupFactory createGroupFactory(); + + } + + public static GroupAsExpression> set(Expression expr) { + return new GroupAsExpression>(expr) { + + private static final long serialVersionUID = -2507144565843468159L; + + @Override + public GroupFactory> createGroupFactory() { + return new GroupFactory>() { + + private final Set set = new HashSet(); + + @Override + public void add(Object o) { + set.add((T) o); + } + + @Override + public Set get() { + return set; + } + + }; + } + + }; + } + + public static GroupAsExpression> list(Expression expr) { + return new GroupAsExpression>(expr) { + + private static final long serialVersionUID = -6941324182786049824L; + + @Override + public GroupFactory> createGroupFactory() { + return new GroupFactory>() { + + private final List list = new ArrayList(); + + @Override + public void add(Object o) { + list.add((T) o); + } + + @Override + public List get() { + return list; + } + + }; + } + + }; + } + + private static class ValueGroupFactory implements GroupFactory { + private T val; + + private boolean first = true; + + @Override + public void add(Object o) { + if (first) { + val = (T) o; + first = false; + } + } + + @Override + public T get() { + return val; + } + + } + + public GroupBy2(Expression groupBy, Expression... args) { + expressions = new Expression[args.length + 1]; + expressions[0] = groupBy; + System.arraycopy(args, 0, expressions, 1, args.length); + } + + @Override + public Collection transform(Projectable projectable) { + final LinkedHashMap groups = new LinkedHashMap(); + + CloseableIterator iter = projectable.iterate(unwrap(expressions)); + try { + while (iter.hasNext()) { + Object[] row = iter.next(); + Object groupBy = row[0]; + // groups.values() should return Collection instead of Collection + GroupTuple group = (GroupTuple) groups.get(groupBy); + if (group == null) { + group = new GroupTuple(); + groups.put(groupBy, group); + } + group.add(row); + } + } finally { + iter.close(); + } + return groups.values(); + } + + private static Expression[] unwrap(Expression... expressions) { + Expression[] unwrapped = new Expression[expressions.length]; + for (int i=0; i < expressions.length; i++) { + if (expressions[i] instanceof GroupAsExpression) { + unwrapped[i] = ((GroupAsExpression) expressions[i]).expr; + } else { + unwrapped[i] = expressions[i]; + } + } + return unwrapped; + } + + private int indexOf(Expression expr) { + for (int i=0; i < expressions.length; i++) { + if (expressions[i].equals(expr)) { + return i; + } + } + return -1; + } + + private class GroupTuple implements Tuple { + + private final GroupFactory[] groupFactories; + + public GroupTuple() { + groupFactories = new GroupFactory[expressions.length]; + for (int i=0; i < expressions.length; i++) { + if (expressions[i] instanceof GroupAsExpression) { + groupFactories[i] = ((GroupAsExpression) expressions[i]).createGroupFactory(); + } else { + groupFactories[i] = new ValueGroupFactory(); + } + } + } + + @Override + public T get(int index, Class type) { + return (T) groupFactories[index].get(); + } + + @Override + public T get(Expression expr) { + int index = indexOf(expr); + return (T) groupFactories[index].get(); + } + + @Override + public Object[] toArray() { + Object[] row = new Object[groupFactories.length]; + for (int i=0; i < groupFactories.length; i++) { + row[i] = groupFactories[i].get(); + } + return row; + } + + public void add(Object[] row) { + for (int i=0; i < groupFactories.length; i++) { + groupFactories[i].add(row[i]); + } + } + } + +} diff --git a/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2Test.java b/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2Test.java new file mode 100644 index 000000000..b8271a4e7 --- /dev/null +++ b/querydsl-core/src/test/java/com/mysema/query/support/GroupBy2Test.java @@ -0,0 +1,154 @@ +package com.mysema.query.support; + + +import static com.mysema.query.support.GroupBy2.list; +import static com.mysema.query.support.GroupBy2.set; +import static junit.framework.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import com.mysema.commons.lang.CloseableIterator; +import com.mysema.commons.lang.EmptyCloseableIterator; +import com.mysema.commons.lang.IteratorAdapter; +import com.mysema.query.Projectable; +import com.mysema.query.Tuple; +import com.mysema.query.types.Expression; +import com.mysema.query.types.expr.NumberExpression; +import com.mysema.query.types.expr.StringExpression; +import com.mysema.query.types.path.NumberPath; +import com.mysema.query.types.path.StringPath; + +public class GroupBy2Test { + + private final NumberExpression postId = new NumberPath(Integer.class, "postId"); + + private final StringExpression postName = new StringPath("postName"); + + private final NumberExpression commentId = new NumberPath(Integer.class, "commentId"); + + static class PostWithComments { + public Integer id; + public String name; + public Set comments; + public PostWithComments(Integer id, String name, Set comments) { + this.id = id; + this.name = name; + this.comments = comments; + } + } + + @Test + public void Expression_Order_And_Type() { + new GroupBy2(postId, postName, set(commentId)).transform(new AbstractProjectable(){ + public CloseableIterator iterate(Expression[] args) { + assertEquals(postId, args[0]); + assertEquals(postName, args[1]); + assertEquals(commentId, args[2]); + return new EmptyCloseableIterator(); + } + }); + } + + /** + *
    + *
  1. Order of groups by first row of a group + *
  2. Rows belonging to a group may appear in any order + *
  3. Group of null is handled correctly + *
+ */ + @Test + public void Multiple_Groups() { + Collection results = new GroupBy2(postId, postName, list(commentId)).transform( + projectable( + row(1, "post 1", 1), + row(2, "post 2", 4), + row(1, "post 1", 2), + row(2, "post 2", 5), + row(3, "post 3", 6), + row(null, "null post", 7), + row(null, "null post", 8), + row(1, "post 1", 3) + ) + ); + assertEquals(4, results.size()); + Iterator iter = results.iterator(); + + Tuple g = iter.next(); + assertEquals(toInt(1), g.get(postId)); + assertEquals("post 1", g.get(postName)); + List comments = g.get(list(commentId)); + assertEquals(toInt(1), comments.get(0)); + assertEquals(toInt(2), comments.get(1)); + assertEquals(toInt(3), comments.get(2)); + + g = iter.next(); + assertEquals(toInt(2), g.get(postId)); + assertEquals("post 2", g.get(postName)); + comments = g.get(list(commentId)); + assertEquals(toInt(4), comments.get(0)); + assertEquals(toInt(5), comments.get(1)); + + g = iter.next(); + assertEquals(toInt(3), g.get(postId)); + assertEquals("post 3", g.get(postName)); + comments = g.get(list(commentId)); + assertEquals(toInt(6), comments.get(0)); + + // Group by null value + g = iter.next(); + assertEquals(null, g.get(postId)); + assertEquals("null post", g.get(postName)); + comments = g.get(list(commentId)); + assertEquals(toInt(7), comments.get(0)); + assertEquals(toInt(8), comments.get(1)); + } + + private static Set toSet(T... o) { + return new HashSet(Arrays.asList(o)); + } + + @Test + public void Group_As_Set() { + Collection results = new GroupBy2(postId, postName, set(commentId)).transform(projectable( + row(1, "post 1", 1), + row(null, "null post", 2) + )); + assertEquals(2, results.size()); + Iterator iter = results.iterator(); + + Tuple group = iter.next(); + assertEquals(toInt(1), group.get(postId)); + assertEquals("post 1", group.get(postName)); + assertEquals(toSet(1), group.get(set(commentId))); + + group = iter.next(); + assertEquals(null, group.get(postId)); + } + + private Projectable projectable(final Object[]... rows) { + return new AbstractProjectable(){ + public CloseableIterator iterate(Expression[] args) { + return iterator(rows); + } + }; + } + + private Integer toInt(int i) { + return Integer.valueOf(i); + } + + private static Object[] row(Object... row) { + return row; + } + + private static CloseableIterator iterator(Object[]... rows) { + return new IteratorAdapter(Arrays.asList(rows).iterator()); + } +} diff --git a/querydsl-core/src/test/java/com/mysema/query/support/GroupByTest.java b/querydsl-core/src/test/java/com/mysema/query/support/GroupByTest.java index 42bf32c45..117d81f8b 100644 --- a/querydsl-core/src/test/java/com/mysema/query/support/GroupByTest.java +++ b/querydsl-core/src/test/java/com/mysema/query/support/GroupByTest.java @@ -143,7 +143,7 @@ public class GroupByTest { @Test public void Nested_Transformers() { - // NOTE: This kind of bean projecting transformer can be generalized... + // Maybe this kind of bean projecting transformer can be generalized... // A transformer that uses results of GroupBy transformer to instantiate beans with collections List posts = new ResultTransformer>() { @@ -192,8 +192,7 @@ public class GroupByTest { } - @SuppressWarnings("unchecked") - private static CloseableIterator iterator(Object[]... rows) { - return new IteratorAdapter(Arrays.asList(rows).iterator()); + private static CloseableIterator iterator(Object[]... rows) { + return new IteratorAdapter(Arrays.asList(rows).iterator()); } }