1   package org.apache.lucene.queryParser;
2   
3   /**
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Vector;
24  
25  import org.apache.lucene.analysis.Analyzer;
26  import org.apache.lucene.search.BooleanClause;
27  import org.apache.lucene.search.BooleanQuery;
28  import org.apache.lucene.search.MultiPhraseQuery;
29  import org.apache.lucene.search.PhraseQuery;
30  import org.apache.lucene.search.Query;
31  
32  /**
33   * A QueryParser which constructs queries to search multiple fields.
34   *
35   *
36   * @version $Revision: 692921 $
37   */
38  public class MultiFieldQueryParser extends QueryParser
39  {
40    protected String[] fields;
41    protected Map      boosts;
42  
43    /**
44     * Creates a MultiFieldQueryParser. 
45     * Allows passing of a map with term to Boost, and the boost to apply to each term.
46     *
47     * <p>It will, when parse(String query)
48     * is called, construct a query like this (assuming the query consists of
49     * two terms and you specify the two fields <code>title</code> and <code>body</code>):</p>
50     * 
51     * <code>
52     * (title:term1 body:term1) (title:term2 body:term2)
53     * </code>
54     *
55     * <p>When setDefaultOperator(AND_OPERATOR) is set, the result will be:</p>
56     *  
57     * <code>
58     * +(title:term1 body:term1) +(title:term2 body:term2)
59     * </code>
60     * 
61     * <p>When you pass a boost (title=>5 body=>10) you can get </p>
62     * 
63     * <code>
64     * +(title:term1^5.0 body:term1^10.0) +(title:term2^5.0 body:term2^10.0)
65     * </code>
66     *
67     * <p>In other words, all the query's terms must appear, but it doesn't matter in
68     * what fields they appear.</p>
69     */
70    public MultiFieldQueryParser(String[] fields, Analyzer analyzer, Map boosts) {
71      this(fields,analyzer);
72      this.boosts = boosts;
73    }
74    
75    /**
76     * Creates a MultiFieldQueryParser.
77     *
78     * <p>It will, when parse(String query)
79     * is called, construct a query like this (assuming the query consists of
80     * two terms and you specify the two fields <code>title</code> and <code>body</code>):</p>
81     * 
82     * <code>
83     * (title:term1 body:term1) (title:term2 body:term2)
84     * </code>
85     *
86     * <p>When setDefaultOperator(AND_OPERATOR) is set, the result will be:</p>
87     *  
88     * <code>
89     * +(title:term1 body:term1) +(title:term2 body:term2)
90     * </code>
91     * 
92     * <p>In other words, all the query's terms must appear, but it doesn't matter in
93     * what fields they appear.</p>
94     */
95    public MultiFieldQueryParser(String[] fields, Analyzer analyzer) {
96      super(null, analyzer);
97      this.fields = fields;
98    }
99    
100   protected Query getFieldQuery(String field, String queryText, int slop) throws ParseException {
101     if (field == null) {
102       List clauses = new ArrayList();
103       for (int i = 0; i < fields.length; i++) {
104         Query q = super.getFieldQuery(fields[i], queryText);
105         if (q != null) {
106           //If the user passes a map of boosts
107           if (boosts != null) {
108             //Get the boost from the map and apply them
109             Float boost = (Float)boosts.get(fields[i]);
110             if (boost != null) {
111               q.setBoost(boost.floatValue());
112             }
113           }
114           applySlop(q,slop);
115           clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
116         }
117       }
118       if (clauses.size() == 0)  // happens for stopwords
119         return null;
120       return getBooleanQuery(clauses, true);
121     }
122     Query q = super.getFieldQuery(field, queryText);
123     applySlop(q,slop);
124     return q;
125   }
126 
127   private void applySlop(Query q, int slop) {
128     if (q instanceof PhraseQuery) {
129       ((PhraseQuery) q).setSlop(slop);
130     } else if (q instanceof MultiPhraseQuery) {
131       ((MultiPhraseQuery) q).setSlop(slop);
132     }
133   }
134   
135 
136   protected Query getFieldQuery(String field, String queryText) throws ParseException {
137     return getFieldQuery(field, queryText, 0);
138   }
139 
140 
141   protected Query getFuzzyQuery(String field, String termStr, float minSimilarity) throws ParseException
142   {
143     if (field == null) {
144       List clauses = new ArrayList();
145       for (int i = 0; i < fields.length; i++) {
146         clauses.add(new BooleanClause(getFuzzyQuery(fields[i], termStr, minSimilarity),
147             BooleanClause.Occur.SHOULD));
148       }
149       return getBooleanQuery(clauses, true);
150     }
151     return super.getFuzzyQuery(field, termStr, minSimilarity);
152   }
153 
154   protected Query getPrefixQuery(String field, String termStr) throws ParseException
155   {
156     if (field == null) {
157       List clauses = new ArrayList();
158       for (int i = 0; i < fields.length; i++) {
159         clauses.add(new BooleanClause(getPrefixQuery(fields[i], termStr),
160             BooleanClause.Occur.SHOULD));
161       }
162       return getBooleanQuery(clauses, true);
163     }
164     return super.getPrefixQuery(field, termStr);
165   }
166 
167   protected Query getWildcardQuery(String field, String termStr) throws ParseException {
168     if (field == null) {
169       List clauses = new ArrayList();
170       for (int i = 0; i < fields.length; i++) {
171         clauses.add(new BooleanClause(getWildcardQuery(fields[i], termStr),
172             BooleanClause.Occur.SHOULD));
173       }
174       return getBooleanQuery(clauses, true);
175     }
176     return super.getWildcardQuery(field, termStr);
177   }
178 
179  
180   protected Query getRangeQuery(String field, String part1, String part2, boolean inclusive) throws ParseException {
181     if (field == null) {
182       List clauses = new ArrayList();
183       for (int i = 0; i < fields.length; i++) {
184         clauses.add(new BooleanClause(getRangeQuery(fields[i], part1, part2, inclusive),
185             BooleanClause.Occur.SHOULD));
186       }
187       return getBooleanQuery(clauses, true);
188     }
189     return super.getRangeQuery(field, part1, part2, inclusive);
190   }
191 
192   /**
193    * Parses a query which searches on the fields specified.
194    * <p>
195    * If x fields are specified, this effectively constructs:
196    * <pre>
197    * <code>
198    * (field1:query1) (field2:query2) (field3:query3)...(fieldx:queryx)
199    * </code>
200    * </pre>
201    * @param queries Queries strings to parse
202    * @param fields Fields to search on
203    * @param analyzer Analyzer to use
204    * @throws ParseException if query parsing fails
205    * @throws IllegalArgumentException if the length of the queries array differs
206    *  from the length of the fields array
207    */
208   public static Query parse(String[] queries, String[] fields,
209       Analyzer analyzer) throws ParseException
210   {
211     if (queries.length != fields.length)
212       throw new IllegalArgumentException("queries.length != fields.length");
213     BooleanQuery bQuery = new BooleanQuery();
214     for (int i = 0; i < fields.length; i++)
215     {
216       QueryParser qp = new QueryParser(fields[i], analyzer);
217       Query q = qp.parse(queries[i]);
218       if (q!=null && // q never null, just being defensive
219           (!(q instanceof BooleanQuery) || ((BooleanQuery)q).getClauses().length>0)) {
220         bQuery.add(q, BooleanClause.Occur.SHOULD);
221       }
222     }
223     return bQuery;
224   }
225 
226   /**
227    * Parses a query, searching on the fields specified.
228    * Use this if you need to specify certain fields as required,
229    * and others as prohibited.
230    * <p><pre>
231    * Usage:
232    * <code>
233    * String[] fields = {"filename", "contents", "description"};
234    * BooleanClause.Occur[] flags = {BooleanClause.Occur.SHOULD,
235    *                BooleanClause.Occur.MUST,
236    *                BooleanClause.Occur.MUST_NOT};
237    * MultiFieldQueryParser.parse("query", fields, flags, analyzer);
238    * </code>
239    * </pre>
240    *<p>
241    * The code above would construct a query:
242    * <pre>
243    * <code>
244    * (filename:query) +(contents:query) -(description:query)
245    * </code>
246    * </pre>
247    *
248    * @param query Query string to parse
249    * @param fields Fields to search on
250    * @param flags Flags describing the fields
251    * @param analyzer Analyzer to use
252    * @throws ParseException if query parsing fails
253    * @throws IllegalArgumentException if the length of the fields array differs
254    *  from the length of the flags array
255    */
256   public static Query parse(String query, String[] fields,
257       BooleanClause.Occur[] flags, Analyzer analyzer) throws ParseException {
258     if (fields.length != flags.length)
259       throw new IllegalArgumentException("fields.length != flags.length");
260     BooleanQuery bQuery = new BooleanQuery();
261     for (int i = 0; i < fields.length; i++) {
262       QueryParser qp = new QueryParser(fields[i], analyzer);
263       Query q = qp.parse(query);
264       if (q!=null && // q never null, just being defensive 
265           (!(q instanceof BooleanQuery) || ((BooleanQuery)q).getClauses().length>0)) {
266         bQuery.add(q, flags[i]);
267       }
268     }
269     return bQuery;
270   }
271 
272   /**
273    * Parses a query, searching on the fields specified.
274    * Use this if you need to specify certain fields as required,
275    * and others as prohibited.
276    * <p><pre>
277    * Usage:
278    * <code>
279    * String[] query = {"query1", "query2", "query3"};
280    * String[] fields = {"filename", "contents", "description"};
281    * BooleanClause.Occur[] flags = {BooleanClause.Occur.SHOULD,
282    *                BooleanClause.Occur.MUST,
283    *                BooleanClause.Occur.MUST_NOT};
284    * MultiFieldQueryParser.parse(query, fields, flags, analyzer);
285    * </code>
286    * </pre>
287    *<p>
288    * The code above would construct a query:
289    * <pre>
290    * <code>
291    * (filename:query1) +(contents:query2) -(description:query3)
292    * </code>
293    * </pre>
294    *
295    * @param queries Queries string to parse
296    * @param fields Fields to search on
297    * @param flags Flags describing the fields
298    * @param analyzer Analyzer to use
299    * @throws ParseException if query parsing fails
300    * @throws IllegalArgumentException if the length of the queries, fields,
301    *  and flags array differ
302    */
303   public static Query parse(String[] queries, String[] fields, BooleanClause.Occur[] flags,
304       Analyzer analyzer) throws ParseException
305   {
306     if (!(queries.length == fields.length && queries.length == flags.length))
307       throw new IllegalArgumentException("queries, fields, and flags array have have different length");
308     BooleanQuery bQuery = new BooleanQuery();
309     for (int i = 0; i < fields.length; i++)
310     {
311       QueryParser qp = new QueryParser(fields[i], analyzer);
312       Query q = qp.parse(queries[i]);
313       if (q!=null && // q never null, just being defensive
314           (!(q instanceof BooleanQuery) || ((BooleanQuery)q).getClauses().length>0)) {
315         bQuery.add(q, flags[i]);
316       }
317     }
318     return bQuery;
319   }
320 
321 }
322