diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/LuceneSerializer.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/LuceneSerializer.java index 234e65ddb..d4b066b95 100644 --- a/querydsl-lucene/src/main/java/com/mysema/query/lucene/LuceneSerializer.java +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/LuceneSerializer.java @@ -5,25 +5,19 @@ */ package com.mysema.query.lucene; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.QueryParser; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.NumericRangeQuery; -import org.apache.lucene.search.PhraseQuery; -import org.apache.lucene.search.PrefixQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.Sort; -import org.apache.lucene.search.SortField; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.TermRangeQuery; -import org.apache.lucene.search.WildcardQuery; +import org.apache.lucene.search.*; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.util.NumericUtils; @@ -34,6 +28,7 @@ import com.mysema.query.types.Operator; import com.mysema.query.types.Ops; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.Path; +import com.mysema.query.types.PathType; /** * Serializes Querydsl queries to Lucene queries. @@ -41,8 +36,20 @@ import com.mysema.query.types.Path; * @author vema * */ -// TODO Add support for longs, floats etc. public class LuceneSerializer { + + private static final Map,Integer> sortFields = new HashMap,Integer>(); + + static{ + sortFields.put(Integer.class, SortField.INT); + sortFields.put(Float.class, SortField.FLOAT); + sortFields.put(Long.class, SortField.LONG); + sortFields.put(Double.class, SortField.DOUBLE); + sortFields.put(Short.class, SortField.SHORT); + sortFields.put(Byte.class, SortField.BYTE); + sortFields.put(BigDecimal.class, SortField.DOUBLE); + sortFields.put(BigInteger.class, SortField.LONG); + } public static final LuceneSerializer DEFAULT = new LuceneSerializer(false, true); @@ -89,6 +96,8 @@ public class LuceneSerializer { return le(operation); } else if (op == Ops.GOE || op == Ops.AOE) { return ge(operation); + } else if (op == PathType.DELEGATE){ + return toQuery(operation.getArg(0)); } throw new UnsupportedOperationException("Illegal operation " + operation); } @@ -116,18 +125,38 @@ public class LuceneSerializer { } return new WildcardQuery(new Term(field, normalize(terms[0]))); } - + + @SuppressWarnings("unchecked") private Query eq(Operation operation) { verifyArguments(operation); String field = toField(operation.getArg(0)); - if (operation.getArg(1).getType().equals(Integer.class)) { - return new TermQuery(new Term(field, NumericUtils - .intToPrefixCoded(((Constant) operation.getArg(1)).getConstant()))); - } else if (operation.getArg(1).getType().equals(Double.class)) { - return new TermQuery(new Term(field, NumericUtils - .doubleToPrefixCoded(((Constant) operation.getArg(1)).getConstant()))); + if (Number.class.isAssignableFrom(operation.getArg(1).getType())){ + return new TermQuery(new Term(field, convertNumber(((Constant)operation.getArg(1)).getConstant()))); + }else{ + return eq(field, createTerms(operation.getArg(1))); } - return eq(field, createTerms(operation.getArg(1))); + } + + private String convertNumber(Number number){ + if (Integer.class.isInstance(number)){ + return NumericUtils.intToPrefixCoded(number.intValue()); + }else if (Double.class.isInstance(number)){ + return NumericUtils.doubleToPrefixCoded(number.doubleValue()); + }else if (Long.class.isInstance(number)){ + return NumericUtils.longToPrefixCoded(number.longValue()); + }else if (Float.class.isInstance(number)){ + return NumericUtils.floatToPrefixCoded(number.floatValue()); + }else if (Byte.class.isInstance(number)){ + return NumericUtils.intToPrefixCoded(number.intValue()); + }else if (Short.class.isInstance(number)){ + return NumericUtils.intToPrefixCoded(number.intValue()); + }else if (BigDecimal.class.isInstance(number)){ + return NumericUtils.doubleToPrefixCoded(number.doubleValue()); + }else if (BigInteger.class.isInstance(number)){ + return NumericUtils.longToPrefixCoded(number.longValue()); + }else{ + throw new IllegalArgumentException("Unsupported numeric type " + number.getClass().getName()); + } } private Query eq(String field, String[] terms) { @@ -228,37 +257,45 @@ public class LuceneSerializer { return range(toField(operation.getArg(0)), operation.getArg(1), null, true, true); } - // TODO Simplify(?) - // TODO Timo: Check if the the annotation is necessary, thanks! -vema @SuppressWarnings("unchecked") private Query range(String field, Expr min, Expr max, boolean minInc, boolean maxInc) { - if (min != null && min.getType().equals(Integer.class) || max != null && max.getType().equals(Integer.class)) { - return integerRange(field, min == null ? null : ((Constant) min).getConstant(), max == null ? null : ((Constant) max).getConstant(), minInc, maxInc); - } else if (min != null && min.getType().equals(Double.class) || max != null && max.getType().equals(Double.class)) { - return doubleRange(field, min == null ? null : ((Constant) min).getConstant(), max == null ? null : ((Constant) max).getConstant(), minInc, maxInc); + if (min != null && Number.class.isAssignableFrom(min.getType()) || max != null && Number.class.isAssignableFrom(max.getType())) { + Class numType = (Class)(min != null ? min.getType() : max.getType()); + return numericRange((Class)numType, field, + (Number)(min == null ? null : ((Constant) min).getConstant()), + (Number)(max == null ? null : ((Constant) max).getConstant()), + minInc, maxInc); } else { return stringRange(field, min, max, minInc, maxInc); } } - - private Query integerRange(String field, Integer min, Integer max, boolean minInc, boolean maxInc) { - return NumericRangeQuery.newIntRange(field, min, max, minInc, maxInc); - } - - private Query doubleRange(String field, Double min, Double max, boolean minInc, boolean maxInc) { - return NumericRangeQuery.newDoubleRange(field, min, max, minInc, maxInc); + + private NumericRangeQuery numericRange(Class clazz, String field, N min, N max, boolean minInc, boolean maxInc){ + if (clazz.equals(Integer.class)){ + return NumericRangeQuery.newIntRange(field, (Integer)min, (Integer)max, minInc, maxInc); + }else if (clazz.equals(Double.class)){ + return NumericRangeQuery.newDoubleRange(field, (Double)min, (Double)max, minInc, minInc); + }else if (clazz.equals(Float.class)){ + return NumericRangeQuery.newFloatRange(field, (Float)min, (Float)max, minInc, minInc); + }else if (clazz.equals(Long.class)){ + return NumericRangeQuery.newLongRange(field, (Long)min, (Long)max, minInc, minInc); + }else if (clazz.equals(Byte.class) || clazz.equals(Short.class)){ + return NumericRangeQuery.newIntRange(field, + min != null ? min.intValue() : null, + max != null ? max.intValue() : null, minInc, maxInc); + }else{ + throw new IllegalArgumentException("Unsupported numeric type " + clazz.getName()); + } } private Query stringRange(String field, Expr min, Expr max, boolean minInc, boolean maxInc) { if (min == null) { - return new TermRangeQuery(field, null, normalize(createTerms(max)[0]), minInc, - maxInc); + return new TermRangeQuery(field, null, normalize(createTerms(max)[0]), minInc, maxInc); } else if (max == null) { - return new TermRangeQuery(field, normalize(createTerms(min)[0]), null, minInc, - maxInc); - } - return new TermRangeQuery(field, normalize(createTerms(min)[0]), normalize(createTerms(max)[0]), minInc, - maxInc); + return new TermRangeQuery(field, normalize(createTerms(min)[0]), null, minInc, maxInc); + }else{ + return new TermRangeQuery(field, normalize(createTerms(min)[0]), normalize(createTerms(max)[0]), minInc, maxInc); + } } @SuppressWarnings("unchecked") @@ -282,26 +319,31 @@ public class LuceneSerializer { List> arguments = operation.getArgs(); for (int i = 1; i < arguments.size(); ++i) { if (!(arguments.get(i) instanceof Constant) - && !(arguments.get(i) instanceof PhraseElement)) { - throw new IllegalArgumentException( - "operation argument was not of type Constant nor PhraseElement."); + && !(arguments.get(i) instanceof PhraseElement) + && !(arguments.get(i) instanceof TermElement)) { + throw new IllegalArgumentException("operand was of unsupported type " + arguments.get(i).getClass().getName()); } } } private String[] createTerms(Expr expr) { - if (splitTerms || expr instanceof PhraseElement) { - return StringUtils.split(expr.toString()); - } - return new String[] { expr.toString() }; + return split(expr, expr.toString()); } private String[] createEscapedTerms(Expr expr) { - String escaped = QueryParser.escape(expr.toString()); - if (splitTerms || expr instanceof PhraseElement) { - return StringUtils.split(escaped); - } - return new String[] { escaped }; + return split(expr, QueryParser.escape(expr.toString())); + } + + private String[] split(Expr expr, String str){ + if (expr instanceof PhraseElement){ + return StringUtils.split(str); + }else if (expr instanceof TermElement){ + return new String[] {str}; + }else if (splitTerms){ + return StringUtils.split(str); + }else{ + return new String[] {str}; + } } private String normalize(String s) { @@ -314,29 +356,25 @@ public class LuceneSerializer { } else if (expr instanceof QueryElement) { return ((QueryElement) expr).getQuery(); } - throw new IllegalArgumentException("expr was not of type Operation or QueryElement"); + throw new IllegalArgumentException("expr was of unsupported type " + expr.getClass().getName()); } - // TODO Add support for sorting floats, longs etc. public Sort toSort(List> orderBys) { - List sortFields = new ArrayList(orderBys.size()); - for (OrderSpecifier orderSpecifier : orderBys) { - if (!(orderSpecifier.getTarget() instanceof Path)) { + List sorts = new ArrayList(orderBys.size()); + for (OrderSpecifier order : orderBys) { + if (!(order.getTarget() instanceof Path)) { throw new IllegalArgumentException("argument was not of type Path."); } - if (orderSpecifier.getTarget().getType().equals(Integer.class)) { - sortFields.add(new SortField(toField((Path) orderSpecifier.getTarget()), - SortField.INT, !orderSpecifier.isAscending())); - } else if (orderSpecifier.getTarget().getType().equals(Double.class)) { - sortFields.add(new SortField(toField((Path) orderSpecifier.getTarget()), - SortField.DOUBLE, !orderSpecifier.isAscending())); + Class type = order.getTarget().getType(); + boolean reverse = !order.isAscending(); + if (Number.class.isAssignableFrom(type)) { + sorts.add(new SortField(toField(order.getTarget()),sortFields.get(type),reverse)); } else { - sortFields.add(new SortField(toField((Path) orderSpecifier.getTarget()), - Locale.ENGLISH, !orderSpecifier.isAscending())); + sorts.add(new SortField(toField(order.getTarget()),Locale.ENGLISH, reverse)); } } Sort sort = new Sort(); - sort.setSort(sortFields.toArray(new SortField[sortFields.size()])); + sort.setSort(sorts.toArray(new SortField[sorts.size()])); return sort; } } diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/PhraseElement.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/PhraseElement.java index 1188d918f..d1b7f4a88 100644 --- a/querydsl-lucene/src/main/java/com/mysema/query/lucene/PhraseElement.java +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/PhraseElement.java @@ -5,6 +5,8 @@ import com.mysema.query.types.expr.EString; import com.mysema.query.types.expr.EStringConst; /** + * PhraseElement represents the embedded String as a phrase + * * @author tiwe * */ diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/TermElement.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/TermElement.java new file mode 100644 index 000000000..cea97904d --- /dev/null +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/TermElement.java @@ -0,0 +1,43 @@ +package com.mysema.query.lucene; + +import com.mysema.query.types.Visitor; +import com.mysema.query.types.expr.EString; +import com.mysema.query.types.expr.EStringConst; + +/** + * TermElement represents the embedded String as a term + * + * @author tiwe + * + */ +public class TermElement extends EString{ + + private static final long serialVersionUID = 2350215644019186076L; + + private final EStringConst string; + + public TermElement(String str) { + this.string = (EStringConst) EStringConst.create(str); + } + + @Override + public void accept(Visitor v) { + string.accept(v); + } + + @Override + public boolean equals(Object o) { + if (o == this){ + return true; + }else if (o instanceof TermElement){ + return ((TermElement)o).string.equals(string); + }else{ + return false; + } + } + + @Override + public int hashCode(){ + return string.hashCode(); + } +} diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/LuceneSerializerTest.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/LuceneSerializerTest.java index 2369166a2..b1e1f7af4 100644 --- a/querydsl-lucene/src/test/java/com/mysema/query/lucene/LuceneSerializerTest.java +++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/LuceneSerializerTest.java @@ -30,6 +30,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import com.mysema.query.BooleanBuilder; import com.mysema.query.MatchingFilters; import com.mysema.query.Module; import com.mysema.query.Target; @@ -55,9 +56,18 @@ public class LuceneSerializerTest { private PString rating; private PNumber year; private PNumber gross; + + private PNumber longField; + private PNumber shortField; + private PNumber byteField; + private PNumber floatField; private static final String YEAR_PREFIX_CODED = NumericUtils.intToPrefixCoded(1990); private static final String GROSS_PREFIX_CODED = NumericUtils.doubleToPrefixCoded(900.00); + private static final String LONG_PREFIX_CODED = NumericUtils.longToPrefixCoded(1); + private static final String SHORT_PREFIX_CODED = NumericUtils.intToPrefixCoded(1); + private static final String BYTE_PREFIX_CODED = NumericUtils.intToPrefixCoded(1); + private static final String FLOAT_PREFIX_CODED = NumericUtils.floatToPrefixCoded((float)1.0); private RAMDirectory idx; private IndexWriter writer; @@ -72,6 +82,11 @@ public class LuceneSerializerTest { doc.add(new Field("rating", new StringReader("Good"))); doc.add(new NumericField("year", Store.YES, true).setIntValue(1990)); doc.add(new NumericField("gross", Store.YES, true).setDoubleValue(900.00)); + + doc.add(new NumericField("longField", Store.YES, true).setLongValue(1)); + doc.add(new NumericField("shortField", Store.YES, true).setIntValue(1)); + doc.add(new NumericField("byteField", Store.YES, true).setIntValue(1)); + doc.add(new NumericField("floatField", Store.YES, true).setFloatValue(1)); return doc; } @@ -87,6 +102,11 @@ public class LuceneSerializerTest { year = entityPath.getNumber("year", Integer.class); rating = entityPath.getString("rating"); gross = entityPath.getNumber("gross", Double.class); + + longField = entityPath.getNumber("longField", Long.class); + shortField = entityPath.getNumber("shortField", Short.class); + byteField = entityPath.getNumber("byteField", Byte.class); + floatField = entityPath.getNumber("floatField", Float.class); idx = new RAMDirectory(); writer = new IndexWriter(idx, new StandardAnalyzer(Version.LUCENE_CURRENT), true, MaxFieldLength.UNLIMITED); @@ -173,6 +193,14 @@ public class LuceneSerializerTest { public void eq_Numeric_Double() throws Exception { testQuery(gross.eq(900.00), "gross:" + GROSS_PREFIX_CODED, 1); } + + @Test + public void eq_Numeric() throws Exception{ + testQuery(longField.eq(1l), "longField:" + LONG_PREFIX_CODED, 1); + testQuery(shortField.eq((short)1), "shortField:" + SHORT_PREFIX_CODED, 1); + testQuery(byteField.eq((byte)1), "byteField:" + BYTE_PREFIX_CODED, 1); + testQuery(floatField.eq((float)1.0), "floatField:" + FLOAT_PREFIX_CODED, 1); + } @Test public void eq_Should_Not_Find_Results_But_Lucene_Semantics_Differs_From_Querydsls() throws Exception { @@ -314,6 +342,15 @@ public class LuceneSerializerTest { testQuery(gross.between(10.00, 19030.00), "gross:[10.0 TO 19030.0]", 1); } + @Test + public void between_Numeric() throws Exception{ + testQuery(longField.between(0l,2l), "longField:[0 TO 2]", 1); + testQuery(shortField.between((short)0,(short)2), "shortField:[0 TO 2]", 1); + testQuery(byteField.between((byte)0,(byte)2), "byteField:[0 TO 2]", 1); + testQuery(floatField.between((float)0.0,(float)2.0), "floatField:[0.0 TO 2.0]", 1); + } + + @Test public void between_Phrase() throws Exception { testQuery(title.between("Jurassic Park", "Kundun"), "title:[jurassic TO kundun]", 1); @@ -497,6 +534,12 @@ public class LuceneSerializerTest { public void goe_Numeric_Double_Not_Found() throws Exception { testQuery(gross.goe(900.10), "gross:[900.1 TO *]", 0); } + + @Test + public void booleanBuilder() throws Exception{ + testQuery(new BooleanBuilder(gross.goe(900.10)), "gross:[900.1 TO *]", 0); + } + @Test @Ignore diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/TermElementTest.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/TermElementTest.java new file mode 100644 index 000000000..f7ebfa67d --- /dev/null +++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/TermElementTest.java @@ -0,0 +1,31 @@ +package com.mysema.query.lucene; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.junit.Test; + +import com.mysema.query.types.path.PString; + + +public class TermElementTest { + + @Test + public void test(){ + PString title = new PString("title"); + LuceneSerializer serializer = new LuceneSerializer(false,true); + assertEquals("title:\"Hello World\"", serializer.toQuery(title.eq("Hello World")).toString()); + assertEquals("title:Hello World", serializer.toQuery(title.eq(new TermElement("Hello World"))).toString()); + } + + @Test + public void testEqualsAndHashCode(){ + TermElement el1 = new TermElement("x"), el2 = new TermElement("x"), el3 = new TermElement("y"); + assertEquals(el1, el2); + assertFalse(el1.equals(el3)); + assertEquals(el1.hashCode(), el2.hashCode()); + } + + + +}