Open Source Repository

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



org/hibernate/hql/ast/util/LiteralProcessor.java
// $Id: LiteralProcessor.java 11258 2007-03-07 22:41:22Z [email protected] $
package org.hibernate.hql.ast.util;

import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.hql.QueryTranslator;
import org.hibernate.hql.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.antlr.SqlTokenTypes;
import org.hibernate.hql.ast.HqlSqlWalker;
import org.hibernate.hql.ast.InvalidPathException;
import org.hibernate.hql.ast.tree.DotNode;
import org.hibernate.hql.ast.tree.FromClause;
import org.hibernate.hql.ast.tree.IdentNode;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.InFragment;
import org.hibernate.type.LiteralType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.util.ReflectHelper;

import antlr.SemanticException;
import antlr.collections.AST;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.math.BigDecimal;
import java.text.DecimalFormat;

/**
 * A delegate that handles literals and constants for HqlSqlWalker, performing the token replacement functions and
 * classifying literals.
 *
 @author josh Sep 2, 2004 7:15:30 AM
 */
public class LiteralProcessor implements HqlSqlTokenTypes {
  /**
   * Indicates that Float and Double literal values should
   * be treated using the SQL "exact" format (i.e., '.001')
   */
  public static final int EXACT = 0;
  /**
   * Indicates that Float and Double literal values should
   * be treated using the SQL "approximate" format (i.e., '1E-3')
   */
  public static final int APPROXIMATE = 1;
  /**
   * In what format should Float and Double literal values be sent
   * to the database?
   @see #EXACT, #APPROXIMATE
   */
  public static int DECIMAL_LITERAL_FORMAT = EXACT;

