You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by sy...@apache.org on 2016/09/11 21:30:53 UTC

[22/50] [abbrv] lucenenet git commit: Moved Lucene.Net.QueryParser and Lucene.Net.Tests.QueryParser projects into src\ directory.

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/ComposedQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/ComposedQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/ComposedQuery.cs
new file mode 100644
index 0000000..d421ad6
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/ComposedQuery.cs
@@ -0,0 +1,144 @@
+\ufeffusing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Base class for composite queries (such as AND/OR/NOT)
+    /// </summary>
+    public abstract class ComposedQuery : SrndQuery
+    {
+        public ComposedQuery(IEnumerable<SrndQuery> qs, bool operatorInfix, string opName)
+        {
+            Recompose(qs);
+            this.operatorInfix = operatorInfix;
+            this.opName = opName;
+        }
+
+        protected virtual void Recompose(IEnumerable<SrndQuery> queries)
+        {
+            if (queries.Count() < 2) throw new InvalidOperationException("Too few subqueries");
+            this.queries = new List<SrndQuery>(queries);
+        }
+
+        protected string opName;
+        public virtual string OperatorName { get { return opName; } }
+
+        protected IList<SrndQuery> queries;
+
+        public virtual IEnumerator<SrndQuery> GetSubQueriesEnumerator()
+        {
+            return queries.GetEnumerator();
+        }
+
+        public virtual int NrSubQueries { get { return queries.Count; } }
+
+        public virtual SrndQuery GetSubQuery(int qn) { return queries[qn]; }
+
+        private bool operatorInfix;
+        public virtual bool IsOperatorInfix { get { return operatorInfix; } } /* else prefix operator */
+
+        public IEnumerable<Search.Query> MakeLuceneSubQueriesField(string fn, BasicQueryFactory qf)
+        {
+            List<Search.Query> luceneSubQueries = new List<Search.Query>();
+            IEnumerator<SrndQuery> sqi = GetSubQueriesEnumerator();
+            while (sqi.MoveNext())
+            {
+                luceneSubQueries.Add((sqi.Current).MakeLuceneQueryField(fn, qf));
+            }
+            return luceneSubQueries;
+        }
+
+        public override string ToString()
+        {
+            StringBuilder r = new StringBuilder();
+            if (IsOperatorInfix)
+            {
+                InfixToString(r);
+            }
+            else
+            {
+                PrefixToString(r);
+            }
+            WeightToString(r);
+            return r.ToString();
+        }
+
+        // Override for different spacing
+        protected virtual string PrefixSeparator { get { return ", "; } }
+        protected virtual string BracketOpen { get { return "("; } }
+        protected virtual string BracketClose { get { return ")"; } }
+
+        protected virtual void InfixToString(StringBuilder r)
+        {
+            /* Brackets are possibly redundant in the result. */
+            IEnumerator<SrndQuery> sqi = GetSubQueriesEnumerator();
+            r.Append(BracketOpen);
+            if (sqi.MoveNext())
+            {
+                r.Append(sqi.Current.ToString());
+                while (sqi.MoveNext())
+                {
+                    r.Append(" ");
+                    r.Append(OperatorName); /* infix operator */
+                    r.Append(" ");
+                    r.Append(sqi.Current.ToString());
+                }
+            }
+            r.Append(BracketClose);
+        }
+
+        protected virtual void PrefixToString(StringBuilder r)
+        {
+            IEnumerator<SrndQuery> sqi = GetSubQueriesEnumerator();
+            r.Append(OperatorName); /* prefix operator */
+            r.Append(BracketOpen);
+            if (sqi.MoveNext())
+            {
+                r.Append(sqi.Current.ToString());
+                while (sqi.MoveNext())
+                {
+                    r.Append(PrefixSeparator);
+                    r.Append(sqi.Current.ToString());
+                }
+            }
+            r.Append(BracketClose);
+        }
+
+        public override bool IsFieldsSubQueryAcceptable
+        {
+            get
+            {
+                /* at least one subquery should be acceptable */
+                IEnumerator<SrndQuery> sqi = GetSubQueriesEnumerator();
+                while (sqi.MoveNext())
+                {
+                    if ((sqi.Current).IsFieldsSubQueryAcceptable)
+                    {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/DistanceQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/DistanceQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/DistanceQuery.cs
new file mode 100644
index 0000000..1ca7a01
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/DistanceQuery.cs
@@ -0,0 +1,117 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Search.Spans;
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Factory for NEAR queries 
+    /// </summary>
+    public class DistanceQuery : ComposedQuery, IDistanceSubQuery
+    {
+        public DistanceQuery(
+            IEnumerable<SrndQuery> queries,
+            bool infix,
+            int opDistance,
+            string opName,
+            bool ordered)
+            : base(queries, infix, opName)
+        {
+            this.opDistance = opDistance; /* the distance indicated in the operator */
+            this.ordered = ordered;
+        }
+
+        private int opDistance;
+        public virtual int OpDistance { get { return opDistance; } }
+
+        private bool ordered;
+        public virtual bool QueriesOrdered { get { return ordered; } }
+
+
+        public virtual string DistanceSubQueryNotAllowed()
+        {
+            var sqi = GetSubQueriesEnumerator();
+            while (sqi.MoveNext())
+            {
+                var dsq = sqi.Current as IDistanceSubQuery;
+                if (dsq != null)
+                {
+                    string m = dsq.DistanceSubQueryNotAllowed();
+                    if (m != null)
+                    {
+                        return m;
+                    }
+                }
+                else
+                {
+                    return "Operator " + OperatorName + " does not allow subquery " + dsq.ToString();
+                }
+            }
+            return null; /* subqueries acceptable */
+        }
+
+        public virtual void AddSpanQueries(SpanNearClauseFactory sncf)
+        {
+            Search.Query snq = GetSpanNearQuery(sncf.IndexReader,
+                                  sncf.FieldName,
+                                  Weight,
+                                  sncf.BasicQueryFactory);
+            sncf.AddSpanQuery(snq);
+        }
+
+        public Search.Query GetSpanNearQuery(
+            IndexReader reader,
+            String fieldName,
+            float boost,
+            BasicQueryFactory qf)
+        {
+            SpanQuery[] spanClauses = new SpanQuery[NrSubQueries];
+            var sqi = GetSubQueriesEnumerator();
+            int qi = 0;
+            while (sqi.MoveNext())
+            {
+                SpanNearClauseFactory sncf = new SpanNearClauseFactory(reader, fieldName, qf);
+
+                ((IDistanceSubQuery)sqi.Current).AddSpanQueries(sncf);
+                if (sncf.Count == 0)
+                { /* distance operator requires all sub queries */
+                    while (sqi.MoveNext())
+                    { /* produce evt. error messages but ignore results */
+                        ((IDistanceSubQuery)sqi.Current).AddSpanQueries(sncf);
+                        sncf.Clear();
+                    }
+                    return SrndQuery.TheEmptyLcnQuery;
+                }
+
+                spanClauses[qi] = sncf.MakeSpanClause();
+                qi++;
+            }
+            SpanNearQuery r = new SpanNearQuery(spanClauses, OpDistance - 1, QueriesOrdered);
+            r.Boost = boost;
+            return r;
+        }
+
+        public override Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf)
+        {
+            return new DistanceRewriteQuery(this, fieldName, qf);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/DistanceRewriteQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/DistanceRewriteQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/DistanceRewriteQuery.cs
new file mode 100644
index 0000000..3d3a108
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/DistanceRewriteQuery.cs
@@ -0,0 +1,35 @@
+\ufeffnamespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    internal class DistanceRewriteQuery : RewriteQuery<DistanceQuery>
+    {
+        public DistanceRewriteQuery(
+            DistanceQuery srndQuery,
+            string fieldName,
+            BasicQueryFactory qf)
+            : base(srndQuery, fieldName, qf)
+        {
+        }
+
+        public override Search.Query Rewrite(Index.IndexReader reader)
+        {
+            return srndQuery.GetSpanNearQuery(reader, fieldName, Boost, qf);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/DistanceSubQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/DistanceSubQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/DistanceSubQuery.cs
new file mode 100644
index 0000000..639f9e0
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/DistanceSubQuery.cs
@@ -0,0 +1,36 @@
+\ufeffnamespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Interface for queries that can be nested as subqueries
+    /// into a span near.
+    /// </summary>
+    public interface IDistanceSubQuery
+    {
+        /// <summary>
+        /// When distanceSubQueryNotAllowed() returns non null, the reason why the subquery
+        /// is not allowed as a distance subquery is returned.
+        /// <br>When distanceSubQueryNotAllowed() returns null addSpanNearQueries() can be used
+        /// in the creation of the span near clause for the subquery.
+        /// </summary>
+        string DistanceSubQueryNotAllowed();
+
+        void AddSpanQueries(SpanNearClauseFactory sncf);
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/FieldsQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/FieldsQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/FieldsQuery.cs
new file mode 100644
index 0000000..912bf36
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/FieldsQuery.cs
@@ -0,0 +1,105 @@
+\ufeffusing System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Forms an OR query of the provided query across multiple fields.
+    /// </summary>
+    public class FieldsQuery : SrndQuery /* mostly untested */
+    {
+        private SrndQuery q;
+        private IEnumerable<string> fieldNames;
+        private readonly char fieldOp;
+        private readonly string OrOperatorName = "OR"; /* for expanded queries, not normally visible */
+
+        public FieldsQuery(SrndQuery q, IEnumerable<string> fieldNames, char fieldOp)
+        {
+            this.q = q;
+            this.fieldNames = new List<string>(fieldNames);
+            this.fieldOp = fieldOp;
+        }
+
+        public FieldsQuery(SrndQuery q, string fieldName, char fieldOp)
+        {
+            this.q = q;
+            var fieldNameList = new List<string>();
+            fieldNameList.Add(fieldName);
+            this.fieldNames = fieldNameList;
+            this.fieldOp = fieldOp;
+        }
+
+        public override bool IsFieldsSubQueryAcceptable
+        {
+            get { return false; }
+        }
+
+        public Search.Query MakeLuceneQueryNoBoost(BasicQueryFactory qf)
+        {
+            if (fieldNames.Count() == 1)
+            { /* single field name: no new queries needed */
+                return q.MakeLuceneQueryFieldNoBoost(fieldNames.FirstOrDefault(), qf);
+            }
+            else
+            { /* OR query over the fields */
+                List<SrndQuery> queries = new List<SrndQuery>();
+                foreach (var fieldName in fieldNames)
+                {
+                    var qc = (SrndQuery)q.Clone();
+                    queries.Add(new FieldsQuery(qc, fieldName, fieldOp));
+                }
+                OrQuery oq = new OrQuery(queries,
+                                        true /* infix OR for field names */,
+                                        OrOperatorName);
+                // System.out.println(getClass().toString() + ", fields expanded: " + oq.toString()); /* needs testing */
+                return oq.MakeLuceneQueryField(null, qf);
+            }
+        }
+
+        public override Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf)
+        {
+            return MakeLuceneQueryNoBoost(qf); /* use this.fieldNames instead of fieldName */
+        }
+
+        public virtual IEnumerable<string> FieldNames { get { return fieldNames; } }
+
+        public virtual char FieldOperator { get { return fieldOp; } }
+
+        public override string ToString()
+        {
+            StringBuilder r = new StringBuilder();
+            r.Append("(");
+            FieldNamesToString(r);
+            r.Append(q.ToString());
+            r.Append(")");
+            return r.ToString();
+        }
+
+        protected virtual void FieldNamesToString(StringBuilder r)
+        {
+            foreach (var fieldName in FieldNames)
+            {
+                r.Append(fieldName);
+                r.Append(FieldOperator);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/NotQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/NotQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/NotQuery.cs
new file mode 100644
index 0000000..30d40a8
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/NotQuery.cs
@@ -0,0 +1,48 @@
+\ufeffusing Lucene.Net.Search;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Factory for prohibited clauses
+    /// </summary>
+    public class NotQuery : ComposedQuery
+    {
+        public NotQuery(IEnumerable<SrndQuery> queries, string opName)
+            : base(queries, true /* infix */, opName)
+        {
+        }
+
+        public override Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf)
+        {
+            var luceneSubQueries = MakeLuceneSubQueriesField(fieldName, qf);
+            BooleanQuery bq = new BooleanQuery();
+            bq.Add(luceneSubQueries.FirstOrDefault(), BooleanClause.Occur.MUST);
+            SrndBooleanQuery.AddQueriesToBoolean(bq,
+                // FIXME: do not allow weights on prohibited subqueries.
+                    //luceneSubQueries.subList(1, luceneSubQueries.size()),
+                    luceneSubQueries.Skip(1).ToList(),
+                // later subqueries: not required, prohibited
+                    BooleanClause.Occur.MUST_NOT);
+            return bq;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/OrQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/OrQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/OrQuery.cs
new file mode 100644
index 0000000..f7d0036
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/OrQuery.cs
@@ -0,0 +1,71 @@
+\ufeffusing Lucene.Net.Search;
+using System.Collections.Generic;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Factory for disjunctions
+    /// </summary>
+    public class OrQuery : ComposedQuery, IDistanceSubQuery
+    {
+        public OrQuery(IEnumerable<SrndQuery> queries, bool infix, string opName)
+            : base(queries, infix, opName)
+        {
+        }
+
+        public override Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf)
+        {
+            return SrndBooleanQuery.MakeBooleanQuery(
+                /* subqueries can be individually boosted */
+                MakeLuceneSubQueriesField(fieldName, qf), BooleanClause.Occur.SHOULD);
+        }
+
+        public virtual string DistanceSubQueryNotAllowed()
+        {
+            var sqi = GetSubQueriesEnumerator();
+            while (sqi.MoveNext())
+            {
+                SrndQuery leq = sqi.Current;
+                if (leq is IDistanceSubQuery)
+                {
+                    string m = ((IDistanceSubQuery)leq).DistanceSubQueryNotAllowed();
+                    if (m != null)
+                    {
+                        return m;
+                    }
+                }
+                else
+                {
+                    return "subquery not allowed: " + leq.ToString();
+                }
+            }
+            return null;
+        }
+
+        public virtual void AddSpanQueries(SpanNearClauseFactory sncf)
+        {
+            var sqi = GetSubQueriesEnumerator();
+            while (sqi.MoveNext())
+            {
+                ((IDistanceSubQuery)sqi.Current).AddSpanQueries(sncf);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/RewriteQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/RewriteQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/RewriteQuery.cs
new file mode 100644
index 0000000..030923f
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/RewriteQuery.cs
@@ -0,0 +1,85 @@
+\ufeffusing Lucene.Net.Index;
+using System;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    public abstract class RewriteQuery<SQ> : Search.Query
+    {
+        protected readonly SQ srndQuery;
+        protected readonly string fieldName;
+        protected readonly BasicQueryFactory qf;
+
+        public RewriteQuery(
+            SQ srndQuery,
+            String fieldName,
+            BasicQueryFactory qf)
+        {
+            this.srndQuery = srndQuery;
+            this.fieldName = fieldName;
+            this.qf = qf;
+        }
+
+        public abstract override Search.Query Rewrite(IndexReader reader);
+
+        public override string ToString()
+        {
+            return ToString(null);
+        }
+
+        public override string ToString(string field)
+        {
+            return GetType().Name
+                + (field == null ? "" : "(unused: " + field + ")")
+                + "(" + fieldName
+                + ", " + srndQuery.ToString()
+                + ", " + qf.ToString()
+                + ")";
+        }
+
+        public override int GetHashCode()
+        {
+            return GetType().GetHashCode()
+                ^ fieldName.GetHashCode()
+                ^ qf.GetHashCode()
+                ^ srndQuery.GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj == null)
+                return false;
+            if (!GetType().Equals(obj.GetType()))
+                return false;
+            RewriteQuery<SQ> other = (RewriteQuery<SQ>)obj;
+            return fieldName.Equals(other.fieldName)
+                && qf.Equals(other.qf)
+                && srndQuery.Equals(other.srndQuery);
+        }
+
+        /// <summary>
+        /// Not supported by this query.
+        /// </summary>
+        /// <exception cref="NotSupportedException">throws NotSupportedException always: clone is not supported.</exception>
+        public override object Clone()
+        {
+            throw new NotSupportedException();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SimpleTerm.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SimpleTerm.cs b/src/Lucene.Net.QueryParser/Surround/Query/SimpleTerm.cs
new file mode 100644
index 0000000..5e39e03
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SimpleTerm.cs
@@ -0,0 +1,118 @@
+\ufeffusing Lucene.Net.Index;
+using System;
+using System.Text;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Base class for queries that expand to sets of simple terms.
+    /// </summary>
+    public abstract class SimpleTerm : SrndQuery, IDistanceSubQuery, IComparable<SimpleTerm>
+    {
+        public SimpleTerm(bool q) 
+        { 
+            quoted = q; 
+        }
+
+        private bool quoted;
+        internal bool IsQuoted { get { return quoted; } }
+
+        public virtual string Quote { get { return "\""; }}
+        public virtual string FieldOperator { get { return "/"; } }
+
+        public abstract string ToStringUnquoted();
+
+        [Obsolete("deprecated (March 2011) Not normally used, to be removed from Lucene 4.0. This class implementing Comparable is to be removed at the same time.")]
+        public int CompareTo(SimpleTerm ost)
+        {
+            /* for ordering terms and prefixes before using an index, not used */
+            return this.ToStringUnquoted().CompareTo(ost.ToStringUnquoted());
+        }
+
+        protected virtual void SuffixToString(StringBuilder r) { } /* override for prefix query */
+
+
+        public override string ToString()
+        {
+            StringBuilder r = new StringBuilder();
+            if (IsQuoted)
+            {
+                r.Append(Quote);
+            }
+            r.Append(ToStringUnquoted());
+            if (IsQuoted)
+            {
+                r.Append(Quote);
+            }
+            SuffixToString(r);
+            WeightToString(r);
+            return r.ToString();
+        }
+
+        public abstract void VisitMatchingTerms(
+                            IndexReader reader,
+                            string fieldName,
+                            IMatchingTermVisitor mtv);
+
+        /// <summary>
+        /// Callback to visit each matching term during "rewrite"
+        /// in <see cref="M:VisitMatchingTerm(Term)"/>
+        /// </summary>
+        public interface IMatchingTermVisitor
+        {
+            void VisitMatchingTerm(Term t);
+        }
+
+        public string DistanceSubQueryNotAllowed()
+        {
+            return null;
+        }
+
+        public void AddSpanQueries(SpanNearClauseFactory sncf)
+        {
+            VisitMatchingTerms(
+                sncf.IndexReader,
+                sncf.FieldName,
+                new AddSpanQueriesMatchingTermVisitor(sncf, Weight));
+        }
+
+        internal class AddSpanQueriesMatchingTermVisitor : IMatchingTermVisitor
+        {
+            private readonly SpanNearClauseFactory sncf;
+            private readonly float weight;
+
+            public AddSpanQueriesMatchingTermVisitor(SpanNearClauseFactory sncf, float weight)
+            {
+                this.sncf = sncf;
+                this.weight = weight;
+            }
+
+            public void VisitMatchingTerm(Term term)
+            {
+                sncf.AddTermWeighted(term, weight);
+            }
+        }
+
+        public override Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf)
+        {
+            return new SimpleTermRewriteQuery(this, fieldName, qf);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SimpleTermRewriteQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SimpleTermRewriteQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SimpleTermRewriteQuery.cs
new file mode 100644
index 0000000..6502d6c
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SimpleTermRewriteQuery.cs
@@ -0,0 +1,64 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Search;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    internal class SimpleTermRewriteQuery : RewriteQuery<SimpleTerm>
+    {
+        public  SimpleTermRewriteQuery(
+            SimpleTerm srndQuery,
+            string fieldName,
+            BasicQueryFactory qf)
+            : base(srndQuery, fieldName, qf)
+        {
+        }
+
+        public override Search.Query Rewrite(IndexReader reader)
+        {
+            var luceneSubQueries = new List<Search.Query>();
+            srndQuery.VisitMatchingTerms(reader, fieldName, 
+                new SimpleTermRewriteMatchingTermVisitor(luceneSubQueries, qf));
+            return (luceneSubQueries.Count == 0) ? SrndQuery.TheEmptyLcnQuery
+                : (luceneSubQueries.Count == 1) ? luceneSubQueries.First()
+                : SrndBooleanQuery.MakeBooleanQuery(
+                /* luceneSubQueries all have default weight */
+                luceneSubQueries, BooleanClause.Occur.SHOULD); /* OR the subquery terms */
+        }
+
+        internal class SimpleTermRewriteMatchingTermVisitor : SimpleTerm.IMatchingTermVisitor
+        {
+            private readonly IList<Search.Query> luceneSubQueries;
+            private readonly BasicQueryFactory qf;
+
+            public SimpleTermRewriteMatchingTermVisitor(IList<Search.Query> luceneSubQueries, BasicQueryFactory qf)
+            {
+                this.luceneSubQueries = luceneSubQueries;
+                this.qf = qf;
+            }
+
+            public void VisitMatchingTerm(Term term)
+            {
+                luceneSubQueries.Add(qf.NewTermQuery(term));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SpanNearClauseFactory.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SpanNearClauseFactory.cs b/src/Lucene.Net.QueryParser/Surround/Query/SpanNearClauseFactory.cs
new file mode 100644
index 0000000..6cddb9c
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SpanNearClauseFactory.cs
@@ -0,0 +1,93 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Search.Spans;
+using Lucene.Net.Support;
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Factory for <see cref="SpanOrQuery"/>
+    /// </summary>
+    public class SpanNearClauseFactory
+    {
+        public SpanNearClauseFactory(IndexReader reader, string fieldName, BasicQueryFactory qf) {
+            this.reader = reader;
+            this.fieldName = fieldName;
+            this.weightBySpanQuery = new HashMap<SpanQuery, float>();
+            this.qf = qf;
+          }
+
+        private IndexReader reader;
+        private string fieldName;
+        private IDictionary<SpanQuery, float> weightBySpanQuery;
+        private BasicQueryFactory qf;
+
+        public virtual IndexReader IndexReader { get { return reader; } }
+
+        public virtual string FieldName { get { return fieldName; } }
+
+        public virtual BasicQueryFactory BasicQueryFactory { get { return qf; } }
+
+        public virtual int Count { get { return weightBySpanQuery.Count; } }
+
+        public virtual void Clear() { weightBySpanQuery.Clear(); }
+
+        protected virtual void AddSpanQueryWeighted(SpanQuery sq, float weight)
+        {
+            float w;
+            if (weightBySpanQuery.ContainsKey(sq))
+                w = weightBySpanQuery[sq] + weight;
+            else
+                w = weight;
+            weightBySpanQuery[sq] = w;
+        }
+
+        public virtual void AddTermWeighted(Term t, float weight)
+        {
+            SpanTermQuery stq = qf.NewSpanTermQuery(t);
+            /* CHECKME: wrap in Hashable...? */
+            AddSpanQueryWeighted(stq, weight);
+        }
+
+        public virtual void AddSpanQuery(Search.Query q)
+        {
+            if (q == SrndQuery.TheEmptyLcnQuery)
+                return;
+            if (!(q is SpanQuery))
+                throw new InvalidOperationException("Expected SpanQuery: " + q.ToString(FieldName));
+            AddSpanQueryWeighted((SpanQuery)q, q.Boost);
+        }
+
+        public SpanQuery MakeSpanClause()
+        {
+            List<SpanQuery> spanQueries = new List<SpanQuery>();
+            foreach (var wsq in weightBySpanQuery)
+            {
+                wsq.Key.Boost = wsq.Value;
+                spanQueries.Add(wsq.Key);
+            }
+            if (spanQueries.Count == 1)
+                return spanQueries[0];
+            else
+                return new SpanOrQuery(spanQueries.ToArray());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SrndBooleanQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SrndBooleanQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SrndBooleanQuery.cs
new file mode 100644
index 0000000..7a1a8b3
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SrndBooleanQuery.cs
@@ -0,0 +1,51 @@
+\ufeffusing Lucene.Net.Search;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    public static class SrndBooleanQuery
+    {
+        public static void AddQueriesToBoolean(
+            BooleanQuery bq,
+            IEnumerable<Search.Query> queries,
+            BooleanClause.Occur occur)
+        {
+            foreach (var query in queries)
+            {
+                bq.Add(query, occur);
+            }
+        }
+
+        public static Search.Query MakeBooleanQuery(
+            IEnumerable<Search.Query> queries,
+            BooleanClause.Occur occur)
+        {
+            if (queries.Count() <= 1)
+            {
+                throw new InvalidOperationException("Too few subqueries: " + queries.Count());
+            }
+            BooleanQuery bq = new BooleanQuery();
+            AddQueriesToBoolean(bq, queries, occur);
+            return bq;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SrndPrefixQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SrndPrefixQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SrndPrefixQuery.cs
new file mode 100644
index 0000000..4044b09
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SrndPrefixQuery.cs
@@ -0,0 +1,108 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Util;
+using System.Text;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Query that matches String prefixes
+    /// </summary>
+    public class SrndPrefixQuery : SimpleTerm
+    {
+        private readonly BytesRef prefixRef;
+        public SrndPrefixQuery(string prefix, bool quoted, char truncator)
+            : base(quoted)
+        {
+            this.prefix = prefix;
+            prefixRef = new BytesRef(prefix);
+            this.truncator = truncator;
+        }
+
+        private readonly string prefix;
+        public virtual string Prefix { get { return prefix; } }
+
+        private readonly char truncator;
+        public virtual char SuffixOperator { get { return truncator; } }
+
+        public virtual Term GetLucenePrefixTerm(string fieldName)
+        {
+            return new Term(fieldName, Prefix);
+        }
+
+        public override string ToStringUnquoted()
+        {
+            return Prefix;
+        }
+
+        protected override void SuffixToString(StringBuilder r)
+        {
+            r.Append(SuffixOperator);
+        }
+
+        public override void VisitMatchingTerms(IndexReader reader, string fieldName, IMatchingTermVisitor mtv)
+        {
+            /* inspired by PrefixQuery.rewrite(): */
+            Terms terms = MultiFields.GetTerms(reader, fieldName);
+            if (terms != null)
+            {
+                TermsEnum termsEnum = terms.Iterator(null);
+
+                bool skip = false;
+                TermsEnum.SeekStatus status = termsEnum.SeekCeil(new BytesRef(Prefix));
+                if (status == TermsEnum.SeekStatus.FOUND)
+                {
+                    mtv.VisitMatchingTerm(GetLucenePrefixTerm(fieldName));
+                }
+                else if (status == TermsEnum.SeekStatus.NOT_FOUND)
+                {
+                    if (StringHelper.StartsWith(termsEnum.Term(), prefixRef))
+                    {
+                        mtv.VisitMatchingTerm(new Term(fieldName, termsEnum.Term().Utf8ToString()));
+                    }
+                    else
+                    {
+                        skip = true;
+                    }
+                }
+                else
+                {
+                    // EOF
+                    skip = true;
+                }
+
+                if (!skip)
+                {
+                    while (true)
+                    {
+                        BytesRef text = termsEnum.Next();
+                        if (text != null && StringHelper.StartsWith(text, prefixRef))
+                        {
+                            mtv.VisitMatchingTerm(new Term(fieldName, text.Utf8ToString()));
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SrndQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SrndQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SrndQuery.cs
new file mode 100644
index 0000000..57b19cc
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SrndQuery.cs
@@ -0,0 +1,149 @@
+\ufeffusing Lucene.Net.Search;
+using Lucene.Net.Support;
+using System;
+using System.Text;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Lowest level base class for surround queries 
+    /// </summary>
+    public abstract class SrndQuery : ICloneable
+    {
+        //public SrndQuery() { }
+
+        private float weight = (float)1.0;
+        private bool weighted = false;
+
+        public virtual bool IsWeighted { get { return weighted; } }
+
+        public virtual float Weight 
+        { 
+            get { return weight; }
+            set
+            {
+                weight = value; /* as parsed from the query text */
+                weighted = true;
+            }
+        }
+
+        public virtual string WeightString { get { return Number.ToString(Weight); } }
+
+        public virtual string WeightOperator { get { return "^"; } }
+
+
+        protected virtual void WeightToString(StringBuilder r)
+        { 
+            /* append the weight part of a query */
+            if (IsWeighted)
+            {
+                r.Append(WeightOperator);
+                r.Append(WeightString);
+            }
+        }
+
+        public virtual Search.Query MakeLuceneQueryField(string fieldName, BasicQueryFactory qf)
+        {
+            Search.Query q = MakeLuceneQueryFieldNoBoost(fieldName, qf);
+            if (IsWeighted)
+            {
+                q.Boost=(Weight * q.Boost); /* weight may be at any level in a SrndQuery */
+            }
+            return q;
+        }
+
+        public abstract Search.Query MakeLuceneQueryFieldNoBoost(string fieldName, BasicQueryFactory qf);
+
+        /// <summary>
+        /// This method is used by <see cref="M:GetHashCode()"/> and <see cref="M:Equals(Object)"/>,
+        /// see LUCENE-2945.
+        /// </summary>
+        /// <returns></returns>
+        public abstract override string ToString();
+
+        public virtual bool IsFieldsSubQueryAcceptable { get { return true; } }
+
+        /// <summary> Shallow clone. Subclasses must override this if they
+        /// need to clone any members deeply,
+        /// </summary>
+        public virtual object Clone()
+        {
+            object clone = null;
+            try
+            {
+                clone = base.MemberwiseClone();
+            }
+            catch (Exception e)
+            {
+                throw new SystemException(e.Message, e); // shouldn't happen
+            }
+            return clone;
+        }
+
+        /// <summary>
+        /// For subclasses of <see cref="SrndQuery"/> within the package
+        /// {@link org.apache.lucene.queryparser.surround.query}
+        /// it is not necessary to override this method, <see cref="M:ToString()"/>
+        /// </summary>
+        public override int GetHashCode()
+        {
+            return GetType().GetHashCode() ^ ToString().GetHashCode();
+        }
+
+        /// <summary>
+        /// For subclasses of <see cref="SrndQuery"/> within the package
+        /// {@link org.apache.lucene.queryparser.surround.query}
+        /// it is not necessary to override this method,
+        /// @see #toString()
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public override bool Equals(object obj)
+        {
+            if (obj == null)
+                return false;
+            if (!GetType().Equals(obj.GetType()))
+                return false;
+            return ToString().Equals(obj.ToString());
+        }
+
+        /// <summary> An empty Lucene query  </summary>
+        public readonly static Search.Query TheEmptyLcnQuery = new EmptyLcnQuery(); /* no changes allowed */ 
+  
+        internal sealed class EmptyLcnQuery : BooleanQuery
+        {
+            public override float Boost
+            {
+                get { return base.Boost; }
+                set { throw new NotSupportedException(); }
+            }
+
+            public override void Add(BooleanClause clause)
+            {
+                throw new NotSupportedException();
+            }
+
+            public override void Add(Search.Query query, BooleanClause.Occur occur)
+            {
+                throw new NotSupportedException();
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SrndTermQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SrndTermQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SrndTermQuery.cs
new file mode 100644
index 0000000..45885a1
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SrndTermQuery.cs
@@ -0,0 +1,63 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Util;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Simple single-term clause
+    /// </summary>
+    public class SrndTermQuery : SimpleTerm
+    {
+        public SrndTermQuery(string termText, bool quoted)
+            : base(quoted)
+        {
+            this.termText = termText;
+        }
+
+        private readonly string termText;
+        public virtual string TermText { get { return termText; } }
+
+        public virtual Term GetLuceneTerm(string fieldName)
+        {
+            return new Term(fieldName, TermText);
+        }
+
+        public override string ToStringUnquoted()
+        {
+            return TermText;
+        }
+
+        public override void VisitMatchingTerms(IndexReader reader, string fieldName, IMatchingTermVisitor mtv)
+        {
+            /* check term presence in index here for symmetry with other SimpleTerm's */
+            Terms terms = MultiFields.GetTerms(reader, fieldName);
+            if (terms != null)
+            {
+                TermsEnum termsEnum = terms.Iterator(null);
+
+                TermsEnum.SeekStatus status = termsEnum.SeekCeil(new BytesRef(TermText));
+                if (status == TermsEnum.SeekStatus.FOUND)
+                {
+                    mtv.VisitMatchingTerm(GetLuceneTerm(fieldName));
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/SrndTruncQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/SrndTruncQuery.cs b/src/Lucene.Net.QueryParser/Surround/Query/SrndTruncQuery.cs
new file mode 100644
index 0000000..5ed9ff3
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/SrndTruncQuery.cs
@@ -0,0 +1,139 @@
+\ufeffusing Lucene.Net.Index;
+using Lucene.Net.Util;
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    public class SrndTruncQuery : SimpleTerm
+    {
+        public SrndTruncQuery(string truncated, char unlimited, char mask)
+            : base(false) /* not quoted */
+        {
+            this.truncated = truncated;
+            this.unlimited = unlimited;
+            this.mask = mask;
+            TruncatedToPrefixAndPattern();
+        }
+
+        private readonly string truncated;
+        private readonly char unlimited;
+        private readonly char mask;
+
+        private string prefix;
+        private BytesRef prefixRef;
+        private Regex pattern;
+
+        public virtual string Truncated { get { return truncated; } }
+
+        public override string ToStringUnquoted()
+        {
+            return Truncated;
+        }
+
+        protected virtual bool MatchingChar(char c)
+        {
+            return (c != unlimited) && (c != mask);
+        }
+
+        protected virtual void AppendRegExpForChar(char c, StringBuilder re)
+        {
+            if (c == unlimited)
+                re.Append(".*");
+            else if (c == mask)
+                re.Append(".");
+            else
+                re.Append(c);
+        }
+
+        protected virtual void TruncatedToPrefixAndPattern()
+        {
+            int i = 0;
+            while ((i < truncated.Length) && MatchingChar(truncated[i]))
+            {
+                i++;
+            }
+            prefix = truncated.Substring(0, i);
+            prefixRef = new BytesRef(prefix);
+
+            StringBuilder re = new StringBuilder();
+            while (i < truncated.Length)
+            {
+                AppendRegExpForChar(truncated[i], re);
+                i++;
+            }
+            pattern = new Regex(re.ToString(), RegexOptions.Compiled);
+        }
+
+        // TODO: Finish implementation
+        public override void VisitMatchingTerms(IndexReader reader, string fieldName, SimpleTerm.IMatchingTermVisitor mtv)
+        {
+            throw new NotImplementedException("Need to translate this from Java's whacky RegEx syntax");
+            //int prefixLength = prefix.Length;
+            //Terms terms = MultiFields.GetTerms(reader, fieldName);
+            //if (terms != null)
+            //{
+            //    MatchCollection matcher = pattern.Matches("");
+            //    try
+            //    {
+            //        TermsEnum termsEnum = terms.Iterator(null);
+
+            //        TermsEnum.SeekStatus status = termsEnum.SeekCeil(prefixRef);
+            //        BytesRef text;
+            //        if (status == TermsEnum.SeekStatus.FOUND)
+            //        {
+            //            text = prefixRef;
+            //        }
+            //        else if (status == TermsEnum.SeekStatus.NOT_FOUND)
+            //        {
+            //            text = termsEnum.Term();
+            //        }
+            //        else
+            //        {
+            //            text = null;
+            //        }
+
+            //        while (text != null)
+            //        {
+            //            if (text != null && StringHelper.StartsWith(text, prefixRef))
+            //            {
+            //                string textString = text.Utf8ToString();
+            //                matcher.Reset(textString.Substring(prefixLength));
+            //                if (matcher.Success)
+            //                {
+            //                    mtv.VisitMatchingTerm(new Term(fieldName, textString));
+            //                }
+            //            }
+            //            else
+            //            {
+            //                break;
+            //            }
+            //            text = termsEnum.Next();
+            //        }
+            //    }
+            //    finally
+            //    {
+            //        matcher.Reset();
+            //    }
+            //}
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.QueryParser/Surround/Query/TooManyBasicQueries.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.QueryParser/Surround/Query/TooManyBasicQueries.cs b/src/Lucene.Net.QueryParser/Surround/Query/TooManyBasicQueries.cs
new file mode 100644
index 0000000..27f313c
--- /dev/null
+++ b/src/Lucene.Net.QueryParser/Surround/Query/TooManyBasicQueries.cs
@@ -0,0 +1,30 @@
+\ufeffnamespace Lucene.Net.QueryParser.Surround.Query
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Exception thrown when <see cref="BasicQueryFactory"/> would exceed the limit
+    /// of query clauses.
+    /// </summary>
+    public class TooManyBasicQueries : System.IO.IOException
+    {
+        public TooManyBasicQueries(int maxBasicQueries)
+            : base("Exceeded maximum of " + maxBasicQueries + " basic queries.")
+        { }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.Tests.QueryParser/Analyzing/TestAnalyzingQueryParser.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.QueryParser/Analyzing/TestAnalyzingQueryParser.cs b/src/Lucene.Net.Tests.QueryParser/Analyzing/TestAnalyzingQueryParser.cs
new file mode 100644
index 0000000..10756cf
--- /dev/null
+++ b/src/Lucene.Net.Tests.QueryParser/Analyzing/TestAnalyzingQueryParser.cs
@@ -0,0 +1,341 @@
+\ufeffusing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Lucene.Net.Analysis;
+using Lucene.Net.Analysis.Tokenattributes;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.QueryParser.Classic;
+using Lucene.Net.Search;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+using NUnit.Framework;
+
+namespace Lucene.Net.QueryParser.Analyzing
+{
+    /*
+     * 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.
+     */
+
+    [TestFixture]
+    public class TestAnalyzingQueryParser : LuceneTestCase
+    {
+        private readonly static string FIELD = "field";
+
+        private Analyzer a;
+
+        private string[] wildcardInput;
+        private string[] wildcardExpected;
+        private string[] prefixInput;
+        private string[] prefixExpected;
+        private string[] rangeInput;
+        private string[] rangeExpected;
+        private string[] fuzzyInput;
+        private string[] fuzzyExpected;
+
+        private IDictionary<string, string> wildcardEscapeHits = new Dictionary<string, string>();
+        private IDictionary<string, string> wildcardEscapeMisses = new Dictionary<string, string>();
+
+        public override void SetUp()
+        {
+            base.SetUp();
+            wildcardInput = new string[] { "*bersetzung �ber*ung",
+                "M�tley Cr\u00fce M�tl?* Cr�?", "Ren�e Zellweger Ren?? Zellw?ger" };
+            wildcardExpected = new string[] { "*bersetzung uber*ung", "motley crue motl?* cru?",
+                "renee zellweger ren?? zellw?ger" };
+
+            prefixInput = new string[] { "�bersetzung �bersetz*",
+                "M�tley Cr�e M�tl* cr�*", "Ren�? Zellw*" };
+            prefixExpected = new string[] { "ubersetzung ubersetz*", "motley crue motl* cru*",
+                "rene? zellw*" };
+
+            rangeInput = new string[] { "[aa TO bb]", "{Ana�s TO Zo�}" };
+            rangeExpected = new string[] { "[aa TO bb]", "{anais TO zoe}" };
+
+            fuzzyInput = new string[] { "�bersetzung �bersetzung~0.9",
+                "M�tley Cr�e M�tley~0.75 Cr�e~0.5",
+                "Ren�e Zellweger Ren�e~0.9 Zellweger~" };
+            fuzzyExpected = new string[] { "ubersetzung ubersetzung~1",
+                "motley crue motley~1 crue~2", "renee zellweger renee~0 zellweger~2" };
+
+            wildcardEscapeHits["m�*tley"] = "moatley";
+
+            // need to have at least one genuine wildcard to trigger the wildcard analysis
+            // hence the * before the y
+            wildcardEscapeHits["m�\\*tl*y"] = "mo*tley";
+
+            // escaped backslash then true wildcard
+            wildcardEscapeHits["m�\\\\*tley"] = "mo\\atley";
+
+            // escaped wildcard then true wildcard
+            wildcardEscapeHits["m�\\??ley"] = "mo?tley";
+
+            // the first is an escaped * which should yield a miss
+            wildcardEscapeMisses["m�\\*tl*y"] = "moatley";
+
+            a = new ASCIIAnalyzer();
+        }
+
+        [Test]
+        public void TestSingleChunkExceptions()
+        {
+            bool ex = false;
+            string termStr = "the*tre";
+
+            Analyzer stopsAnalyzer = new MockAnalyzer
+                (Random(), MockTokenizer.WHITESPACE, true, MockTokenFilter.ENGLISH_STOPSET);
+            try
+            {
+                string q = ParseWithAnalyzingQueryParser(termStr, stopsAnalyzer, true);
+            }
+            catch (ParseException e)
+            {
+                if (e.Message.Contains("returned nothing"))
+                {
+                    ex = true;
+                }
+            }
+            assertEquals("Should have returned nothing", true, ex);
+            ex = false;
+
+            AnalyzingQueryParser qp = new AnalyzingQueryParser(TEST_VERSION_CURRENT, FIELD, a);
+            try
+            {
+                qp.AnalyzeSingleChunk(FIELD, "", "not a single chunk");
+            }
+            catch (ParseException e)
+            {
+                if (e.Message.Contains("multiple terms"))
+                {
+                    ex = true;
+                }
+            }
+            assertEquals("Should have produced multiple terms", true, ex);
+        }
+
+        [Test]
+        public void TestWildcardAlone()
+        {
+            //seems like crazy edge case, but can be useful in concordance 
+            bool pex = false;
+            try
+            {
+                Query q = GetAnalyzedQuery("*", a, false);
+            }
+            catch (ParseException e)
+            {
+                pex = true;
+            }
+            assertEquals("Wildcard alone with allowWildcard=false", true, pex);
+
+            pex = false;
+            try
+            {
+                String qString = ParseWithAnalyzingQueryParser("*", a, true);
+                assertEquals("Every word", "*", qString);
+            }
+            catch (ParseException e)
+            {
+                pex = true;
+            }
+
+            assertEquals("Wildcard alone with allowWildcard=true", false, pex);
+        }
+
+        [Test]
+        public void TestWildCardEscapes()
+        {
+            foreach (var entry in wildcardEscapeHits)
+            {
+                Query q = GetAnalyzedQuery(entry.Key, a, false);
+                assertEquals("WildcardEscapeHits: " + entry.Key, true, IsAHit(q, entry.Value, a));
+            }
+            foreach (var entry in wildcardEscapeMisses)
+            {
+                Query q = GetAnalyzedQuery(entry.Key, a, false);
+                assertEquals("WildcardEscapeMisses: " + entry.Key, false, IsAHit(q, entry.Value, a));
+            }
+        }
+
+        [Test]
+        public void TestWildCardQueryNoLeadingAllowed()
+        {
+            bool ex = false;
+            try
+            {
+                string q = ParseWithAnalyzingQueryParser(wildcardInput[0], a, false);
+
+            }
+            catch (ParseException e)
+            {
+                ex = true;
+            }
+            assertEquals("Testing initial wildcard not allowed",
+                true, ex);
+        }
+
+        [Test]
+        public void TestWildCardQuery()
+        {
+            for (int i = 0; i < wildcardInput.Length; i++)
+            {
+                assertEquals("Testing wildcards with analyzer " + a.GetType() + ", input string: "
+                    + wildcardInput[i], wildcardExpected[i], ParseWithAnalyzingQueryParser(wildcardInput[i], a, true));
+            }
+        }
+
+        [Test]
+        public void TestPrefixQuery()
+        {
+            for (int i = 0; i < prefixInput.Length; i++)
+            {
+                assertEquals("Testing prefixes with analyzer " + a.GetType() + ", input string: "
+                    + prefixInput[i], prefixExpected[i], ParseWithAnalyzingQueryParser(prefixInput[i], a, false));
+            }
+        }
+
+        [Test]
+        public void TestRangeQuery()
+        {
+            for (int i = 0; i < rangeInput.Length; i++)
+            {
+                assertEquals("Testing ranges with analyzer " + a.GetType() + ", input string: "
+                    + rangeInput[i], rangeExpected[i], ParseWithAnalyzingQueryParser(rangeInput[i], a, false));
+            }
+        }
+
+        [Test]
+        public void TestFuzzyQuery()
+        {
+            for (int i = 0; i < fuzzyInput.Length; i++)
+            {
+                assertEquals("Testing fuzzys with analyzer " + a.GetType() + ", input string: "
+                  + fuzzyInput[i], fuzzyExpected[i], ParseWithAnalyzingQueryParser(fuzzyInput[i], a, false));
+            }
+        }
+
+
+        private string ParseWithAnalyzingQueryParser(string s, Analyzer a, bool allowLeadingWildcard)
+        {
+            Query q = GetAnalyzedQuery(s, a, allowLeadingWildcard);
+            return q.ToString(FIELD);
+        }
+
+        private Query GetAnalyzedQuery(string s, Analyzer a, bool allowLeadingWildcard)
+        {
+            AnalyzingQueryParser qp = new AnalyzingQueryParser(TEST_VERSION_CURRENT, FIELD, a);
+            qp.AllowLeadingWildcard = allowLeadingWildcard;
+            Query q = qp.Parse(s);
+            return q;
+        }
+
+        internal sealed class FoldingFilter : TokenFilter
+        {
+            private readonly ICharTermAttribute termAtt;
+
+            public FoldingFilter(TokenStream input)
+                : base(input)
+            {
+                termAtt = AddAttribute<ICharTermAttribute>();
+            }
+
+            public sealed override bool IncrementToken()
+            {
+                if (input.IncrementToken())
+                {
+                    char[] term = termAtt.Buffer();
+                    for (int i = 0; i < term.Length; i++)
+                        switch (term[i])
+                        {
+                            case '�':
+                                term[i] = 'u';
+                                break;
+                            case '�':
+                                term[i] = 'o';
+                                break;
+                            case '�':
+                                term[i] = 'e';
+                                break;
+                            case '�':
+                                term[i] = 'i';
+                                break;
+                        }
+                    return true;
+                }
+                else
+                {
+                    return false;
+                }
+            }
+        }
+
+        internal sealed class ASCIIAnalyzer : Analyzer
+        {
+
+            public override TokenStreamComponents CreateComponents(string fieldName, System.IO.TextReader reader)
+            {
+                Tokenizer result = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
+                return new TokenStreamComponents(result, new FoldingFilter(result));
+            }
+        }
+
+        // LUCENE-4176
+        [Test]
+        public void TestByteTerms()
+        {
+            string s = "\u0e40\u0e02";
+            Analyzer analyzer = new MockBytesAnalyzer();
+            Classic.QueryParser qp = new AnalyzingQueryParser(TEST_VERSION_CURRENT, FIELD, analyzer);
+            Query q = qp.Parse("[\u0e40\u0e02 TO \u0e40\u0e02]");
+            assertEquals(true, IsAHit(q, s, analyzer));
+        }
+
+        private bool IsAHit(Query q, string content, Analyzer analyzer)
+        {
+            int hits;
+            using (Directory ramDir = NewDirectory())
+            {
+                using (RandomIndexWriter writer = new RandomIndexWriter(Random(), ramDir, analyzer))
+                {
+                    Document doc = new Document();
+                    FieldType fieldType = new FieldType();
+                    fieldType.Indexed = (true);
+                    fieldType.Tokenized = (true);
+                    fieldType.Stored = (true);
+                    Field field = new Field(FIELD, content, fieldType);
+                    doc.Add(field);
+                    writer.AddDocument(doc);
+                }
+                using (DirectoryReader ir = DirectoryReader.Open(ramDir))
+                {
+                    IndexSearcher @is = new IndexSearcher(ir);
+
+                    hits = @is.Search(q, 10).TotalHits;
+                }
+            }
+            if (hits == 1)
+            {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/679ad24c/src/Lucene.Net.Tests.QueryParser/Classic/TestMultiAnalyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.QueryParser/Classic/TestMultiAnalyzer.cs b/src/Lucene.Net.Tests.QueryParser/Classic/TestMultiAnalyzer.cs
new file mode 100644
index 0000000..350f181
--- /dev/null
+++ b/src/Lucene.Net.Tests.QueryParser/Classic/TestMultiAnalyzer.cs
@@ -0,0 +1,278 @@
+\ufeffusing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Lucene.Net.Analysis;
+using Lucene.Net.Analysis.Tokenattributes;
+using Lucene.Net.Search;
+using Lucene.Net.Util;
+
+namespace Lucene.Net.QueryParser.Classic
+{
+    [TestFixture]
+    public class TestMultiAnalyzer_ : BaseTokenStreamTestCase
+    {
+
+        private static int multiToken = 0;
+
+        [Test]
+        public void TestMultiAnalyzer()
+        {
+
+            QueryParser qp = new QueryParser(TEST_VERSION_CURRENT, "", new MultiAnalyzer());
+
+            // trivial, no multiple tokens:
+            assertEquals("foo", qp.Parse("foo").toString());
+            assertEquals("foo", qp.Parse("\"foo\"").toString());
+            assertEquals("foo foobar", qp.Parse("foo foobar").toString());
+            assertEquals("\"foo foobar\"", qp.Parse("\"foo foobar\"").toString());
+            assertEquals("\"foo foobar blah\"", qp.Parse("\"foo foobar blah\"").toString());
+
+            // two tokens at the same position:
+            assertEquals("(multi multi2) foo", qp.Parse("multi foo").toString());
+            assertEquals("foo (multi multi2)", qp.Parse("foo multi").toString());
+            assertEquals("(multi multi2) (multi multi2)", qp.Parse("multi multi").toString());
+            assertEquals("+(foo (multi multi2)) +(bar (multi multi2))",
+                qp.Parse("+(foo multi) +(bar multi)").toString());
+            assertEquals("+(foo (multi multi2)) field:\"bar (multi multi2)\"",
+                qp.Parse("+(foo multi) field:\"bar multi\"").toString());
+
+            // phrases:
+            assertEquals("\"(multi multi2) foo\"", qp.Parse("\"multi foo\"").toString());
+            assertEquals("\"foo (multi multi2)\"", qp.Parse("\"foo multi\"").toString());
+            assertEquals("\"foo (multi multi2) foobar (multi multi2)\"",
+                qp.Parse("\"foo multi foobar multi\"").toString());
+
+            // fields:
+            assertEquals("(field:multi field:multi2) field:foo", qp.Parse("field:multi field:foo").toString());
+            assertEquals("field:\"(multi multi2) foo\"", qp.Parse("field:\"multi foo\"").toString());
+
+            // three tokens at one position:
+            assertEquals("triplemulti multi3 multi2", qp.Parse("triplemulti").toString());
+            assertEquals("foo (triplemulti multi3 multi2) foobar",
+                qp.Parse("foo triplemulti foobar").toString());
+
+            // phrase with non-default slop:
+            assertEquals("\"(multi multi2) foo\"~10", qp.Parse("\"multi foo\"~10").toString());
+
+            // phrase with non-default boost:
+            assertEquals("\"(multi multi2) foo\"^2.0", qp.Parse("\"multi foo\"^2").toString());
+
+            // phrase after changing default slop
+            qp.PhraseSlop=(99);
+            assertEquals("\"(multi multi2) foo\"~99 bar",
+                         qp.Parse("\"multi foo\" bar").toString());
+            assertEquals("\"(multi multi2) foo\"~99 \"foo bar\"~2",
+                         qp.Parse("\"multi foo\" \"foo bar\"~2").toString());
+            qp.PhraseSlop=(0);
+
+            // non-default operator:
+            qp.DefaultOperator=(QueryParserBase.AND_OPERATOR);
+            assertEquals("+(multi multi2) +foo", qp.Parse("multi foo").toString());
+
+        }
+
+        [Test]
+        public void TestMultiAnalyzerWithSubclassOfQueryParser()
+        {
+
+            DumbQueryParser qp = new DumbQueryParser("", new MultiAnalyzer());
+            qp.PhraseSlop = (99); // modified default slop
+
+            // direct call to (super's) getFieldQuery to demonstrate differnce
+            // between phrase and multiphrase with modified default slop
+            assertEquals("\"foo bar\"~99",
+                         qp.GetSuperFieldQuery("", "foo bar", true).toString());
+            assertEquals("\"(multi multi2) bar\"~99",
+                         qp.GetSuperFieldQuery("", "multi bar", true).toString());
+
+
+            // ask sublcass to parse phrase with modified default slop
+            assertEquals("\"(multi multi2) foo\"~99 bar",
+                         qp.Parse("\"multi foo\" bar").toString());
+
+        }
+
+        [Test]
+        public void TestPosIncrementAnalyzer()
+        {
+            QueryParser qp = new QueryParser(LuceneVersion.LUCENE_40, "", new PosIncrementAnalyzer());
+            assertEquals("quick brown", qp.Parse("the quick brown").toString());
+            assertEquals("quick brown fox", qp.Parse("the quick brown fox").toString());
+        }
+
+        /// <summary>
+        /// Expands "multi" to "multi" and "multi2", both at the same position,
+        /// and expands "triplemulti" to "triplemulti", "multi3", and "multi2".  
+        /// </summary>
+        private class MultiAnalyzer : Analyzer
+        {
+            public override TokenStreamComponents CreateComponents(string fieldName, System.IO.TextReader reader)
+            {
+                Tokenizer result = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
+                return new TokenStreamComponents(result, new TestFilter(result));
+            }
+        }
+
+        private sealed class TestFilter : TokenFilter
+        {
+
+            private string prevType;
+            private int prevStartOffset;
+            private int prevEndOffset;
+
+            private readonly ICharTermAttribute termAtt;
+            private readonly IPositionIncrementAttribute posIncrAtt;
+            private readonly IOffsetAttribute offsetAtt;
+            private readonly ITypeAttribute typeAtt;
+
+            public TestFilter(TokenStream @in)
+                : base(@in)
+            {
+                termAtt = AddAttribute<ICharTermAttribute>();
+                posIncrAtt = AddAttribute<IPositionIncrementAttribute>();
+                offsetAtt = AddAttribute<IOffsetAttribute>();
+                typeAtt = AddAttribute<ITypeAttribute>();
+            }
+
+            public override sealed bool IncrementToken()
+            {
+                if (multiToken > 0)
+                {
+                    termAtt.SetEmpty().Append("multi" + (multiToken + 1));
+                    offsetAtt.SetOffset(prevStartOffset, prevEndOffset);
+                    typeAtt.Type = (prevType);
+                    posIncrAtt.PositionIncrement = (0);
+                    multiToken--;
+                    return true;
+                }
+                else
+                {
+                    bool next = input.IncrementToken();
+                    if (!next)
+                    {
+                        return false;
+                    }
+                    prevType = typeAtt.Type;
+                    prevStartOffset = offsetAtt.StartOffset();
+                    prevEndOffset = offsetAtt.EndOffset();
+                    string text = termAtt.toString();
+                    if (text.equals("triplemulti"))
+                    {
+                        multiToken = 2;
+                        return true;
+                    }
+                    else if (text.equals("multi"))
+                    {
+                        multiToken = 1;
+                        return true;
+                    }
+                    else
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            public override void Reset()
+            {
+                base.Reset();
+                this.prevType = null;
+                this.prevStartOffset = 0;
+                this.prevEndOffset = 0;
+            }
+        }
+
+        /// <summary>
+        /// Analyzes "the quick brown" as: quick(incr=2) brown(incr=1).
+        /// Does not work correctly for input other than "the quick brown ...".
+        /// </summary>
+        private class PosIncrementAnalyzer : Analyzer
+        {
+            public override TokenStreamComponents CreateComponents(string fieldName, System.IO.TextReader reader)
+            {
+                Tokenizer result = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
+                return new TokenStreamComponents(result, new TestPosIncrementFilter(result));
+            }
+        }
+
+        private sealed class TestPosIncrementFilter : TokenFilter
+        {
+            ICharTermAttribute termAtt;
+            IPositionIncrementAttribute posIncrAtt;
+
+            public TestPosIncrementFilter(TokenStream @in)
+                : base(@in)
+            {
+                termAtt = AddAttribute<ICharTermAttribute>();
+                posIncrAtt = AddAttribute<IPositionIncrementAttribute>();
+            }
+
+            public override sealed bool IncrementToken()
+            {
+                while (input.IncrementToken())
+                {
+                    if (termAtt.toString().equals("the"))
+                    {
+                        // stopword, do nothing
+                    }
+                    else if (termAtt.toString().equals("quick"))
+                    {
+                        posIncrAtt.PositionIncrement = (2);
+                        return true;
+                    }
+                    else
+                    {
+                        posIncrAtt.PositionIncrement = (1);
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// a very simple subclass of QueryParser
+        /// </summary>
+        private sealed class DumbQueryParser : QueryParser
+        {
+            public DumbQueryParser(string f, Analyzer a)
+                : base(TEST_VERSION_CURRENT, f, a)
+            {
+            }
+
+            // expose super's version 
+            public Query GetSuperFieldQuery(string f, string t, bool quoted)
+            {
+                return base.GetFieldQuery(f, t, quoted);
+            }
+
+            // wrap super's version
+            protected internal override Query GetFieldQuery(string field, string queryText, bool quoted)
+            {
+                return new DumbQueryWrapper(GetSuperFieldQuery(field, queryText, quoted));
+            }
+        }
+
+        /// <summary>
+        /// A very simple wrapper to prevent instanceof checks but uses
+        /// the toString of the query it wraps.
+        /// </summary>
+        private sealed class DumbQueryWrapper : Query
+        {
+            private Query q;
+            public DumbQueryWrapper(Query q)
+            {
+                this.q = q;
+            }
+
+            public override string ToString(string field)
+            {
+                return q.ToString(field);
+            }
+        }
+
+    }
+}