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.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
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
61
62
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
83
84
85
86
87
88 public QueryResultColumns( List<Column> columns,
89 boolean includeFullTextSearchScores ) {
90 this(includeFullTextSearchScores, columns);
91 CheckArg.isNotEmpty(columns, "columns");
92 }
93
94
95
96
97
98
99
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
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
183
184
185
186 public Columns subSelect( List<Column> columns ) {
187 return new QueryResultColumns(columns, this);
188 }
189
190
191
192
193
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
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
257
258
259
260 public List<Column> getColumns() {
261 return columns;
262 }
263
264
265
266
267
268
269 public Iterator<Column> iterator() {
270 return getColumns().iterator();
271 }
272
273
274
275
276
277
278 public List<String> getColumnNames() {
279 return columnNames;
280 }
281
282
283
284
285
286
287 public int getColumnCount() {
288 return columns.size();
289 }
290
291
292
293
294
295
296 public int getLocationCount() {
297 return selectorNames.size();
298 }
299
300
301
302
303
304
305 public List<String> getSelectorNames() {
306 return selectorNames;
307 }
308
309
310
311
312
313
314 public int getTupleSize() {
315 return tupleSize;
316 }
317
318
319
320
321
322
323 public List<String> getTupleValueNames() {
324 if (this.tupleValueNames == null) {
325
326 List<String> results = new ArrayList<String>(getTupleSize());
327
328 results.addAll(columnNames);
329
330 for (String selectorName : selectorNames) {
331 String name = "Location(" + selectorName + ")";
332 results.add(name);
333 }
334
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
348
349
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
362
363
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
376
377
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
389
390
391
392 public boolean hasSelector( String selectorName ) {
393 return locationIndexBySelectorName.containsKey(selectorName);
394 }
395
396
397
398
399
400
401 public String getPropertyNameForColumn( int columnIndex ) {
402 return columns.get(columnIndex).getPropertyName();
403 }
404
405
406
407
408
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
420
421
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
433
434
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
451
452
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
465
466
467
468 public boolean hasFullTextSearchScores() {
469 return fullTextSearchScoreIndexBySelectorName != null;
470 }
471
472
473
474
475
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
485
486
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
498
499
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
513
514
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 }