KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hibernate > hql > ast > tree > DotNode


1 // $Id: DotNode.java,v 1.6 2005/07/20 19:35:21 steveebersole Exp $
2
package org.hibernate.hql.ast.tree;
3
4 import org.hibernate.QueryException;
5 import org.hibernate.engine.JoinSequence;
6 import org.hibernate.hql.CollectionProperties;
7 import org.hibernate.hql.antlr.SqlTokenTypes;
8 import org.hibernate.hql.ast.util.ASTPrinter;
9 import org.hibernate.hql.ast.util.ASTUtil;
10 import org.hibernate.hql.ast.util.ColumnHelper;
11 import org.hibernate.persister.collection.QueryableCollection;
12 import org.hibernate.persister.entity.EntityPersister;
13 import org.hibernate.sql.JoinFragment;
14 import org.hibernate.type.CollectionType;
15 import org.hibernate.type.EntityType;
16 import org.hibernate.type.Type;
17 import org.hibernate.util.StringHelper;
18
19 import antlr.SemanticException;
20 import antlr.collections.AST;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 /**
26  * Represents a reference to a property or alias expression. This should duplicate the relevant behaviors in
27  * PathExpressionParser.
28  * <hr>
29  * User: josh<br>
30  * Date: Dec 16, 2003<br>
31  * Time: 8:03:09 AM
32  */

