Index: contrib/queries/src/test/org/apache/lucene/search/TestCollatingRangeQuery.java
===================================================================
--- contrib/queries/src/test/org/apache/lucene/search/TestCollatingRangeQuery.java	(revision 0)
+++ contrib/queries/src/test/org/apache/lucene/search/TestCollatingRangeQuery.java	(revision 0)
@@ -0,0 +1,137 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.RAMDirectory;
+
+import org.apache.lucene.util.LuceneTestCase;
+import java.io.IOException;
+import java.util.Locale;
+
+
+/** Test class mostly stolen from TestRangeQuery, except the testFarsi() method */
+public class TestCollatingRangeQuery extends LuceneTestCase {
+
+  private int docCount = 0;
+  private RAMDirectory dir;
+
+  public void setUp() throws Exception {
+    super.setUp();
+    dir = new RAMDirectory();
+  }
+
+  public void testExclusive() throws Exception {
+    Query query = new CollatingRangeQuery(new Term("content", "A"),
+                                          new Term("content", "C"),
+                                          false, Locale.ENGLISH);
+    initializeIndex(new String[] {"A", "B", "C", "D"});
+    IndexSearcher searcher = new IndexSearcher(dir);
+    Hits hits = searcher.search(query);
+    assertEquals("A,B,C,D, only B in range", 1, hits.length());
+    searcher.close();
+
+    initializeIndex(new String[] {"A", "B", "D"});
+    searcher = new IndexSearcher(dir);
+    hits = searcher.search(query);
+    assertEquals("A,B,D, only B in range", 1, hits.length());
+    searcher.close();
+
+    addDoc("C");
+    searcher = new IndexSearcher(dir);
+    hits = searcher.search(query);
+    assertEquals("C added, still only B in range", 1, hits.length());
+    searcher.close();
+  }
+
+  public void testInclusive() throws Exception {
+    Query query = new CollatingRangeQuery(new Term("content", "A"),
+                                          new Term("content", "C"),
+                                          true, Locale.ENGLISH);
+
+    initializeIndex(new String[]{"A", "B", "C", "D"});
+    IndexSearcher searcher = new IndexSearcher(dir);
+    Hits hits = searcher.search(query);
+    assertEquals("A,B,C,D - A,B,C in range", 3, hits.length());
+    searcher.close();
+
+    initializeIndex(new String[]{"A", "B", "D"});
+    searcher = new IndexSearcher(dir);
+    hits = searcher.search(query);
+    assertEquals("A,B,D - A and B in range", 2, hits.length());
+    searcher.close();
+
+    addDoc("C");
+    searcher = new IndexSearcher(dir);
+    hits = searcher.search(query);
+    assertEquals("C added - A, B, C in range", 3, hits.length());
+    searcher.close();
+  }
+
+  public void testFarsi() throws Exception {
+    // Neither Java 1.4.2 nor 1.5.0 has Farsi Locale collation available in
+    // RuleBasedCollator.  However, the Arabic Locale seems to order the Farsi
+    // characters properly.
+    Query query = new CollatingRangeQuery(new Term("content", "\u062F"),
+                                          new Term("content", "\u0698"),
+                                          true, new Locale("ar"));
+    // Unicode order would include U+0633 in [ U+062F - U+0698 ], but Farsi
+    // orders the U+0698 character before the U+0633 character, so the single
+    // index Term below should NOT be returned by a CollatingRangeQuery with a
+    // Farsi Locale.
+    initializeIndex(new String[]{ "\u0633\u0627\u0628"});
+    IndexSearcher searcher = new IndexSearcher(dir);
+    Hits hits = searcher.search(query);
+    assertEquals("The index Term should not be included.", 0, hits.length());
+
+    query =  new CollatingRangeQuery(new Term("content", "\u0633"),
+                                     new Term("content", "\u0638"),
+                                     true, new Locale("ar"));
+    hits = searcher.search(query);
+    assertEquals("The index Term should be included.", 1, hits.length());
+    searcher.close();
+  }
+
+  private void initializeIndex(String[] values) throws IOException {
+    IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.LIMITED);
+    for (int i = 0; i < values.length; i++) {
+      insertDoc(writer, values[i]);
+    }
+    writer.close();
+  }
+
+  private void addDoc(String content) throws IOException {
+    IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), false, IndexWriter.MaxFieldLength.LIMITED);
+    insertDoc(writer, content);
+    writer.close();
+  }
+
+  private void insertDoc(IndexWriter writer, String content) throws IOException {
+    Document doc = new Document();
+
+    doc.add(new Field("id", "id" + docCount, Field.Store.YES, Field.Index.UN_TOKENIZED));
+    doc.add(new Field("content", content, Field.Store.NO, Field.Index.TOKENIZED));
+
+    writer.addDocument(doc);
+    docCount++;
+  }
+}
\ No newline at end of file

Property changes on: contrib/queries/src/test/org/apache/lucene/search/TestCollatingRangeQuery.java
___________________________________________________________________
Name: cvs2svn:cvs-rev
   - 1.4
Name: svn:keywords
   - Author Date Id Revision
Name: svn:eol-style
   - native
Name: svn:executable
   + *

