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