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