  private static final Log log = LogFactory.getLogLiteralProcessor.class );

  private HqlSqlWalker walker;

  public LiteralProcessor(HqlSqlWalker hqlSqlWalker) {
    this.walker = hqlSqlWalker;
  }

  public boolean isAlias(String alias) {
    FromClause from = walker.getCurrentFromClause();
    while from.isSubQuery() ) {
      if from.containsClassAlias(alias) ) {
        return true;
      }
      from = from.getParentFromClause();
    }
    return from.containsClassAlias(alias);
  }

  public void processConstant(AST constant, boolean resolveIdentthrows SemanticException {
    // If the constant is an IDENT, figure out what it means...
    boolean isIdent = constant.getType() == IDENT || constant.getType() == WEIRD_IDENT );
    if resolveIdent && isIdent && isAliasconstant.getText() ) ) { // IDENT is a class alias in the FROM.
      IdentNode ident = IdentNode constant;
      // Resolve to an identity column.
      ident.resolve(false, true);
    }
    else {  // IDENT might be the name of a class.
      Queryable queryable = walker.getSessionFactoryHelper().findQueryableUsingImportsconstant.getText() );
      if isIdent && queryable != null ) {
        constant.setTextqueryable.getDiscriminatorSQLValue() );
      }
      // Otherwise, it's a literal.
      else {
        processLiteralconstant );
      }
    }
  }

  public void lookupConstant(DotNode nodethrows SemanticException {
    String text = ASTUtil.getPathTextnode );
    Queryable persister = walker.getSessionFactoryHelper().findQueryableUsingImportstext );
    if persister != null ) {
      // the name of an entity class
      final String discrim = persister.getDiscriminatorSQLValue();
      node.setDataTypepersister.getDiscriminatorType() );
      if InFragment.NULL.equals(discrim|| InFragment.NOT_NULL.equals(discrim) ) {
        throw new InvalidPathException"subclass test not allowed for null or not null discriminator: '" + text + "'" );
      }
      else {
        setSQLValuenode, text, discrim )//the class discriminator value
      }
    }
    else {
      Object value = ReflectHelper.getConstantValuetext );
      if value == null ) {
        throw new InvalidPathException"Invalid path: '" + text + "'" );
      }
      else {
        setConstantValuenode, text, value );
      }
    }
  }

  private void setSQLValue(DotNode node, String text, String value) {
    if log.isDebugEnabled() ) {
      log.debug"setSQLValue() " + text + " -> " + value );
    }
    node.setFirstChildnull );  // Chop off the rest of the tree.
    node.setTypeSqlTokenTypes.SQL_TOKEN );
    node.setText(value);
    node.setResolvedConstanttext );
  }

  private void setConstantValue(DotNode node, String text, Object value) {
    if log.isDebugEnabled() ) {
      log.debug"setConstantValue() " + text + " -> " + value + " " + value.getClass().getName() );
    }
    node.setFirstChildnull );  // Chop off the rest of the tree.
    if value instanceof String ) {
      node.setTypeSqlTokenTypes.QUOTED_STRING );
    }
    else if value instanceof Character ) {
      node.setTypeSqlTokenTypes.QUOTED_STRING );
    }
    else if value instanceof Byte ) {
      node.setTypeSqlTokenTypes.NUM_INT );
    }
    else if value instanceof Short ) {
      node.setTypeSqlTokenTypes.NUM_INT );
    }
    else if value instanceof Integer ) {
      node.setTypeSqlTokenTypes.NUM_INT );
    }
    else if value instanceof Long ) {
      node.setTypeSqlTokenTypes.NUM_LONG );
    }
    else if value instanceof Double ) {
      node.setTypeSqlTokenTypes.NUM_DOUBLE );
    }
    else if value instanceof Float ) {
      node.setTypeSqlTokenTypes.NUM_FLOAT );
    }
    else {
      node.setTypeSqlTokenTypes.CONSTANT );
    }
    Type type;
    try {
      type = TypeFactory.heuristicTypevalue.getClass().getName() );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
    if type == null ) {
      throw new QueryExceptionQueryTranslator.ERROR_CANNOT_DETERMINE_TYPE + node.getText() );
    }
    try {
      LiteralType literalType = LiteralType type;
      Dialect dialect = walker.getSessionFactoryHelper().getFactory().getDialect();
      node.setTextliteralType.objectToSQLStringvalue, dialect ) );
    }
    catch Exception e ) {
      throw new QueryExceptionQueryTranslator.ERROR_CANNOT_FORMAT_LITERAL + node.getText(), e );
    }
    node.setDataTypetype );
    node.setResolvedConstanttext );
  }

  public void processBoolean(AST constant) {
    // TODO: something much better - look at the type of the other expression!
    // TODO: Have comparisonExpression and/or arithmeticExpression rules complete the resolution of boolean nodes.
    String replacement = String walker.getTokenReplacements().getconstant.getText() );
    if replacement != null ) {
      constant.setTextreplacement );
    }
    else {
      boolean bool = "true".equalsconstant.getText().toLowerCase() );
      Dialect dialect = walker.getSessionFactoryHelper().getFactory().getDialect();
      constant.setTextdialect.toBooleanValueString(bool) );
    }
  }

  private void processLiteral(AST constant) {
    String replacement = String walker.getTokenReplacements().getconstant.getText() );
    if replacement != null ) {
      if log.isDebugEnabled() ) {
        log.debug"processConstant() : Replacing '" + constant.getText() "' with '" + replacement + "'" );
      }
      constant.setTextreplacement );
    }
  }

  public void processNumeric(AST literal) {
    if literal.getType() == NUM_INT || literal.getType() == NUM_LONG ) {
      literal.setTextdetermineIntegerRepresentationliteral.getText(), literal.getType() ) );
    }
    else if literal.getType() == NUM_FLOAT || literal.getType() == NUM_DOUBLE ) {
      literal.setTextdetermineDecimalRepresentationliteral.getText(), literal.getType() ) );
    }
    else {
      log.warn"Unexpected literal token type [" + literal.getType() "] passed for numeric processing" );
    }
  }

  private String determineIntegerRepresentation(String text, int type) {
    try {
      if type == NUM_INT ) {
        try {
          return Integer.valueOftext ).toString();
        }
        catchNumberFormatException e ) {
          log.trace"could not format incoming text [" + text + "] as a NUM_INT; assuming numeric overflow and attempting as NUM_LONG" );
        }
      }
      String literalValue = text;
      if literalValue.endsWith"l" || literalValue.endsWith"L" ) ) {
        literalValue = literalValue.substring0, literalValue.length() );
      }
      return Long.valueOfliteralValue ).toString();
    }
    catchThrowable t ) {
      throw new HibernateException"Could not parse literal [" + text + "] as integer", t );
    }
  }

  public String determineDecimalRepresentation(String text, int type) {
    String literalValue = text;
    if type == NUM_FLOAT ) {
      if literalValue.endsWith"f" || literalValue.endsWith"F" ) ) {
        literalValue = literalValue.substring0, literalValue.length() );
      }
    }
    else if type == NUM_DOUBLE ) {
      if literalValue.endsWith"d" || literalValue.endsWith"D" ) ) {
        literalValue = literalValue.substring0, literalValue.length() );
      }
    }

    BigDecimal number = null;
    try {
      number = new BigDecimalliteralValue );
    }
    catchThrowable t ) {
      throw new HibernateException"Could not parse literal [" + text + "] as big-decimal", t );
    }

    return formattersDECIMAL_LITERAL_FORMAT ].formatnumber );
  }

  private static interface DecimalFormatter {
    String format(BigDecimal number);
  }

  private static class ExactDecimalFormatter implements DecimalFormatter {
    public String format(BigDecimal number) {
      return number.toString();
    }
  }

  private static class ApproximateDecimalFormatter implements DecimalFormatter {
    private static final String FORMAT_STRING = "#0.0E0";
    public String format(BigDecimal number) {
      try {
        // TODO : what amount of significant digits need to be supported here?
        //      - from the DecimalFormat docs:
        //          [significant digits] = [minimum integer digits] + [maximum fraction digits]
        DecimalFormat jdkFormatter = new DecimalFormatFORMAT_STRING );
        jdkFormatter.setMinimumIntegerDigits);
        jdkFormatter.setMaximumFractionDigitsInteger.MAX_VALUE );
        return jdkFormatter.formatnumber );
      }
      catchThrowable t ) {
        throw new HibernateException"Unable to format decimal literal in approximate format [" + number.toString() "]", t );
      }
    }
  }

  private static final DecimalFormatter[] formatters = new DecimalFormatter[] {
      new ExactDecimalFormatter(),
      new ApproximateDecimalFormatter()
  };
}