package org.hibernate.hql.internal.classic;
import java.util.LinkedList;
import java.util.Map;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.hql.internal.CollectionSubqueryFactory;
import org.hibernate.persister.collection.CollectionPropertyMapping;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
public class PathExpressionParser implements Parser {
private int dotcount;
private String currentName;
private String currentProperty;
private String oneToOneOwnerName;
private AssociationType ownerAssociationType;
private String[] columns;
private String collectionName;
private String collectionOwnerName;
private String collectionRole;
private final StringBuilder componentPath = new StringBuilder();
private Type type;
private final StringBuilder path = new StringBuilder();
private boolean ignoreInitialJoin;
private boolean continuation;
private JoinType joinType = JoinType.INNER_JOIN;
private boolean useThetaStyleJoin = true;
private PropertyMapping currentPropertyMapping;
private JoinSequence joinSequence;
private boolean expectingCollectionIndex;
private LinkedList collectionElements = new LinkedList();
void setJoinType(JoinType joinType) {
this.joinType = joinType;
}
void setUseThetaStyleJoin(boolean useThetaStyleJoin) {
this.useThetaStyleJoin = useThetaStyleJoin;
}
private void addJoin(String name, AssociationType joinableType) throws QueryException {
try {
joinSequence.addJoin( joinableType, name, joinType, currentColumns() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
}
private void addJoin(String name, AssociationType joinableType, String[] foreignKeyColumns) throws QueryException {
try {
joinSequence.addJoin( joinableType, name, joinType, foreignKeyColumns );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
}
String continueFromManyToMany(String entityName, String[] joinColumns, QueryTranslatorImpl q) throws QueryException {
start( q );
continuation = true;
currentName = q.createNameFor( entityName );
q.addType( currentName, entityName );
Queryable classPersister = q.getEntityPersister( entityName );
addJoin( currentName, q.getFactory().getTypeResolver().getTypeFactory().manyToOne( entityName ), joinColumns );
currentPropertyMapping = classPersister;
return currentName;
}
public void ignoreInitialJoin() {
ignoreInitialJoin = true;
}
public void token(String token, QueryTranslatorImpl q) throws QueryException {
if ( token != null ) path.append( token );
String alias = q.getPathAlias( path.toString() );
if ( alias != null ) {
reset( q );
currentName = alias;
currentPropertyMapping = q.getPropertyMapping( currentName );
if ( !ignoreInitialJoin ) {
JoinSequence ojf = q.getPathJoin( path.toString() );
try {
joinSequence.addCondition( ojf.toJoinFragment( q.getEnabledFilters(), true ).toWhereFragmentString() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
}
}
else if ( ".".equals( token ) ) {
dotcount++;
}
else {
if ( dotcount == 0 ) {
if ( !continuation ) {
if ( !q.isName( token ) ) throw new QueryException( "undefined alias: " + token );
currentName = token;
currentPropertyMapping = q.getPropertyMapping( currentName );
}
}
else if ( dotcount == 1 ) {
if ( currentName != null ) {
currentProperty = token;
}
else if ( collectionName != null ) {
continuation = false;
}
else {
throw new QueryException( "unexpected" );
}
}
else {
Type propertyType = getPropertyType();
if ( propertyType == null ) {
throw new QueryException( "unresolved property: " + path );
}
if ( propertyType.isComponentType() ) {
dereferenceComponent( token );
}
else if ( propertyType.isEntityType() ) {
if ( !isCollectionValued() ) dereferenceEntity( token, ( EntityType ) propertyType, q );
}
else if ( propertyType.isCollectionType() ) {
dereferenceCollection( token, ( ( CollectionType ) propertyType ).getRole(), q );
}
else {
if ( token != null ) throw new QueryException( "dereferenced: " + path );
}
}
}
}
private void dereferenceEntity(String propertyName, EntityType propertyType, QueryTranslatorImpl q)
throws QueryException {
boolean isIdShortcut = EntityPersister.ENTITY_ID.equals( propertyName ) &&
propertyType.isReferenceToPrimaryKey();
final String idPropertyName;
try {
idPropertyName = propertyType.getIdentifierOrUniqueKeyPropertyName( q.getFactory() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
boolean isNamedIdPropertyShortcut = idPropertyName != null
&& idPropertyName.equals( propertyName )
&& propertyType.isReferenceToPrimaryKey();
if ( isIdShortcut || isNamedIdPropertyShortcut ) {
if ( componentPath.length() > 0 ) componentPath.append( '.' );
componentPath.append( propertyName );
}
else {
String entityClass = propertyType.getAssociatedEntityName();
String name = q.createNameFor( entityClass );
q.addType( name, entityClass );
addJoin( name, propertyType );
if ( propertyType.isOneToOne() ) oneToOneOwnerName = currentName;
ownerAssociationType = propertyType;
currentName = name;
currentProperty = propertyName;
q.addPathAliasAndJoin( path.substring( 0, path.toString().lastIndexOf( '.' ) ), name, joinSequence.copy() );
componentPath.setLength( 0 );
currentPropertyMapping = q.getEntityPersister( entityClass );
}
}
private void dereferenceComponent(String propertyName) {
if ( propertyName != null ) {
if ( componentPath.length() > 0 ) componentPath.append( '.' );
componentPath.append( propertyName );
}
}
private void dereferenceCollection(String propertyName, String role, QueryTranslatorImpl q) throws QueryException {
collectionRole = role;
QueryableCollection collPersister = q.getCollectionPersister( role );
String name = q.createNameForCollection( role );
addJoin( name, collPersister.getCollectionType() );
collectionName = name;
collectionOwnerName = currentName;
currentName = name;
currentProperty = propertyName;
componentPath.setLength( 0 );
currentPropertyMapping = new CollectionPropertyMapping( collPersister );
}
private String getPropertyPath() {
if ( currentProperty == null ) {
return EntityPersister.ENTITY_ID;
}
else {
if ( componentPath.length() > 0 ) {
return new StringBuilder()
.append( currentProperty )
.append( '.' )
.append( componentPath.toString() )
.toString();
}
else {
return currentProperty;
}
}
}
private PropertyMapping getPropertyMapping() {
return currentPropertyMapping;
}
private void setType() throws QueryException {
if ( currentProperty == null ) {
type = getPropertyMapping().getType();
}
else {
type = getPropertyType();
}
}
protected Type getPropertyType() throws QueryException {
String propertyPath = getPropertyPath();
Type propertyType = getPropertyMapping().toType( propertyPath );
if ( propertyType == null ) {
throw new QueryException( "could not resolve property type: " + propertyPath );
}
return propertyType;
}
protected String[] currentColumns() throws QueryException {
String propertyPath = getPropertyPath();
String[] propertyColumns = getPropertyMapping().toColumns( currentName, propertyPath );
if ( propertyColumns == null ) {
throw new QueryException( "could not resolve property columns: " + propertyPath );
}
return propertyColumns;
}
private void reset(QueryTranslatorImpl q) {
dotcount = 0;
currentName = null;
currentProperty = null;
collectionName = null;
collectionRole = null;
componentPath.setLength( 0 );
type = null;
collectionName = null;
columns = null;
expectingCollectionIndex = false;
continuation = false;
currentPropertyMapping = null;
}
public void start(QueryTranslatorImpl q) {
if ( !continuation ) {
reset( q );
path.setLength( 0 );
joinSequence = new JoinSequence( q.getFactory() ).setUseThetaStyle( useThetaStyleJoin );
}
}
public void end(QueryTranslatorImpl q) throws QueryException {
ignoreInitialJoin = false;
Type propertyType = getPropertyType();
if ( propertyType != null && propertyType.isCollectionType() ) {
collectionRole = ( ( CollectionType ) propertyType ).getRole();
collectionName = q.createNameForCollection( collectionRole );
prepareForIndex( q );
}
else {
columns = currentColumns();
setType();
}
continuation = false;
}
private void prepareForIndex(QueryTranslatorImpl q) throws QueryException {
QueryableCollection collPersister = q.getCollectionPersister( collectionRole );
if ( !collPersister.hasIndex() ) throw new QueryException( "unindexed collection before []: " + path );
String[] indexCols = collPersister.getIndexColumnNames();
if ( indexCols.length != 1 ) throw new QueryException( "composite-index appears in []: " + path );
JoinSequence fromJoins = new JoinSequence( q.getFactory() )
.setUseThetaStyle( useThetaStyleJoin )
.setRoot( collPersister, collectionName )
.setNext( joinSequence.copy() );
if ( !continuation ) addJoin( collectionName, collPersister.getCollectionType() );
joinSequence.addCondition( collectionName + '.' + indexCols[0] + " = " );
CollectionElement elem = new CollectionElement();
elem.elementColumns = collPersister.getElementColumnNames(collectionName);
elem.elementType = collPersister.getElementType();
elem.isOneToMany = collPersister.isOneToMany();
elem.alias = collectionName;
elem.joinSequence = joinSequence;
collectionElements.addLast( elem );
setExpectingCollectionIndex();
q.addCollection( collectionName, collectionRole );
q.addFromJoinOnly( collectionName, fromJoins );
}
static final class CollectionElement {
Type elementType;
boolean isOneToMany;
String alias;
String[] elementColumns;
JoinSequence joinSequence;
StringBuilder indexValue = new StringBuilder();
}
public CollectionElement lastCollectionElement() {
return ( CollectionElement ) collectionElements.removeLast();
}
public void setLastCollectionElementIndexValue(String value) {
( ( CollectionElement ) collectionElements.getLast() ).indexValue.append( value );
}
public boolean isExpectingCollectionIndex() {
return expectingCollectionIndex;
}
protected void setExpectingCollectionIndex() throws QueryException {
expectingCollectionIndex = true;
}
public JoinSequence getWhereJoin() {
return joinSequence;
}
public String getWhereColumn() throws QueryException {
if ( columns.length != 1 ) {
throw new QueryException( "path expression ends in a composite value: " + path );
}
return columns[0];
}
public String[] getWhereColumns() {
return columns;
}
public Type getWhereColumnType() {
return type;
}
public String getName() {
return currentName == null ? collectionName : currentName;
}
public String getCollectionSubquery(Map enabledFilters) throws QueryException {
return CollectionSubqueryFactory.createCollectionSubquery( joinSequence, enabledFilters, currentColumns() );
}
public boolean isCollectionValued() throws QueryException {
return collectionName != null && !getPropertyType().isCollectionType();
}
public void addAssociation(QueryTranslatorImpl q) throws QueryException {
q.addJoin( getName(), joinSequence );
}
public String addFromAssociation(QueryTranslatorImpl q) throws QueryException {
if ( isCollectionValued() ) {
return addFromCollection( q );
}
else {
q.addFrom( currentName, joinSequence );
return currentName;
}
}
public String addFromCollection(QueryTranslatorImpl q) throws QueryException {
Type collectionElementType = getPropertyType();
if ( collectionElementType == null ) {
throw new QueryException( "must specify 'elements' for collection valued property in from clause: " + path );
}
if ( collectionElementType.isEntityType() ) {
QueryableCollection collectionPersister = q.getCollectionPersister( collectionRole );
Queryable entityPersister = ( Queryable ) collectionPersister.getElementPersister();
String clazz = entityPersister.getEntityName();
final String elementName;
if ( collectionPersister.isOneToMany() ) {
elementName = collectionName;
q.decoratePropertyMapping( elementName, collectionPersister );
}
else {
q.addCollection( collectionName, collectionRole );
elementName = q.createNameFor( clazz );
addJoin( elementName, ( AssociationType ) collectionElementType );
}
q.addFrom( elementName, clazz, joinSequence );
currentPropertyMapping = new CollectionPropertyMapping( collectionPersister );
return elementName;
}
else {
q.addFromCollection( collectionName, collectionRole, joinSequence );
return collectionName;
}
}
String getCollectionName() {
return collectionName;
}
String getCollectionRole() {
return collectionRole;
}
String getCollectionOwnerName() {
return collectionOwnerName;
}
String getOneToOneOwnerName() {
return oneToOneOwnerName;
}
AssociationType getOwnerAssociationType() {
return ownerAssociationType;
}
String getCurrentProperty() {
return currentProperty;
}
String getCurrentName() {
return currentName;
}
public void fetch(QueryTranslatorImpl q, String entityName) throws QueryException {
if ( isCollectionValued() ) {
q.setCollectionToFetch( getCollectionRole(), getCollectionName(), getCollectionOwnerName(), entityName );
}
else {
q.addEntityToFetch( entityName, getOneToOneOwnerName(), getOwnerAssociationType() );
}
}
}