Open Source Repository

Home /hibernate/hibernate-3.2.4.ga | Repository Home



org/hibernate/hql/QuerySplitter.java
//$Id: QuerySplitter.java 7646 2005-07-25 07:37:13Z oneovthafew $
package org.hibernate.hql;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.MappingException;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.hql.classic.ParserHelper;
import org.hibernate.util.StringHelper;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

/**
 * Provides query splitting methods, which were originally in QueryTranslator.
 * <br>
 * TODO: This will need to be refactored at some point.
 *
 * @author josh Mar 14, 2004 10:50:23 AM
 */
public final class QuerySplitter {

  private static final Log log = LogFactory.getLog( QuerySplitter.class );

  private static final Set BEFORE_CLASS_TOKENS = new HashSet();
  private static final Set NOT_AFTER_CLASS_TOKENS = new HashSet();

  static {
    BEFORE_CLASS_TOKENS.add( "from" );
    BEFORE_CLASS_TOKENS.add( "delete" );
    BEFORE_CLASS_TOKENS.add( "update" );
    //beforeClassTokens.add("new"); DEFINITELY DON'T HAVE THIS!!
    BEFORE_CLASS_TOKENS.add( "," );
    NOT_AFTER_CLASS_TOKENS.add( "in" );
    //notAfterClassTokens.add(",");
    NOT_AFTER_CLASS_TOKENS.add( "from" );
    NOT_AFTER_CLASS_TOKENS.add( ")" );
  }

  /**
   * Private empty constructor.
   * (or else checkstyle says: 'warning: Utility classes should not have a public or default constructor.')
   */
  private QuerySplitter() {
  }

  /**
   * Handle Hibernate "implicit" polymorphism, by translating the query string into
   * several "concrete" queries against mapped classes.
   */
  public static String[] concreteQueries(String query, SessionFactoryImplementor factory) throws MappingException {

    //scan the query string for class names appearing in the from clause and replace
    //with all persistent implementors of the class/interface, returning multiple
    //query strings (make sure we don't pick up a class in the select clause!)

    //TODO: this is one of the ugliest and most fragile pieces of code in Hibernate....

    String[] tokens = StringHelper.split( StringHelper.WHITESPACE + "(),", query, true );
    if ( tokens.length == 0 ) return new String[]{query}; // just especially for the trivial collection filter
    ArrayList placeholders = new ArrayList();
    ArrayList replacements = new ArrayList();
    StringBuffer templateQuery = new StringBuffer( 40 );
    int count = 0;
    String last = null;
    int nextIndex = 0;
    String next = null;
    boolean isSelectClause = false;

    templateQuery.append( tokens[0] );
    if ( "select".equals( tokens[0].toLowerCase() ) ) isSelectClause = true;
        
    for ( int i = 1; i < tokens.length; i++ ) {

      //update last non-whitespace token, if necessary
      if ( !ParserHelper.isWhitespace( tokens[i - 1] ) ) last = tokens[i - 1].toLowerCase();

      // select-range is terminated by declaration of "from"
      if ( "from".equals( tokens[i].toLowerCase() ) ) isSelectClause = false;

      String token = tokens[i];
      if ( !ParserHelper.isWhitespace( token ) || last == null ) {

        //scan for next non-whitespace token
        if ( nextIndex <= i ) {
          for ( nextIndex = i + 1; nextIndex < tokens.length; nextIndex++ ) {
            next = tokens[nextIndex].toLowerCase();
            if ( !ParserHelper.isWhitespace( next ) ) break;
          }
        }

        boolean process = !isSelectClause && 
            isJavaIdentifier( token ) && 
            isPossiblyClassName( last, next );
            
        if (process) {
          String importedClassName = getImportedClass( token, factory );
          if ( importedClassName != null ) {
            String[] implementors = factory.getImplementors( importedClassName );
            String placeholder = "$clazz" + count++ + "$";
            if ( implementors != null ) {
              placeholders.add( placeholder );
              replacements.add( implementors );
            }
            token = placeholder; // Note this!!
          }
        }

      }

      templateQuery.append( token );

    }
    String[] results = StringHelper.multiply( templateQuery.toString(), placeholders.iterator(), replacements.iterator() );
    if ( results.length == 0 ) log.warn( "no persistent classes found for query class: " + query );
    return results;
  }

  private static boolean isPossiblyClassName(String last, String next) {
    return "class".equals( last ) || ( 
        BEFORE_CLASS_TOKENS.contains( last ) && 
        !NOT_AFTER_CLASS_TOKENS.contains( next ) 
      );
  }

  private static boolean isJavaIdentifier(String token) {
    return Character.isJavaIdentifierStart( token.charAt( 0 ) );
  }

  public static String getImportedClass(String name, SessionFactoryImplementor factory) {
    return factory.getImportedClassName( name );
  }
}