33 public class DotNode extends FromReferenceNode implements DisplayableNode, SelectExpression {
34
35     /**
36      * A logger for this class.
37      */

38     private static final Log log = LogFactory.getLog( DotNode.class );
39
40     private static final int DEREF_UNKNOWN = 0;
41     private static final int DEREF_ENTITY = 1;
42     private static final int DEREF_COMPONENT = 2;
43     private static final int DEREF_COLLECTION = 3;
44     private static final int DEREF_PRIMITIVE = 4;
45     private static final int DEREF_IDENTIFIER = 5;
46     private static final int DEREF_JAVA_CONSTANT = 6;
47
48     /**
49      * The identifier that is the name of the property.
50      */

51     private String JavaDoc propertyName;
52     /**
53      * The full path, to the root alias of this dot node.
54      */

55     private String JavaDoc path;
56     /**
57      * The unresolved property path relative to this dot node.
58      */

59     private String JavaDoc propertyPath;
60
61     /**
62      * The column names that this resolves to.
63      */

64     private String JavaDoc[] columns;
65
66     /**
67      * The type of join to create. Default is an inner join.
68      */

69     private int joinType = JoinFragment.INNER_JOIN;
70
71     /**
72      * Fetch join or not.
73      */

74     private boolean fetch = false;
75
76     /**
77      * The type of dereference that hapened (DEREF_xxx).
78      */

79     private int dereferenceType = DEREF_UNKNOWN;
80
81     private FromElement impliedJoin;
82
83     /**
84      * Sets the join type for the '.' node (JoinFragment.XXX).
85      *
86      * @param joinType
87      * @see JoinFragment
88      */

89     public void setJoinType(int joinType) {
90         this.joinType = joinType;
91     }
92
93     private String JavaDoc[] getColumns() throws QueryException {
94         if ( columns == null ) {
95             // Use the table fromElement and the property name to get the array of column names.
96
String JavaDoc tableAlias = getLhs().getFromElement().getTableAlias();
97             columns = getFromElement().toColumns( tableAlias, propertyPath, false );
98         }
99         return columns;
100     }
101
102     public String JavaDoc getDisplayText() {
103         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
104         FromElement fromElement = getFromElement();
105         buf.append( "{propertyName=" ).append( propertyName );
106         buf.append( ",dereferenceType=" ).append( ASTPrinter.getConstantName( getClass(), dereferenceType ) );
107         buf.append( ",propertyPath=" ).append( propertyPath );
108         buf.append( ",path=" ).append( getPath() );
109         if ( fromElement != null ) {
110             buf.append( ",tableAlias=" ).append( fromElement.getTableAlias() );
111             buf.append( ",className=" ).append( fromElement.getClassName() );
112             buf.append( ",classAlias=" ).append( fromElement.getClassAlias() );
113         }
114         else {
115             buf.append( ",no from element" );
116         }
117         buf.append( '}' );
118         return buf.toString();
119     }
120
121     /**
122      * Resolves the left hand side of the DOT.
123      *
124      * @throws SemanticException
125      */

126     public void resolveFirstChild() throws SemanticException {
127         FromReferenceNode lhs = ( FromReferenceNode ) getFirstChild();
128         SqlNode property = ( SqlNode ) lhs.getNextSibling();
129
130         // Set the attributes of the property reference expression.
131
String JavaDoc propName = property.getText();
132         propertyName = propName;
133         // If the uresolved property path isn't set yet, just use the property name.
134
if ( propertyPath == null ) {
135             propertyPath = propName;
136         }
137         // Resolve the LHS fully, generate implicit joins. Pass in the property name so that the resolver can
138
// discover foreign key (id) properties.
139
lhs.resolve( true, true, null, this );
140         setFromElement( lhs.getFromElement() ); // The 'from element' that the property is in.
141
}
142     
143     public void resolveInFunctionCall(boolean generateJoin, boolean implicitJoin) throws SemanticException {
144         if ( isResolved() ) {
145             return;
146         }
147         Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.
148
if ( propertyType!=null && propertyType.isCollectionType() ) {
149             resolveIndex(null);
150         }
151         else {
152             resolveFirstChild();
153             super.resolve(generateJoin, implicitJoin);
154         }
155     }
156
157
158     public void resolveIndex(AST parent) throws SemanticException {
159         if ( isResolved() ) {
160             return;
161         }
162         Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.
163
dereferenceCollection( ( CollectionType ) propertyType, true, true, null, parent );
164     }
165
166     public void resolve(boolean generateJoin, boolean implicitJoin, String JavaDoc classAlias, AST parent)
167     throws SemanticException {
168         // If this dot has already been resolved, stop now.
169
if ( isResolved() ) {
170             return;
171         }
172         Type propertyType = prepareLhs(); // Prepare the left hand side and get the data type.
173

174         // If there is no data type for this node, and we're at the end of the path (top most dot node), then
175
// this might be a Java constant.
176
if ( propertyType == null ) {
177             if ( parent == null ) {
178                 getWalker().getLiteralProcessor().lookupConstant( this );
179             }
180             // If the propertyType is null and there isn't a parent, just
181
// stop now... there was a problem resolving the node anyway.
182
return;
183         }
184         // The property is a component...
185
if ( propertyType.isComponentType() ) {
186             dereferenceComponent( parent );
187             initText();
188         }
189         // The property is another class..
190
else if ( propertyType.isEntityType() ) {
191             dereferenceEntity( ( EntityType ) propertyType, implicitJoin, classAlias, generateJoin, parent );
192             initText();
193         }
194         // The property is a collection...
195
else if ( propertyType.isCollectionType() ) {
196             dereferenceCollection( ( CollectionType ) propertyType, implicitJoin, false, classAlias, parent );
197         }
198         else { // Otherwise, this is a primitive type.
199
dereferenceType = DEREF_PRIMITIVE;
200             initText();
201         }
202         setResolved();
203     }
204     
205     private void initText() {
206         String JavaDoc[] cols = getColumns();
207         String JavaDoc text = StringHelper.join( ", ", cols );
208         if ( cols.length > 1 && getWalker().isComparativeExpressionClause() ) {
209             text = "(" + text + ")";
210         }
211         setText( text );
212     }
213
214     private Type prepareLhs() throws SemanticException {
215         FromReferenceNode lhs = getLhs();
216         lhs.prepareForDot( propertyName );
217         Type propertyType = getDataType();
218         return propertyType;
219     }
220
221     private void dereferenceCollection(CollectionType collectionType, boolean implicitJoin, boolean indexed, String JavaDoc classAlias, AST parent)
222     throws SemanticException {
223         
224         dereferenceType = DEREF_COLLECTION;
225         String JavaDoc role = collectionType.getRole();
226         
227         //foo.bars.size (also handles deprecated stuff like foo.bars.maxelement for backwardness)
228
boolean isSizeProperty = getNextSibling()!=null &&
229             CollectionProperties.isAnyCollectionProperty( getNextSibling().getText() );
230
231         if ( isSizeProperty ) indexed = true; //yuck!
232

233         QueryableCollection queryableCollection = getSessionFactoryHelper().requireQueryableCollection( role );
234         String JavaDoc propName = getPath();
235         FromClause currentFromClause = getWalker().getCurrentFromClause();
236
237         //We do not look for an existing join on the same path, because
238
//it makes sense to join twice on the same collection role
239
FromElementFactory factory = new FromElementFactory(
240                 currentFromClause,
241                 getLhs().getFromElement(),
242                 propName,
243                 classAlias,
244                 getColumns(),
245                 implicitJoin
246         );
247         FromElement elem = factory.createCollection( queryableCollection, role, joinType, fetch, indexed );
248         
249         if ( log.isDebugEnabled() ) {
250             log.debug( "dereferenceCollection() : Created new FROM element for " + propName + " : " + elem );
251         }
252         
253         setImpliedJoin( elem );
254         setFromElement( elem ); // This 'dot' expression now refers to the resulting from element.
255

256         if ( isSizeProperty ) {
257             elem.setText("");
258             elem.setUseWhereFragment(false);
259         }
260         
261         if ( !implicitJoin ) {
262             EntityPersister entityPersister = elem.getEntityPersister();
263             if ( entityPersister != null ) {
264                 getWalker().addQuerySpaces( entityPersister.getQuerySpaces() );
265             }
266         }
267         getWalker().addQuerySpaces( queryableCollection.getCollectionSpaces() ); // Always add the collection's query spaces.
268
}
269
270     private void dereferenceEntity(EntityType entityType, boolean implicitJoin, String JavaDoc classAlias, boolean generateJoin, AST parent) throws SemanticException {
271         checkForCorrelatedSubquery( "dereferenceEntity" );
272         // If this is an entity inside a component reference, then generate the join.
273
if ( unresolvedComponent( generateJoin ) ) {
274             if ( log.isDebugEnabled() ) {
275                 log.debug( "dereferenceEntity() : resolving unresolved component '" + propertyPath + "' ... " );
276             }
277             dereferenceEntityJoin( classAlias, entityType, implicitJoin, parent );
278             return;
279         }
280
281         // Only join to the entity table if:
282
// 1) we were instructed to generate any needed joins (generateJoins==true)
283
// AND
284
// 2) EITHER:
285
// A) our parent represents a further dereference of this entity to anything
286
// other than the entity's id property
287
// OR
288
// B) this node is in any clause, other than the select clause (unless that
289
// select clause is part of a scalar query :/ )
290
DotNode parentAsDotNode = null;
291         String JavaDoc property = propertyName;
292         boolean joinIsNeeded = false;
293
294         if ( isDotNode( parent ) ) {
295             parentAsDotNode = ( DotNode ) parent;
296             property = parentAsDotNode.propertyName;
297             joinIsNeeded = generateJoin && !isReferenceToPrimaryKey( parentAsDotNode.propertyName, entityType );
298         }
299         else {
300             joinIsNeeded = generateJoin && ( !getWalker().isInSelect() || !getWalker().isShallowQuery() );
301         }
302
303         if ( joinIsNeeded ) {
304             dereferenceEntityJoin( classAlias, entityType, implicitJoin, parent );
305         }
306         else {
307             dereferenceEntityIdentifier( property, parentAsDotNode );
308         }
309
310     }
311
312     private boolean unresolvedComponent(boolean generateJoin) {
313         AST c = getFirstChild();
314         if ( generateJoin && isDotNode( c ) ) {
315             DotNode dot = ( DotNode ) c;
316             if ( dot.dereferenceType == DEREF_COMPONENT || dot.dereferenceType == DEREF_IDENTIFIER ) {
317                 if ( StringHelper.isNotEmpty( propertyPath ) ) {
318                     return true;
319                 }
320             }
321         }
322         return false;
323     }
324
325     private boolean isDotNode(AST n) {
326         return n != null && n.getType() == SqlTokenTypes.DOT;
327     }
328
329     private void dereferenceEntityJoin(String JavaDoc classAlias, EntityType propertyType, boolean impliedJoin, AST parent)
330     throws SemanticException {
331         dereferenceType = DEREF_ENTITY;
332         if ( log.isDebugEnabled() ) {
333             log.debug( "dereferenceEntityJoin() : generating join for " + propertyName + " in "
334                     + getFromElement().getClassName() + " "
335                     + ( ( classAlias == null ) ? "{no alias}" : "(" + classAlias + ")" )
336                     + " parent = " + ASTUtil.getDebugString( parent )
337             );
338         }
339         // Create a new FROM node for the referenced class.
340
String JavaDoc associatedEntityName = propertyType.getAssociatedEntityName();
341         String JavaDoc tableAlias = getAliasGenerator().createName( associatedEntityName );
342
343         String JavaDoc[] joinColumns = getColumns();
344         String JavaDoc joinPath = getPath();
345
346         if ( impliedJoin && getWalker().isInFrom() ) {
347             int impliedJoinType = getWalker().getImpliedJoinType();
348             joinType = impliedJoinType;
349         }
350
351         FromClause currentFromClause = getWalker().getCurrentFromClause();
352         FromElement elem = null;
353         elem = currentFromClause.findJoinByPath( joinPath );
354
355 ///////////////////////////////////////////////////////////////////////////////
356
//
357
// This is the piece which recognizes the condition where an implicit join path
358
// resolved earlier in a correlated subquery is now being referenced in the
359
// outer query. For 3.0final, we just let this generate a second join (which
360
// is exactly how the old parser handles this). Eventually we need to add this
361
// logic back in and complete the logic in FromClause.promoteJoin; however,
362
// FromClause.promoteJoin has its own difficulties (see the comments in
363
// FromClause.promoteJoin).
364
//
365
// if ( elem == null ) {
366
// // see if this joinPath has been used in a "child" FromClause, and if so
367
// // promote that element to the outer query
368
// FromClause currentNodeOwner = getFromElement().getFromClause();
369
// FromClause currentJoinOwner = currentNodeOwner.locateChildFromClauseWithJoinByPath( joinPath );
370
// if ( currentJoinOwner != null && currentNodeOwner != currentJoinOwner ) {
371
// elem = currentJoinOwner.findJoinByPathLocal( joinPath );
372
// if ( elem != null ) {
373
// currentFromClause.promoteJoin( elem );
374
// // EARLY EXIT!!!
375
// return;
376
// }
377
// }
378
// }
379
//
380
///////////////////////////////////////////////////////////////////////////////
381

382         if ( elem == null ) {
383             // If this is an implied join in a from element, then use the impled join type which is part of the
384
// tree parser's state (set by the gramamar actions).
385
JoinSequence joinSequence = getSessionFactoryHelper()
386                 .createJoinSequence( impliedJoin, propertyType, tableAlias, joinType, joinColumns );
387
388             FromElementFactory factory = new FromElementFactory(
389                     currentFromClause,
390                     getLhs().getFromElement(),
391                     joinPath,
392                     classAlias,
393                     joinColumns,
394                     impliedJoin
395             );
396             elem = factory.createEntityJoin(
397                     associatedEntityName,
398                     tableAlias,
399                     joinSequence,
400                     fetch,
401                     getWalker().isInFrom(),
402                     propertyType
403             );
404         }
405         else {
406             currentFromClause.addDuplicateAlias(classAlias, elem);
407         }
408         setImpliedJoin( elem );
409         getWalker().addQuerySpaces( elem.getEntityPersister().getQuerySpaces() );
410         setFromElement( elem ); // This 'dot' expression now refers to the resulting from element.
411
}
412
413     private void setImpliedJoin(FromElement elem) {
414         this.impliedJoin = elem;
415         if ( getFirstChild().getType() == SqlTokenTypes.DOT ) {
416             DotNode dotLhs = ( DotNode ) getFirstChild();
417             if ( dotLhs.getImpliedJoin() != null ) {
418                 this.impliedJoin = dotLhs.getImpliedJoin();
419             }
420         }
421     }
422
423     public FromElement getImpliedJoin() {
424         return impliedJoin;
425     }
426
427     private boolean isReferenceToPrimaryKey(String JavaDoc propertyName, EntityType propertyType) {
428         if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) {
429             // the referenced node text is the special 'id'
430
return propertyType.isReferenceToPrimaryKey();
431         }
432         else {
433             String JavaDoc keyPropertyName = getSessionFactoryHelper()
434                     .getIdentifierOrUniqueKeyPropertyName( propertyType );
435             return keyPropertyName != null && keyPropertyName.equals( propertyName );
436         }
437     }
438
439 // private boolean isPrimaryKeyReference(String property, EntityType propertyType) {
440
// boolean isIdShortcut = EntityPersister.ENTITY_ID.equals( property ) &&
441
// propertyType.isReferenceToPrimaryKey();
442
// return isIdShortcut;
443
// }
444
//
445
// private boolean isNamedIdPropertyShortcut(EntityType propertyType, String property) {
446
// final String idPropertyName = getSessionFactoryHelper()
447
// .getIdentifierOrUniqueKeyPropertyName( propertyType );
448
// boolean isNamedIdPropertyShortcut = idPropertyName != null &&
449
// idPropertyName.equals( property );
450
// return isNamedIdPropertyShortcut;
451
// }
452

