View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.query.process;
25  
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.NoSuchElementException;
34  import java.util.Set;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  import net.jcip.annotations.Immutable;
37  import org.modeshape.common.util.CheckArg;
38  import org.modeshape.graph.GraphI18n;
39  import org.modeshape.graph.Location;
40  import org.modeshape.graph.query.QueryResults.Columns;
41  import org.modeshape.graph.query.model.Column;
42  import org.modeshape.graph.query.model.Constraint;
43  import org.modeshape.graph.query.model.FullTextSearch;
44  import org.modeshape.graph.query.model.Visitors;
45  
46  /**
47   * Defines the columns associated with the results of a query. This definition allows the values to be accessed
48   */
49  @Immutable
50  public class QueryResultColumns implements Columns {
51      private static final long serialVersionUID = 1L;
52  
53      protected static final List<Column> NO_COLUMNS = Collections.<Column>emptyList();
54      protected static final QueryResultColumns EMPTY = new QueryResultColumns(false, null);
55  
56      protected static final String DEFAULT_SELECTOR_NAME = "Results";
57  
58      /**
59       * Get an empty results column definition.
60       * 
61       * @return the empty columns definition; never null
62       */
63      public static QueryResultColumns empty() {
64          return EMPTY;
65      }
66  
67      private final int tupleSize;
68      private final List<Column> columns;
69      private final List<String> columnNames;
70      private final List<String> selectorNames;
71      private List<String> tupleValueNames;
72      private final Map<String, Column> columnsByName;
73      private final Map<String, Integer> columnIndexByColumnName;
74      private final Map<String, Integer> locationIndexBySelectorName;
75      private final Map<String, Integer> locationIndexByColumnName;
76      private final Map<Integer, Integer> locationIndexByColumnIndex;
77      private final Map<String, Map<String, Integer>> columnIndexByPropertyNameBySelectorName;
78      private final Map<String, Integer> fullTextSearchScoreIndexBySelectorName;
79  
80      /**
81       * Create a new definition for the query results given the supplied columns.
82       * 
83       * @param columns the columns that define the results; should never be modified directly
84       * @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each
85       *        {@link Location}, or false otherwise
86       */
87      public QueryResultColumns( List<Column> columns,
88                                 boolean includeFullTextSearchScores ) {
89          this(includeFullTextSearchScores, columns);
90          CheckArg.isNotEmpty(columns, "columns");
91      }
92  
93      /**
94       * Create a new definition for the query results given the supplied columns.
95       * 
96       * @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each
97       *        {@link Location}, or false otherwise
98       * @param columns the columns that define the results; should never be modified directly
99       */
100     protected QueryResultColumns( boolean includeFullTextSearchScores,
101                                   List<Column> columns ) {
102         this.columns = columns != null ? Collections.<Column>unmodifiableList(columns) : NO_COLUMNS;
103         this.columnsByName = new HashMap<String, Column>();
104         this.columnIndexByColumnName = new HashMap<String, Integer>();
105         Set<String> selectors = new HashSet<String>();
106         final int columnCount = this.columns.size();
107         Integer selectorIndex = new Integer(columnCount - 1);
108         this.locationIndexBySelectorName = new HashMap<String, Integer>();
109         this.locationIndexByColumnIndex = new HashMap<Integer, Integer>();
110         this.locationIndexByColumnName = new HashMap<String, Integer>();
111         this.columnIndexByPropertyNameBySelectorName = new HashMap<String, Map<String, Integer>>();
112         List<String> selectorNames = new ArrayList<String>(columnCount);
113         List<String> names = new ArrayList<String>(columnCount);
114         for (int i = 0, max = this.columns.size(); i != max; ++i) {
115             Column column = this.columns.get(i);
116             assert column != null;
117             String columnName = column.getColumnName();
118             assert columnName != null;
119             if (columnsByName.put(columnName, column) != null) {
120                 assert false : "Column names must be unique";
121             }
122             names.add(columnName);
123             columnIndexByColumnName.put(columnName, new Integer(i));
124             String selectorName = column.getSelectorName().getName();
125             if (selectors.add(selectorName)) {
126                 selectorNames.add(selectorName);
127                 selectorIndex = new Integer(selectorIndex.intValue() + 1);
128                 locationIndexBySelectorName.put(selectorName, selectorIndex);
129             }
130             locationIndexByColumnIndex.put(new Integer(i), selectorIndex);
131             locationIndexByColumnName.put(columnName, selectorIndex);
132             // Insert the entry by selector name and property name ...
133             Map<String, Integer> byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
134             if (byPropertyName == null) {
135                 byPropertyName = new HashMap<String, Integer>();
136                 columnIndexByPropertyNameBySelectorName.put(selectorName, byPropertyName);
137             }
138             byPropertyName.put(column.getPropertyName(), new Integer(i));
139         }
140         if (columns != null && selectorNames.isEmpty()) {
141             String selectorName = DEFAULT_SELECTOR_NAME;
142             selectorNames.add(selectorName);
143             locationIndexBySelectorName.put(selectorName, 0);
144         }
145         this.selectorNames = Collections.unmodifiableList(selectorNames);
146         this.columnNames = Collections.unmodifiableList(names);
147         if (includeFullTextSearchScores) {
148             this.fullTextSearchScoreIndexBySelectorName = new HashMap<String, Integer>();
149             int index = columnNames.size() + selectorNames.size();
150             for (String selectorName : selectorNames) {
151                 fullTextSearchScoreIndexBySelectorName.put(selectorName, new Integer(index++));
152             }
153             this.tupleSize = columnNames.size() + selectorNames.size() + selectorNames.size();
154         } else {
155             this.fullTextSearchScoreIndexBySelectorName = null;
156             this.tupleSize = columnNames.size() + selectorNames.size();
157         }
158     }
159 
160     public static boolean includeFullTextScores( Iterable<Constraint> constraints ) {
161         for (Constraint constraint : constraints) {
162             if (includeFullTextScores(constraint)) return true;
163         }
164         return false;
165     }
166 
167     public static boolean includeFullTextScores( Constraint constraint ) {
168         final AtomicBoolean includeFullTextScores = new AtomicBoolean(false);
169         if (constraint != null) {
170             Visitors.visitAll(constraint, new Visitors.AbstractVisitor() {
171                 @Override
172                 public void visit( FullTextSearch obj ) {
173                     includeFullTextScores.set(true);
174                 }
175             });
176         }
177         return includeFullTextScores.get();
178     }
179 
180     /**
181      * {@inheritDoc}
182      * 
183      * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(java.util.List)
184      */
185     public Columns subSelect( List<Column> columns ) {
186         return new QueryResultColumns(columns, this);
187     }
188 
189     /**
190      * {@inheritDoc}
191      * 
192      * @see org.modeshape.graph.query.QueryResults.Columns#subSelect(org.modeshape.graph.query.model.Column[])
193      */
194     public Columns subSelect( Column... columns ) {
195         return new QueryResultColumns(Arrays.asList(columns), this);
196     }
197 
198     private QueryResultColumns( List<Column> columns,
199                                 QueryResultColumns wrappedAround ) {
200         assert columns != null;
201         this.columns = Collections.unmodifiableList(columns);
202         this.columnsByName = new HashMap<String, Column>();
203         this.columnIndexByColumnName = new HashMap<String, Integer>();
204         this.locationIndexBySelectorName = new HashMap<String, Integer>();
205         this.locationIndexByColumnIndex = new HashMap<Integer, Integer>();
206         this.locationIndexByColumnName = new HashMap<String, Integer>();
207         this.columnIndexByPropertyNameBySelectorName = new HashMap<String, Map<String, Integer>>();
208         this.selectorNames = new ArrayList<String>(columns.size());
209         List<String> names = new ArrayList<String>(columns.size());
210         for (int i = 0, max = this.columns.size(); i != max; ++i) {
211             Column column = this.columns.get(i);
212             assert column != null;
213             String columnName = column.getColumnName();
214             assert columnName != null;
215             if (columnsByName.put(columnName, column) != null) {
216                 assert false : "Column names must be unique";
217             }
218             names.add(columnName);
219             Integer columnIndex = new Integer(wrappedAround.getColumnIndexForName(columnName));
220             columnIndexByColumnName.put(columnName, columnIndex);
221             String selectorName = column.getSelectorName().getName();
222             if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName);
223             Integer selectorIndex = new Integer(wrappedAround.getLocationIndex(selectorName));
224             locationIndexBySelectorName.put(selectorName, selectorIndex);
225             locationIndexByColumnIndex.put(new Integer(0), selectorIndex);
226             locationIndexByColumnName.put(columnName, selectorIndex);
227             // Insert the entry by selector name and property name ...
228             Map<String, Integer> byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
229             if (byPropertyName == null) {
230                 byPropertyName = new HashMap<String, Integer>();
231                 columnIndexByPropertyNameBySelectorName.put(selectorName, byPropertyName);
232             }
233             byPropertyName.put(column.getPropertyName(), columnIndex);
234         }
235         if (selectorNames.isEmpty()) {
236             String selectorName = DEFAULT_SELECTOR_NAME;
237             selectorNames.add(selectorName);
238             locationIndexBySelectorName.put(selectorName, 0);
239         }
240         this.columnNames = Collections.unmodifiableList(names);
241         if (wrappedAround.fullTextSearchScoreIndexBySelectorName != null) {
242             this.fullTextSearchScoreIndexBySelectorName = new HashMap<String, Integer>();
243             int index = columnNames.size() + selectorNames.size();
244             for (String selectorName : selectorNames) {
245                 fullTextSearchScoreIndexBySelectorName.put(selectorName, new Integer(index++));
246             }
247             this.tupleSize = columnNames.size() + selectorNames.size() + selectorNames.size();
248         } else {
249             this.fullTextSearchScoreIndexBySelectorName = null;
250             this.tupleSize = columnNames.size() + selectorNames.size();
251         }
252     }
253 
254     /**
255      * {@inheritDoc}
256      * 
257      * @see org.modeshape.graph.query.QueryResults.Columns#getColumns()
258      */
259     public List<Column> getColumns() {
260         return columns;
261     }
262 
263     /**
264      * {@inheritDoc}
265      * 
266      * @see org.modeshape.graph.query.QueryResults.Columns#getColumnNames()
267      */
268     public List<String> getColumnNames() {
269         return columnNames;
270     }
271 
272     /**
273      * {@inheritDoc}
274      * 
275      * @see org.modeshape.graph.query.QueryResults.Columns#getColumnCount()
276      */
277     public int getColumnCount() {
278         return columns.size();
279     }
280 
281     /**
282      * {@inheritDoc}
283      * 
284      * @see org.modeshape.graph.query.QueryResults.Columns#getLocationCount()
285      */
286     public int getLocationCount() {
287         return selectorNames.size();
288     }
289 
290     /**
291      * {@inheritDoc}
292      * 
293      * @see org.modeshape.graph.query.QueryResults.Columns#getSelectorNames()
294      */
295     public List<String> getSelectorNames() {
296         return selectorNames;
297     }
298 
299     /**
300      * {@inheritDoc}
301      * 
302      * @see org.modeshape.graph.query.QueryResults.Columns#getTupleSize()
303      */
304     public int getTupleSize() {
305         return tupleSize;
306     }
307 
308     /**
309      * {@inheritDoc}
310      * 
311      * @see org.modeshape.graph.query.QueryResults.Columns#getTupleValueNames()
312      */
313     public List<String> getTupleValueNames() {
314         if (this.tupleValueNames == null) {
315             // This is idempotent, so no need to lock ...
316             List<String> results = new ArrayList<String>(getTupleSize());
317             // Add the column names ...
318             results.addAll(columnNames);
319             // Add the location names ...
320             for (String selectorName : selectorNames) {
321                 String name = "Location(" + selectorName + ")";
322                 results.add(name);
323             }
324             // Add the full-text search score names ...
325             if (fullTextSearchScoreIndexBySelectorName != null) {
326                 for (String selectorName : selectorNames) {
327                     String name = "Score(" + selectorName + ")";
328                     results.add(name);
329                 }
330             }
331             this.tupleValueNames = results;
332         }
333         return this.tupleValueNames;
334     }
335 
336     /**
337      * {@inheritDoc}
338      * 
339      * @see org.modeshape.graph.query.QueryResults.Columns#getLocationIndexForColumn(int)
340      */
341     public int getLocationIndexForColumn( int columnIndex ) {
342         if (locationIndexByColumnIndex.isEmpty()) return 0;
343         Integer result = locationIndexByColumnIndex.get(new Integer(columnIndex));
344         if (result == null) {
345             throw new IndexOutOfBoundsException(GraphI18n.columnDoesNotExistInQuery.text(columnIndex));
346         }
347         return result.intValue();
348     }
349 
350     /**
351      * {@inheritDoc}
352      * 
353      * @see org.modeshape.graph.query.QueryResults.Columns#getLocationIndexForColumn(java.lang.String)
354      */
355     public int getLocationIndexForColumn( String columnName ) {
356         if (locationIndexByColumnName.isEmpty()) return 0;
357         Integer result = locationIndexByColumnName.get(columnName);
358         if (result == null) {
359             throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
360         }
361         return result.intValue();
362     }
363 
364     /**
365      * {@inheritDoc}
366      * 
367      * @see org.modeshape.graph.query.QueryResults.Columns#getLocationIndex(java.lang.String)
368      */
369     public int getLocationIndex( String selectorName ) {
370         Integer result = locationIndexBySelectorName.get(selectorName);
371         if (result == null) {
372             throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
373         }
374         return result.intValue();
375     }
376 
377     /**
378      * {@inheritDoc}
379      * 
380      * @see org.modeshape.graph.query.QueryResults.Columns#hasSelector(java.lang.String)
381      */
382     public boolean hasSelector( String selectorName ) {
383         return locationIndexBySelectorName.containsKey(selectorName);
384     }
385 
386     /**
387      * {@inheritDoc}
388      * 
389      * @see org.modeshape.graph.query.QueryResults.Columns#getPropertyNameForColumn(int)
390      */
391     public String getPropertyNameForColumn( int columnIndex ) {
392         return columns.get(columnIndex).getPropertyName();
393     }
394 
395     /**
396      * {@inheritDoc}
397      * 
398      * @see org.modeshape.graph.query.QueryResults.Columns#getPropertyNameForColumn(java.lang.String)
399      */
400     public String getPropertyNameForColumn( String columnName ) {
401         Column result = columnsByName.get(columnName);
402         if (result == null) {
403             throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
404         }
405         return result.getPropertyName();
406     }
407 
408     /**
409      * {@inheritDoc}
410      * 
411      * @see org.modeshape.graph.query.QueryResults.Columns#getColumnIndexForName(java.lang.String)
412      */
413     public int getColumnIndexForName( String columnName ) {
414         Integer result = columnIndexByColumnName.get(columnName);
415         if (result == null) {
416             throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
417         }
418         return result.intValue();
419     }
420 
421     /**
422      * {@inheritDoc}
423      * 
424      * @see org.modeshape.graph.query.QueryResults.Columns#getColumnIndexForProperty(java.lang.String, java.lang.String)
425      */
426     public int getColumnIndexForProperty( String selectorName,
427                                           String propertyName ) {
428         Map<String, Integer> byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
429         if (byPropertyName == null) {
430             throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
431         }
432         Integer result = byPropertyName.get(propertyName);
433         if (result == null) {
434             throw new NoSuchElementException(GraphI18n.propertyOnSelectorIsNotUsedInQuery.text(propertyName, selectorName));
435         }
436         return result.intValue();
437     }
438 
439     /**
440      * {@inheritDoc}
441      * 
442      * @see org.modeshape.graph.query.QueryResults.Columns#getFullTextSearchScoreIndexFor(java.lang.String)
443      */
444     public int getFullTextSearchScoreIndexFor( String selectorName ) {
445         if (fullTextSearchScoreIndexBySelectorName == null) return -1;
446         Integer result = fullTextSearchScoreIndexBySelectorName.get(selectorName);
447         if (result == null) {
448             throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
449         }
450         return result.intValue();
451     }
452 
453     /**
454      * {@inheritDoc}
455      * 
456      * @see org.modeshape.graph.query.QueryResults.Columns#hasFullTextSearchScores()
457      */
458     public boolean hasFullTextSearchScores() {
459         return fullTextSearchScoreIndexBySelectorName != null;
460     }
461 
462     /**
463      * {@inheritDoc}
464      * 
465      * @see org.modeshape.graph.query.QueryResults.Columns#includes(org.modeshape.graph.query.QueryResults.Columns)
466      */
467     public boolean includes( Columns other ) {
468         if (other == this) return true;
469         if (other == null) return false;
470         return this.getColumns().containsAll(other.getColumns());
471     }
472 
473     /**
474      * {@inheritDoc}
475      * 
476      * @see org.modeshape.graph.query.QueryResults.Columns#isUnionCompatible(org.modeshape.graph.query.QueryResults.Columns)
477      */
478     public boolean isUnionCompatible( Columns other ) {
479         if (this == other) return true;
480         if (other == null) return false;
481         if (this.hasFullTextSearchScores() != other.hasFullTextSearchScores()) return false;
482         if (this.getColumnCount() != other.getColumnCount()) return false;
483         return this.getColumns().containsAll(other.getColumns()) && other.getColumns().containsAll(this.getColumns());
484     }
485 
486     /**
487      * {@inheritDoc}
488      * 
489      * @see java.lang.Object#equals(java.lang.Object)
490      */
491     @Override
492     public boolean equals( Object obj ) {
493         if (obj == this) return true;
494         if (obj instanceof QueryResultColumns) {
495             QueryResultColumns that = (QueryResultColumns)obj;
496             return this.getColumns().equals(that.getColumns());
497         }
498         return false;
499     }
500 
501     /**
502      * {@inheritDoc}
503      * 
504      * @see java.lang.Object#toString()
505      */
506     @Override
507     public String toString() {
508         StringBuilder sb = new StringBuilder();
509         sb.append(" [");
510         boolean first = true;
511         for (Column column : getColumns()) {
512             if (first) first = false;
513             else sb.append(", ");
514             sb.append(column);
515         }
516         sb.append("] => Locations[");
517         first = true;
518         for (int i = 0, count = getColumnCount(); i != count; ++i) {
519             if (first) first = false;
520             else sb.append(", ");
521             sb.append(getLocationIndexForColumn(i));
522         }
523         sb.append(']');
524         return sb.toString();
525     }
526 }