Index: contrib/queries/src/java/org/apache/lucene/search/CollatingRangeQuery.java
===================================================================
--- contrib/queries/src/java/org/apache/lucene/search/CollatingRangeQuery.java	(revision 0)
+++ contrib/queries/src/java/org/apache/lucene/search/CollatingRangeQuery.java	(revision 0)
@@ -0,0 +1,148 @@
+package org.apache.lucene.search;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.index.Term;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.text.Collator;
+
+/**
+ * A Query that matches documents within an exclusive range, where membership in
+ * the range is determined by the collation specified by the passed-in Locale.
+ *
+ * <strong>WARNING:</strong> In order to determine whether index Terms are
+ * included in the given range, every single index Term in the Field referenced
+ * by lowerTerm and/or upperTerm must be examined.  Depending on the number of
+ * index Terms in this Field, the operation could be very slow.
+ *
+ * Note that instances of this class are not created by QueryParser.  To create
+ * a non-collating RangeQuery using QueryParser, call
+ * {@link org.apache.lucene.queryParser.QueryParser#setUseOldRangeQuery(boolean)}
+ * with an argument of <code>true</code>.
+ *
+ * @see RangeQuery
+ * @see ConstantScoreRangeQuery
+ *
+ * @version $Id$
+ */
+public class CollatingRangeQuery extends RangeQuery {
+
+  private Locale locale;
+  private Collator collator;
+
+  /** Constructs a query selecting all terms greater than
+   * <code>lowerTerm</code> but less than <code>upperTerm</code>.
+   * There must be at least one term and either term may be null,
+   * in which case there is no bound on that side, but if there are
+   * two terms, both terms <b>must</b> be for the same field.
+   * If <code>inclusive</code> is true, then <code>lowerTerm</code> and
+   * <code>upperTerm</code> are included in the Term range.
+   *
+   * @param lowerTerm The Term at the lower end of the range
+   * @param upperTerm The Term at the upper end of the range
+   * @param inclusive If true, both <code>lowerTerm</code> and
+   *  <code>upperTerm</code> will themselves be included in the range.
+   * @param locale The Locale to use to collate index Terms, to determine their
+   *  membership in the Term range.
+   */
+  public CollatingRangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive,
+                             Locale locale) {
+    super(lowerTerm, upperTerm, inclusive);
+    this.locale = locale;
+    collator = Collator.getInstance(locale);
+  }
+
+
+  /** Constructs a query selecting all terms greater than
+   * <code>lowerTerm</code> but less than <code>upperTerm</code>.
+   * There must be at least one term and either term may be null,
+   * in which case there is no bound on that side, but if there are
+   * two terms, both terms <b>must</b> be for the same field.
+   * If <code>inclusive</code> is true, then <code>lowerTerm</code> and
+   * <code>upperTerm</code> are included in the Term range.
+   *
+   * @param lowerTerm The Term at the lower end of the range
+   * @param upperTerm The Term at the upper end of the range
+   * @param inclusive If true, both <code>lowerTerm</code> and
+   *  <code>upperTerm</code> will themselves be included in the range.
+   * @param locale The Locale to use to collate index Terms, to determine their
+   *  membership in the Term range.
+   * @param collationStrength Sets the (locale-dependent) collation strength.
+   *  Must be one of {@link Collator#PRIMARY}, {@link Collator#SECONDARY},
+   *  {@link Collator#TERTIARY}, or {@link Collator#IDENTICAL}.
+   *  See {@link Collator} class javadoc for more information.
+   */
+  public CollatingRangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive,
+                             Locale locale, int collationStrength) {
+    this(lowerTerm, upperTerm, inclusive, locale);
+    collator.setStrength(collationStrength);
+  }
+
+
+  public Query rewrite(IndexReader reader) throws IOException {
+
+    BooleanQuery query = new BooleanQuery(true);
+    TermEnum enumerator = reader.terms(new Term(getField(), ""));
+
+    try {
+      String testField = getField();
+
+      String lowerTermText
+        = getLowerTerm() != null ? getLowerTerm().text() : null;
+      String upperTermText
+        = getUpperTerm() != null ? getUpperTerm().text() : null;
+
+      do {
+        Term term = enumerator.term();
+        if (term != null && term.field() == testField) { // interned comparison
+          if ((isInclusive()
+               && (lowerTermText == null
+                   || collator.compare(term.text(), lowerTermText) >= 0)
+               && (upperTermText == null
+                   || collator.compare(term.text(), upperTermText) <= 0))
+              || (!isInclusive()
+                  && (lowerTermText == null
+                      || collator.compare(term.text(), lowerTermText) > 0)
+                  && (upperTermText == null
+                      || collator.compare(term.text(), upperTermText) < 0))) {
+            TermQuery tq = new TermQuery(term); // found a match
+            tq.setBoost(getBoost()); // set the boost
+            query.add(tq, BooleanClause.Occur.SHOULD); // add to query
+          }
+        }
+      }
+      while (enumerator.next());
+    }
+    finally {
+      enumerator.close();
+    }
+    return query;
+  }
+
+  /**
+   * @return The Locale used for to collate index terms to determine membership
+   *  in the range.
+   */
+  public Locale getLocale() {
+    return locale;
+  }
+}
