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.Collections;
27 import java.util.Iterator;
28 import java.util.List;
29 import net.jcip.annotations.Immutable;
30 import org.modeshape.common.collection.ImmutableProblems;
31 import org.modeshape.common.collection.Problems;
32 import org.modeshape.common.collection.SimpleProblems;
33 import org.modeshape.common.util.StringUtil;
34 import org.modeshape.graph.GraphI18n;
35 import org.modeshape.graph.Location;
36 import org.modeshape.graph.query.model.TypeSystem;
37 import org.modeshape.graph.query.model.TypeSystem.TypeFactory;
38
39 /**
40 * The resulting output of a query.
41 */
42 @Immutable
43 public class QueryResults implements org.modeshape.graph.query.QueryResults {
44 private static final Problems NO_PROBLEMS = new ImmutableProblems(new SimpleProblems());
45
46 private static final long serialVersionUID = 1L;
47
48 private final Problems problems;
49 private final Columns columns;
50 private final List<Object[]> tuples;
51 private final Statistics statistics;
52 private final String plan;
53
54 /**
55 * Create a results object for the supplied context, command, and result columns and with the supplied tuples.
56 *
57 * @param columns the definition of the query result columns
58 * @param statistics the statistics for this query; may not be null
59 * @param tuples the tuples
60 * @param problems the problems; may be null if there are no problems
61 * @param plan the text representation of the query plan, if the hints asked for it
62 */
63 public QueryResults( Columns columns,
64 Statistics statistics,
65 List<Object[]> tuples,
66 Problems problems,
67 String plan ) {
68 assert columns != null;
69 assert statistics != null;
70 this.problems = problems != null ? problems : NO_PROBLEMS;
71 this.columns = columns;
72 this.tuples = tuples;
73 this.statistics = statistics;
74 this.plan = plan;
75 }
76
77 /**
78 * Create a results object for the supplied context, command, and result columns and with the supplied tuples.
79 *
80 * @param columns the definition of the query result columns
81 * @param statistics the statistics for this query; may not be null
82 * @param tuples the tuples
83 */
84 public QueryResults( Columns columns,
85 Statistics statistics,
86 List<Object[]> tuples ) {
87 this(columns, statistics, tuples, NO_PROBLEMS, null);
88 }
89
90 /**
91 * Create an empty {@link QueryResults} object for the supplied context, command, and result columns.
92 *
93 * @param columns the definition of the query result columns
94 * @param statistics the statistics for this query; may not be null
95 * @param problems the problems; may be null if there are no problems
96 */
97 public QueryResults( Columns columns,
98 Statistics statistics,
99 Problems problems ) {
100 this(columns, statistics, Collections.<Object[]>emptyList(), problems, null);
101 }
102
103 /**
104 * Create an empty {@link QueryResults} object for the supplied context, command, and result columns.
105 *
106 * @param columns the definition of the query result columns
107 * @param statistics the statistics for this query; may not be null
108 */
109 public QueryResults( Columns columns,
110 Statistics statistics ) {
111 this(columns, statistics, Collections.<Object[]>emptyList(), null, null);
112 }
113
114 /**
115 * {@inheritDoc}
116 *
117 * @see org.modeshape.graph.query.QueryResults#getColumns()
118 */
119 public Columns getColumns() {
120 return columns;
121 }
122
123 /**
124 * Get a cursor that can be used to walk through the results.
125 *
126 * @return the cursor; never null, though possibly empty (meaning {@link Cursor#hasNext()} may return true)
127 */
128 public Cursor getCursor() {
129 return new TupleCursor(columns, tuples.iterator());
130 }
131
132 /**
133 * {@inheritDoc}
134 *
135 * @see org.modeshape.graph.query.QueryResults#getTuples()
136 */
137 public List<Object[]> getTuples() {
138 return tuples;
139 }
140
141 /**
142 * {@inheritDoc}
143 *
144 * @see org.modeshape.graph.query.QueryResults#getRowCount()
145 */
146 public int getRowCount() {
147 return tuples.size();
148 }
149
150 /**
151 * {@inheritDoc}
152 *
153 * @see org.modeshape.graph.query.QueryResults#getPlan()
154 */
155 public String getPlan() {
156 return plan;
157 }
158
159 /**
160 * {@inheritDoc}
161 *
162 * @see org.modeshape.graph.query.QueryResults#getProblems()
163 */
164 public Problems getProblems() {
165 return problems;
166 }
167
168 /**
169 * {@inheritDoc}
170 *
171 * @see org.modeshape.graph.query.QueryResults#hasErrors()
172 */
173 public boolean hasErrors() {
174 return getProblems().hasErrors();
175 }
176
177 /**
178 * {@inheritDoc}
179 *
180 * @see org.modeshape.graph.query.QueryResults#hasWarnings()
181 */
182 public boolean hasWarnings() {
183 return getProblems().hasWarnings();
184 }
185
186 /**
187 * {@inheritDoc}
188 *
189 * @see org.modeshape.graph.query.QueryResults#getStatistics()
190 */
191 public Statistics getStatistics() {
192 return statistics;
193 }
194
195 /**
196 * {@inheritDoc}
197 *
198 * @see java.lang.Object#toString()
199 */
200 @Override
201 public String toString() {
202 return toString(null, Integer.MAX_VALUE);
203 }
204
205 /**
206 * Get a string representation of this result object, with a maximum number of tuples to include.
207 *
208 * @param typeSystem the type system that can be used to convert the values to a string; may be null if
209 * {@link Object#toString()} should be used
210 * @param maxTuples the maximum number of tuples to print, or {@link Integer#MAX_VALUE} if all the tuples are to be printed
211 * @return the string representation; never null
212 */
213 public String toString( TypeSystem typeSystem,
214 int maxTuples ) {
215 StringBuilder sb = new StringBuilder();
216 toString(typeSystem, sb, maxTuples);
217 return sb.toString();
218 }
219
220 /**
221 * Get a string representation of this result object.
222 *
223 * @param typeSystem the type system that can be used to convert the values to a string; may be null if
224 * {@link Object#toString()} should be used
225 * @param sb the string builder to which the results should be written; may not be null
226 */
227 public void toString( TypeSystem typeSystem,
228 StringBuilder sb ) {
229 toString(typeSystem, sb, Integer.MAX_VALUE);
230 }
231
232 /**
233 * Get a string representation of this result object, with a maximum number of tuples to include.
234 *
235 * @param typeSystem the type system that can be used to convert the values to a string; may be null if
236 * {@link Object#toString()} should be used
237 * @param sb the string builder to which the results should be written; may not be null
238 * @param maxTuples the maximum number of tuples to print, or {@link Integer#MAX_VALUE} if all the tuples are to be printed
239 */
240 public void toString( TypeSystem typeSystem,
241 StringBuilder sb,
242 int maxTuples ) {
243 int[] columnWidths = determineColumnWidths(typeSystem, Integer.MAX_VALUE, true);
244 printDelimiterLine(sb, columnWidths, true);
245 printHeader(sb, columnWidths);
246 printDelimiterLine(sb, columnWidths, true);
247 printLines(typeSystem, sb, columnWidths, maxTuples);
248 printDelimiterLine(sb, columnWidths, false);
249 }
250
251 /**
252 * Determine the width of each column.
253 *
254 * @param typeSystem the type system that can be used to convert the values to a string; may be null if
255 * {@link Object#toString()} should be used
256 * @param maxWidth the maximum width; must be positive
257 * @param useData true if the data should be used to compute the length, or false if just the column names should be used
258 * @return the array of widths for each column, excluding any decorating characters; never null
259 */
260 protected int[] determineColumnWidths( TypeSystem typeSystem,
261 int maxWidth,
262 boolean useData ) {
263 assert maxWidth > 0;
264 int tupleLength = columns.getTupleSize();
265 int[] columnWidths = new int[tupleLength + 1]; // +1 for the row number column
266 for (int i = 0; i != columnWidths.length; ++i) {
267 columnWidths[i] = 0;
268 }
269 // Determine the width of the first column that shows the row number ...
270 String rowNumber = Integer.toString(getTuples().size());
271 columnWidths[0] = rowNumber.length();
272
273 // Compute the column names ...
274 List<String> tupleValueNames = columns.getTupleValueNames();
275 for (int i = 0, j = 1, max = tupleValueNames.size(); i != max; ++i, ++j) {
276 String name = tupleValueNames.get(i);
277 columnWidths[j] = Math.max(Math.min(maxWidth, name.length()), columnWidths[j]);
278 }
279 // Look at the data ...
280 if (useData) {
281 for (Object[] tuple : getTuples()) {
282 for (int i = 0, j = 1; i != tupleLength; ++i, ++j) {
283 String valueStr = stringOf(typeSystem, tuple[i]);
284 if (valueStr == null) continue;
285 columnWidths[j] = Math.max(Math.min(maxWidth, valueStr.length()), columnWidths[j]);
286 }
287 }
288 }
289 return columnWidths;
290 }
291
292 protected String stringOf( TypeSystem typeSystem,
293 Object value ) {
294 if (value == null) return null;
295 if (typeSystem == null) return value.toString();
296 TypeFactory<?> typeFactory = typeSystem.getTypeFactory(value);
297 return typeFactory.asReadableString(value);
298 }
299
300 protected void printHeader( StringBuilder sb,
301 int[] columnWidths ) {
302 // Print the row number column ...
303 sb.append("| ").append(StringUtil.justifyLeft("#", columnWidths[0], ' ')).append(' ');
304 // Print the name line ...
305 sb.append('|');
306 int i = 1;
307 for (String name : columns.getTupleValueNames()) {
308 sb.append(' ');
309 sb.append(StringUtil.justifyLeft(name, columnWidths[i], ' '));
310 sb.append(" |");
311 ++i;
312 }
313 sb.append('\n');
314 }
315
316 protected void printLines( TypeSystem typeSystem,
317 StringBuilder sb,
318 int[] columnWidths,
319 int maxRowsToPrint ) {
320 int rowNumber = 1;
321 int tupleLength = columns.getTupleSize();
322 // Should they all be printed ?
323 if (maxRowsToPrint > tuples.size()) {
324 // Print all tuples ...
325 for (Object[] tuple : getTuples()) {
326 printTuple(typeSystem, sb, columnWidths, rowNumber, tupleLength, tuple);
327 ++rowNumber;
328 }
329 } else {
330 // Print max number of rows ...
331 for (Object[] tuple : getTuples()) {
332 printTuple(typeSystem, sb, columnWidths, rowNumber, tupleLength, tuple);
333 if (rowNumber >= maxRowsToPrint) break;
334 ++rowNumber;
335 }
336 }
337
338 }
339
340 private final void printTuple( TypeSystem typeSystem,
341 StringBuilder sb,
342 int[] columnWidths,
343 int rowNumber,
344 int tupleLength,
345 Object[] tuple ) {
346 // Print the row number column ...
347 sb.append("| ").append(StringUtil.justifyLeft(Integer.toString(rowNumber), columnWidths[0], ' ')).append(' ');
348 // Print the remaining columns ...
349 for (int i = 0, j = 1; i != tupleLength; ++i, ++j) {
350 String valueStr = stringOf(typeSystem, tuple[i]);
351 valueStr = StringUtil.justifyLeft(valueStr, columnWidths[j], ' ');
352 sb.append('|').append(' ').append(valueStr).append(' ');
353 }
354 sb.append('|');
355 sb.append('\n');
356 }
357
358 protected void printDelimiterLine( StringBuilder sb,
359 int[] columnWidths,
360 boolean includeLineFeed ) {
361 sb.append('+');
362 for (int i = 0, max = columnWidths.length; i != max; ++i) {
363 for (int j = 0, width = columnWidths[i] + 2; j != width; ++j) { // +1 for space before, +1 for space after
364 sb.append('-');
365 }
366 sb.append('+');
367 }
368 if (includeLineFeed) sb.append('\n');
369 }
370
371 /**
372 * An interface used to walk through the results.
373 */
374 public final class TupleCursor implements Cursor {
375 private final Columns columns;
376 private final Iterator<Object[]> iterator;
377 private Object[] currentTuple;
378 private int tupleIndex;
379
380 protected TupleCursor( Columns columns,
381 Iterator<Object[]> iterator ) {
382 this.iterator = iterator;
383 this.columns = columns;
384 this.tupleIndex = -1;
385 }
386
387 /**
388 * {@inheritDoc}
389 *
390 * @see org.modeshape.graph.query.QueryResults.Cursor#hasNext()
391 */
392 public boolean hasNext() {
393 return iterator.hasNext();
394 }
395
396 /**
397 * {@inheritDoc}
398 *
399 * @see org.modeshape.graph.query.QueryResults.Cursor#next()
400 */
401 public void next() {
402 currentTuple = iterator.next();
403 ++tupleIndex;
404 }
405
406 /**
407 * {@inheritDoc}
408 *
409 * @see org.modeshape.graph.query.QueryResults.Cursor#getLocation(int)
410 */
411 public Location getLocation( int columnNumber ) {
412 return (Location)currentTuple[columns.getLocationIndexForColumn(columnNumber)];
413 }
414
415 /**
416 * {@inheritDoc}
417 *
418 * @see org.modeshape.graph.query.QueryResults.Cursor#getLocation(java.lang.String)
419 */
420 public Location getLocation( String selectorName ) {
421 return (Location)currentTuple[columns.getLocationIndex(selectorName)];
422 }
423
424 /**
425 * {@inheritDoc}
426 *
427 * @see org.modeshape.graph.query.QueryResults.Cursor#getRowIndex()
428 */
429 public int getRowIndex() {
430 return tupleIndex;
431 }
432
433 /**
434 * {@inheritDoc}
435 *
436 * @see org.modeshape.graph.query.QueryResults.Cursor#getValue(int)
437 */
438 public Object getValue( int columnNumber ) {
439 if (columnNumber >= columns.getColumnCount()) {
440 throw new IndexOutOfBoundsException();
441 }
442 if (currentTuple == null) {
443 throw new IllegalStateException(GraphI18n.nextMethodMustBeCalledBeforeGettingValue.text());
444 }
445 return currentTuple[columnNumber];
446 }
447
448 /**
449 * {@inheritDoc}
450 *
451 * @see org.modeshape.graph.query.QueryResults.Cursor#getValue(java.lang.String)
452 */
453 public Object getValue( String columnName ) {
454 if (currentTuple == null) {
455 throw new IllegalStateException(GraphI18n.nextMethodMustBeCalledBeforeGettingValue.text());
456 }
457 return currentTuple[columns.getColumnIndexForName(columnName)];
458 }
459 }
460 }