453     private void checkForCorrelatedSubquery(String JavaDoc methodName) {
454         if ( isCorrelatedSubselect() ) {
455             if ( log.isDebugEnabled() ) {
456                 log.debug( methodName + "() : correlated subquery" );
457             }
458         }
459     }
460
461     private boolean isCorrelatedSubselect() {
462         return getWalker().isSubQuery() &&
463             getFromElement().getFromClause() != getWalker().getCurrentFromClause();
464     }
465
466     private void dereferenceComponent(AST parent) {
467         dereferenceType = DEREF_COMPONENT;
468         setPropertyNameAndPath( parent );
469     }
470
471     private void dereferenceEntityIdentifier(String JavaDoc propertyName, DotNode dotParent) {
472         // special shortcut for id properties, skip the join!
473
// this must only occur at the _end_ of a path expression
474
if ( log.isDebugEnabled() ) {
475             log.debug( "dereferenceShortcut() : property " +
476                 propertyName + " in " + getFromElement().getClassName() +
477                 " does not require a join." );
478         }
479
480         initText();
481         setPropertyNameAndPath( dotParent ); // Set the unresolved path in this node and the parent.
482
// Set the text for the parent.
483
if ( dotParent != null ) {
484             dotParent.dereferenceType = DEREF_IDENTIFIER;
485             dotParent.setText( getText() );
486             dotParent.columns = getColumns();
487         }
488     }
489
490     private void setPropertyNameAndPath(AST parent) {
491         if ( isDotNode( parent ) ) {
492             DotNode dotNode = ( DotNode ) parent;
493             AST lhs = dotNode.getFirstChild();
494             AST rhs = lhs.getNextSibling();
495             propertyName = rhs.getText();
496             propertyPath = propertyPath + "." + propertyName; // Append the new property name onto the unresolved path.
497
dotNode.propertyPath = propertyPath;
498             if ( log.isDebugEnabled() ) {
499                 log.debug( "Unresolved property path is now '" + dotNode.propertyPath + "'" );
500             }
501         }
502         else {
503             // Handle "select foo.component from Foo foo", or even "where foo.component = bar.component"
504
AST lhs = getFirstChild();
505             AST rhs = lhs.getNextSibling();
506             propertyPath = rhs.getText();
507         }
508     }
509
510     public Type getDataType() {
511         if ( super.getDataType() == null ) {
512             FromElement fromElement = getLhs().getFromElement();
513             if ( fromElement == null ) {
514                 return null;
515             }
516             // If the lhs is a collection, use CollectionPropertyMapping
517
Type propertyType = fromElement.getPropertyType( propertyName, propertyPath );
518             if ( log.isDebugEnabled() ) {
519                 log.debug( "getDataType() : " + propertyPath + " -> " + propertyType );
520             }
521             super.setDataType( propertyType );
522         }
523         return super.getDataType();
524     }
525
526     public void setPropertyPath(String JavaDoc propertyPath) {
527         this.propertyPath = propertyPath;
528     }
529
530     public String JavaDoc getPropertyPath() {
531         return propertyPath;
532     }
533
534     public FromReferenceNode getLhs() {
535         FromReferenceNode lhs = ( ( FromReferenceNode ) getFirstChild() );
536         if ( lhs == null ) {
537             throw new IllegalStateException JavaDoc( "DOT node with no left-hand-side!" );
538         }
539         return lhs;
540     }
541
542     /**
543      * Returns the full path of the node.
544      *
545      * @return the full path of the node.
546      */

