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.ArrayList;
27  import java.util.Comparator;
28  import java.util.List;
29  import net.jcip.annotations.NotThreadSafe;
30  import org.modeshape.common.collection.Problems;
31  import org.modeshape.graph.Location;
32  import org.modeshape.graph.property.Path;
33  import org.modeshape.graph.query.QueryContext;
34  import org.modeshape.graph.query.QueryResults.Columns;
35  import org.modeshape.graph.query.model.ArithmeticOperand;
36  import org.modeshape.graph.query.model.DynamicOperand;
37  import org.modeshape.graph.query.model.FullTextSearchScore;
38  import org.modeshape.graph.query.model.Length;
39  import org.modeshape.graph.query.model.LowerCase;
40  import org.modeshape.graph.query.model.NodeDepth;
41  import org.modeshape.graph.query.model.NodeLocalName;
42  import org.modeshape.graph.query.model.NodeName;
43  import org.modeshape.graph.query.model.NodePath;
44  import org.modeshape.graph.query.model.PropertyValue;
45  import org.modeshape.graph.query.model.ReferenceValue;
46  import org.modeshape.graph.query.model.TypeSystem;
47  import org.modeshape.graph.query.model.UpperCase;
48  import org.modeshape.graph.query.model.TypeSystem.TypeFactory;
49  import org.modeshape.graph.query.validate.Schemata;
50  import org.modeshape.graph.query.validate.Schemata.Column;
51  import org.modeshape.graph.query.validate.Schemata.Table;
52  
53  /**
54   * A component that performs (some) portion of the query processing by {@link #execute() returning the tuples} that result from
55   * this stage of processing. Processing components are designed to be assembled into a processing structure, with a single
56   * component at the top that returns the results of a query.
57   */
58  @NotThreadSafe
59  public abstract class ProcessingComponent {
60  
61      private final QueryContext context;
62      private final Columns columns;
63  
64      protected ProcessingComponent( QueryContext context,
65                                     Columns columns ) {
66          this.context = context;
67          this.columns = columns;
68          assert this.context != null;
69          assert this.columns != null;
70      }
71  
72      /**
73       * Get the context in which this query is being executed.
74       * 
75       * @return context
76       */
77      public final QueryContext getContext() {
78          return context;
79      }
80  
81      /**
82       * Get the column definitions.
83       * 
84       * @return the column mappings; never null
85       */
86      public final Columns getColumns() {
87          return columns;
88      }
89  
90      /**
91       * Get the container for problems encountered during processing.
92       * 
93       * @return the problems container; never null
94       */
95      protected final Problems problems() {
96          return context.getProblems();
97      }
98  
99      /**
100      * Execute this stage of processing and return the resulting tuples that each conform to the {@link #getColumns() columns}.
101      * 
102      * @return the list of tuples, where each tuple corresonds to the {@link #getColumns() columns}; never null
103      */
104     public abstract List<Object[]> execute();
105 
106     /**
107      * Close these results, allowing any resources to be released.
108      */
109     public void close() {
110     }
111 
112     /**
113      * Utility method to create a new tuples list that is empty.
114      * 
115      * @return the empty tuples list; never null
116      */
117     protected List<Object[]> emptyTuples() {
118         return new ArrayList<Object[]>(0);
119     }
120 
121     /**
122      * Interface for evaluating a {@link DynamicOperand} to return the resulting value.
123      */
124     protected static interface DynamicOperation {
125         /**
126          * Get the expected type of the result of this evaluation
127          * 
128          * @return the property type; never null
129          */
130         String getExpectedType();
131 
132         /**
133          * Perform the dynamic evaluation to obtain the desired result.
134          * 
135          * @param tuple the tuple; never null
136          * @return the value that results from dynamically evaluating the operand against the tuple; may be null
137          */
138         Object evaluate( Object[] tuple );
139     }
140 
141     /**
142      * Create a {@link DynamicOperation} instance that is able to evaluate the supplied {@link DynamicOperand}.
143      * 
144      * @param typeSystem the type system; may not be null
145      * @param schemata the schemata; may not be null
146      * @param columns the definition of the result columns and the tuples; may not be null
147      * @param operand the dynamic operand that is to be evaluated by the returned object; may not be null
148      * @return the dynamic operand operation; never null
149      */
150     protected DynamicOperation createDynamicOperation( final TypeSystem typeSystem,
151                                                        Schemata schemata,
152                                                        Columns columns,
153                                                        DynamicOperand operand ) {
154         assert operand != null;
155         assert columns != null;
156         assert context != null;
157         if (operand instanceof PropertyValue) {
158             PropertyValue propValue = (PropertyValue)operand;
159             String propertyName = propValue.getPropertyName();
160             String selectorName = propValue.getSelectorName().getName();
161             final int index = columns.getColumnIndexForProperty(selectorName, propertyName);
162             // Find the expected property type of the value ...
163             Table table = schemata.getTable(propValue.getSelectorName());
164             Column schemaColumn = table.getColumn(propertyName);
165             final String expectedType = schemaColumn.getPropertyType();
166             final TypeFactory<?> typeFactory = typeSystem.getTypeFactory(expectedType);
167             return new DynamicOperation() {
168                 public String getExpectedType() {
169                     return expectedType;
170                 }
171 
172                 public Object evaluate( Object[] tuple ) {
173                     return typeFactory.create(tuple[index]);
174                 }
175             };
176         }
177         if (operand instanceof ReferenceValue) {
178             ReferenceValue refValue = (ReferenceValue)operand;
179             String propertyName = refValue.getPropertyName();
180             String selectorName = refValue.getSelectorName().getName();
181             final int index = columns.getColumnIndexForProperty(selectorName, propertyName);
182             // Find the expected property type of the value ...
183             Table table = schemata.getTable(refValue.getSelectorName());
184             Column schemaColumn = table.getColumn(propertyName);
185             final String expectedType = schemaColumn.getPropertyType();
186             final TypeFactory<?> typeFactory = typeSystem.getTypeFactory(expectedType);
187             return new DynamicOperation() {
188                 public String getExpectedType() {
189                     return expectedType;
190                 }
191 
192                 public Object evaluate( Object[] tuple ) {
193                     return typeFactory.create(tuple[index]);
194                 }
195             };
196         }
197         final TypeFactory<String> stringFactory = typeSystem.getStringFactory();
198         if (operand instanceof Length) {
199             Length length = (Length)operand;
200             PropertyValue value = length.getPropertyValue();
201             String propertyName = value.getPropertyName();
202             String selectorName = value.getSelectorName().getName();
203             final int index = columns.getColumnIndexForProperty(selectorName, propertyName);
204             // Find the expected property type of the value ...
205             Table table = context.getSchemata().getTable(value.getSelectorName());
206             Column schemaColumn = table.getColumn(propertyName);
207             final String expectedType = schemaColumn.getPropertyType();
208             final TypeFactory<?> typeFactory = typeSystem.getTypeFactory(expectedType);
209             final TypeFactory<Long> longFactory = typeSystem.getLongFactory();
210             return new DynamicOperation() {
211                 public String getExpectedType() {
212                     return longFactory.getTypeName(); // length is always LONG
213                 }
214 
215                 public Object evaluate( Object[] tuple ) {
216                     Object value = tuple[index];
217                     return typeFactory.length(typeFactory.create(value));
218                 }
219             };
220         }
221         if (operand instanceof LowerCase) {
222             LowerCase lowerCase = (LowerCase)operand;
223             final DynamicOperation delegate = createDynamicOperation(typeSystem, schemata, columns, lowerCase.getOperand());
224             return new DynamicOperation() {
225                 public String getExpectedType() {
226                     return stringFactory.getTypeName();
227                 }
228 
229                 public Object evaluate( Object[] tuple ) {
230                     String result = stringFactory.create(delegate.evaluate(tuple));
231                     return result != null ? result.toLowerCase() : null;
232                 }
233             };
234         }
235         if (operand instanceof UpperCase) {
236             UpperCase upperCase = (UpperCase)operand;
237             final DynamicOperation delegate = createDynamicOperation(typeSystem, schemata, columns, upperCase.getOperand());
238             return new DynamicOperation() {
239                 public String getExpectedType() {
240                     return stringFactory.getTypeName();
241                 }
242 
243                 public Object evaluate( Object[] tuple ) {
244                     String result = stringFactory.create(delegate.evaluate(tuple));
245                     return result != null ? result.toUpperCase() : null;
246                 }
247             };
248         }
249         if (operand instanceof NodeDepth) {
250             NodeDepth nodeDepth = (NodeDepth)operand;
251             final int locationIndex = columns.getLocationIndex(nodeDepth.getSelectorName().getName());
252             return new DynamicOperation() {
253                 public String getExpectedType() {
254                     return typeSystem.getLongFactory().getTypeName(); // depth is always LONG
255                 }
256 
257                 public Object evaluate( Object[] tuple ) {
258                     Location location = (Location)tuple[locationIndex];
259                     if (location == null) return null;
260                     Path path = location.getPath();
261                     assert path != null;
262                     return new Long(path.size());
263                 }
264             };
265         }
266         if (operand instanceof NodePath) {
267             NodePath nodePath = (NodePath)operand;
268             final int locationIndex = columns.getLocationIndex(nodePath.getSelectorName().getName());
269             return new DynamicOperation() {
270                 public String getExpectedType() {
271                     return stringFactory.getTypeName();
272                 }
273 
274                 public Object evaluate( Object[] tuple ) {
275                     Location location = (Location)tuple[locationIndex];
276                     if (location == null) return null;
277                     assert location.getPath() != null;
278                     return stringFactory.create(location.getPath());
279                 }
280             };
281         }
282         if (operand instanceof NodeName) {
283             NodeName nodeName = (NodeName)operand;
284             final int locationIndex = columns.getLocationIndex(nodeName.getSelectorName().getName());
285             return new DynamicOperation() {
286                 public String getExpectedType() {
287                     return stringFactory.getTypeName();
288                 }
289 
290                 public Object evaluate( Object[] tuple ) {
291                     Location location = (Location)tuple[locationIndex];
292                     if (location == null) return null;
293                     Path path = location.getPath();
294                     assert path != null;
295                     return path.isRoot() ? "" : stringFactory.create(location.getPath().getLastSegment().getName());
296                 }
297             };
298         }
299         if (operand instanceof NodeLocalName) {
300             NodeLocalName nodeName = (NodeLocalName)operand;
301             final int locationIndex = columns.getLocationIndex(nodeName.getSelectorName().getName());
302             return new DynamicOperation() {
303                 public String getExpectedType() {
304                     return stringFactory.getTypeName();
305                 }
306 
307                 public Object evaluate( Object[] tuple ) {
308                     Location location = (Location)tuple[locationIndex];
309                     if (location == null) return null;
310                     Path path = location.getPath();
311                     assert path != null;
312                     return path.isRoot() ? "" : location.getPath().getLastSegment().getName().getLocalName();
313                 }
314             };
315         }
316         if (operand instanceof FullTextSearchScore) {
317             FullTextSearchScore score = (FullTextSearchScore)operand;
318             String selectorName = score.getSelectorName().getName();
319             final int index = columns.getFullTextSearchScoreIndexFor(selectorName);
320             final TypeFactory<Double> doubleFactory = typeSystem.getDoubleFactory();
321             if (index < 0) {
322                 // No full-text search score for this selector, so return 0.0d;
323                 return new DynamicOperation() {
324                     public String getExpectedType() {
325                         return doubleFactory.getTypeName();
326                     }
327 
328                     public Object evaluate( Object[] tuple ) {
329                         return new Double(0.0d);
330                     }
331                 };
332             }
333             return new DynamicOperation() {
334                 public String getExpectedType() {
335                     return doubleFactory.getTypeName();
336                 }
337 
338                 public Object evaluate( Object[] tuple ) {
339                     return tuple[index];
340                 }
341             };
342         }
343         if (operand instanceof ArithmeticOperand) {
344             ArithmeticOperand arith = (ArithmeticOperand)operand;
345             final DynamicOperation leftOp = createDynamicOperation(typeSystem, schemata, columns, arith.getLeft());
346             final DynamicOperation rightOp = createDynamicOperation(typeSystem, schemata, columns, arith.getRight());
347             // compute the expected (common) type ...
348             String leftType = leftOp.getExpectedType();
349             String rightType = rightOp.getExpectedType();
350             final String commonType = typeSystem.getCompatibleType(leftType, rightType);
351             if (typeSystem.getDoubleFactory().getTypeName().equals(commonType)) {
352                 final TypeFactory<Double> commonTypeFactory = typeSystem.getDoubleFactory();
353                 switch (arith.getOperator()) {
354                     case ADD:
355                         return new DynamicOperation() {
356                             public String getExpectedType() {
357                                 return commonType;
358                             }
359 
360                             public Object evaluate( Object[] tuple ) {
361                                 Double right = commonTypeFactory.create(rightOp.evaluate(tuple));
362                                 Double left = commonTypeFactory.create(leftOp.evaluate(tuple));
363                                 if (right == null) return left;
364                                 if (left == null) return right;
365                                 return left.doubleValue() / right.doubleValue();
366                             }
367                         };
368                     case SUBTRACT:
369                         return new DynamicOperation() {
370                             public String getExpectedType() {
371                                 return commonType;
372                             }
373 
374                             public Object evaluate( Object[] tuple ) {
375                                 Double right = commonTypeFactory.create(rightOp.evaluate(tuple));
376                                 Double left = commonTypeFactory.create(leftOp.evaluate(tuple));
377                                 if (right == null) return left;
378                                 if (left == null) left = 0.0d;
379                                 return left.doubleValue() * right.doubleValue();
380                             }
381                         };
382                     case MULTIPLY:
383                         return new DynamicOperation() {
384                             public String getExpectedType() {
385                                 return commonType;
386                             }
387 
388                             public Object evaluate( Object[] tuple ) {
389                                 Double right = commonTypeFactory.create(rightOp.evaluate(tuple));
390                                 Double left = commonTypeFactory.create(leftOp.evaluate(tuple));
391                                 if (right == null || left == null) return null;
392                                 return left.doubleValue() * right.doubleValue();
393                             }
394                         };
395                     case DIVIDE:
396                         return new DynamicOperation() {
397                             public String getExpectedType() {
398                                 return commonType;
399                             }
400 
401                             public Object evaluate( Object[] tuple ) {
402                                 Double right = commonTypeFactory.create(rightOp.evaluate(tuple));
403                                 Double left = commonTypeFactory.create(leftOp.evaluate(tuple));
404                                 if (right == null || left == null) return null;
405                                 return left.doubleValue() / right.doubleValue();
406                             }
407                         };
408                 }
409             } else if (typeSystem.getLongFactory().getTypeName().equals(commonType)) {
410                 final TypeFactory<Long> commonTypeFactory = typeSystem.getLongFactory();
411                 switch (arith.getOperator()) {
412                     case ADD:
413                         return new DynamicOperation() {
414                             public String getExpectedType() {
415                                 return commonType;
416                             }
417 
418                             public Object evaluate( Object[] tuple ) {
419                                 Long right = commonTypeFactory.create(rightOp.evaluate(tuple));
420                                 Long left = commonTypeFactory.create(leftOp.evaluate(tuple));
421                                 if (right == null) return left;
422                                 if (left == null) return right;
423                                 return left.longValue() / right.longValue();
424                             }
425                         };
426                     case SUBTRACT:
427                         return new DynamicOperation() {
428                             public String getExpectedType() {
429                                 return commonType;
430                             }
431 
432                             public Object evaluate( Object[] tuple ) {
433                                 Long right = commonTypeFactory.create(rightOp.evaluate(tuple));
434                                 Long left = commonTypeFactory.create(leftOp.evaluate(tuple));
435                                 if (right == null) return left;
436                                 if (left == null) left = 0L;
437                                 return left.longValue() * right.longValue();
438                             }
439                         };
440                     case MULTIPLY:
441                         return new DynamicOperation() {
442                             public String getExpectedType() {
443                                 return commonType;
444                             }
445 
446                             public Object evaluate( Object[] tuple ) {
447                                 Long right = commonTypeFactory.create(rightOp.evaluate(tuple));
448                                 Long left = commonTypeFactory.create(leftOp.evaluate(tuple));
449                                 if (right == null || left == null) return null;
450                                 return left.longValue() * right.longValue();
451                             }
452                         };
453                     case DIVIDE:
454                         return new DynamicOperation() {
455                             public String getExpectedType() {
456                                 return commonType;
457                             }
458 
459                             public Object evaluate( Object[] tuple ) {
460                                 Long right = commonTypeFactory.create(rightOp.evaluate(tuple));
461                                 Long left = commonTypeFactory.create(leftOp.evaluate(tuple));
462                                 if (right == null || left == null) return null;
463                                 return left.longValue() / right.longValue();
464                             }
465                         };
466                 }
467             }
468         }
469         assert false;
470         return null;
471     }
472 
473     protected Comparator<Object[]> createSortComparator( QueryContext context,
474                                                          Columns columns ) {
475         assert context != null;
476         final int numLocations = columns.getLocationCount();
477         assert numLocations > 0;
478         final Comparator<Location> typeComparator = Location.comparator();
479         if (numLocations == 1) {
480             // We can do this a tad faster if we know there is only one Location object ...
481             final int locationIndex = columns.getColumnCount();
482             return new Comparator<Object[]>() {
483                 public int compare( Object[] tuple1,
484                                     Object[] tuple2 ) {
485                     Location value1 = (Location)tuple1[locationIndex];
486                     Location value2 = (Location)tuple2[locationIndex];
487                     return typeComparator.compare(value1, value2);
488                 }
489             };
490         }
491         final int firstLocationIndex = columns.getColumnCount();
492         return new Comparator<Object[]>() {
493             public int compare( Object[] tuple1,
494                                 Object[] tuple2 ) {
495                 int result = 0;
496                 for (int locationIndex = firstLocationIndex; locationIndex != numLocations; ++locationIndex) {
497                     Location value1 = (Location)tuple1[locationIndex];
498                     Location value2 = (Location)tuple2[locationIndex];
499                     result = typeComparator.compare(value1, value2);
500                     if (result != 0) return result;
501                 }
502                 return result;
503             }
504         };
505     }
506 }