1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
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
65
66
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
87
88
89
90
91
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
103
104
105
106
107
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
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
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
253
254
255
256 public Columns subSelect( List<Column> columns ) {
257 return new QueryResultColumns(columns, this);
258 }
259
260
261
262
263
264
265 public Columns subSelect( Column... columns ) {
266 return new QueryResultColumns(Arrays.asList(columns), this);
267 }
268
269
270
271
272
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
288
289
290
291 public List<? extends Column> getColumns() {
292 return columns;
293 }
294
295
296
297
298
299
300 @SuppressWarnings( "unchecked" )
301 public Iterator<Column> iterator() {
302 return (Iterator<Column>)getColumns().iterator();
303 }
304
305
306
307
308
309
310 public List<String> getColumnNames() {
311 return columnNames;
312 }
313
314
315
316
317
318
319 public List<String> getColumnTypes() {
320 return columnTypes;
321 }
322
323
324
325
326
327
328 public int getColumnCount() {
329 return columns.size();
330 }
331
332
333
334
335
336
337 public int getLocationCount() {
338 return selectorNames.size();
339 }
340
341
342
343
344
345
346 public List<String> getSelectorNames() {
347 return selectorNames;
348 }
349
350
351
352
353
354
355 public int getTupleSize() {
356 return tupleSize;
357 }
358
359
360
361
362
363
364 public List<String> getTupleValueNames() {
365 if (this.tupleValueNames == null) {
366
367 List<String> results = new ArrayList<String>(getTupleSize());
368
369 results.addAll(columnNames);
370
371 for (String selectorName : selectorNames) {
372 String name = "Location(" + selectorName + ")";
373 results.add(name);
374 }
375
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
389
390
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
403
404
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
417
418
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
430
431
432
433 public boolean hasSelector( String selectorName ) {
434 return locationIndexBySelectorName.containsKey(selectorName);
435 }
436
437
438
439
440
441
442 public String getPropertyNameForColumn( int columnIndex ) {
443 return columns.get(columnIndex).propertyName();
444 }
445
446
447
448
449
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
465
466
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
483
484
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
497
498
499
500 public boolean hasFullTextSearchScores() {
501 return fullTextSearchScoreIndexBySelectorName != null;
502 }
503
504
505
506
507
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
517
518
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
530
531
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
571
572
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 }