547     public String JavaDoc getPath() {
548         if ( path == null ) {
549             FromReferenceNode lhs = getLhs();
550             if ( lhs == null ) {
551                 path = getText();
552             }
553             else {
554                 SqlNode rhs = ( SqlNode ) lhs.getNextSibling();
555                 path = lhs.getPath() + "." + rhs.getOriginalText();
556             }
557         }
558         return path;
559     }
560
561     public void setFetch(boolean fetch) {
562         this.fetch = fetch;
563     }
564
565     public void setScalarColumnText(int i) throws SemanticException {
566         String JavaDoc[] sqlColumns = getColumns();
567         ColumnHelper.generateScalarColumns( this, sqlColumns, i );
568     }
569
570     /**
571      * Special method to resolve expressions in the SELECT list.
572      *
573      * @throws SemanticException if this cannot be resolved.
574      */

575     public void resolveSelectExpression() throws SemanticException {
576         if ( getWalker().isShallowQuery() || getWalker().getCurrentFromClause().isSubQuery() ) {
577             resolve(false, true);
578         }
579         else {
580             resolve(true, false);
581             Type type = getDataType();
582             if ( type.isEntityType() ) {
583                 FromElement fromElement = getFromElement();
584                 fromElement.setIncludeSubclasses( true ); // Tell the destination fromElement to 'includeSubclasses'.
585
if ( useThetaStyleImplicitJoins ) {
586                     fromElement.getJoinSequence().setUseThetaStyle( true ); // Use theta style (for regression)
587
// Move the node up, after the origin node.
588
FromElement origin = fromElement.getOrigin();
589                     if ( origin != null ) {
590                         ASTUtil.makeSiblingOfParent( origin, fromElement );
591                     }
592                 }
593             }
594         }
595     }
596     
597     /**
598      * Used ONLY for regression testing!
599      */

600     public static boolean useThetaStyleImplicitJoins = false;
601
602     public void setResolvedConstant(String JavaDoc text) {
603         path = text;
604         dereferenceType = DEREF_JAVA_CONSTANT;
605         setResolved(); // Don't resolve the node again.
606
}
607 }
608
Popular Tags