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.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
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
60
61
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
82
83
84
85
86
87 public QueryResultColumns( List<Column> columns,
88 boolean includeFullTextSearchScores ) {
89 this(includeFullTextSearchScores, columns);
90 CheckArg.isNotEmpty(columns, "columns");
91 }
92
93
94
95
96
97
98
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
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
182
183
184
185 public Columns subSelect( List<Column> columns ) {
186 return new QueryResultColumns(columns, this);
187 }
188
189
190
191
192
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
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
256
257
258
259 public List<Column> getColumns() {
260 return columns;
261 }
262
263
264
265
266
267
268 public List<String> getColumnNames() {
269 return columnNames;
270 }
271
272
273
274
275
276
277 public int getColumnCount() {
278 return columns.size();
279 }
280
281
282
283
284
285
286 public int getLocationCount() {
287 return selectorNames.size();
288 }
289
290
291
292
293
294
295 public List<String> getSelectorNames() {
296 return selectorNames;
297 }
298
299
300
301
302
303
304 public int getTupleSize() {
305 return tupleSize;
306 }
307
308
309
310
311
312
313 public List<String> getTupleValueNames() {
314 if (this.tupleValueNames == null) {
315
316 List<String> results = new ArrayList<String>(getTupleSize());
317
318 results.addAll(columnNames);
319
320 for (String selectorName : selectorNames) {
321 String name = "Location(" + selectorName + ")";
322 results.add(name);
323 }
324
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
338
339
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
352
353
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
366
367
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
379
380
381
382 public boolean hasSelector( String selectorName ) {
383 return locationIndexBySelectorName.containsKey(selectorName);
384 }
385
386
387
388
389
390
391 public String getPropertyNameForColumn( int columnIndex ) {
392 return columns.get(columnIndex).getPropertyName();
393 }
394
395
396
397
398
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
410
411
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
423
424
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
441
442
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
455
456
457
458 public boolean hasFullTextSearchScores() {
459 return fullTextSearchScoreIndexBySelectorName != null;
460 }
461
462
463
464
465
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
475
476
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
488
489
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
503
504
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 }