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.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 }