001: // $Id: LiteralProcessor.java 11258 2007-03-07 22:41:22Z steve.ebersole@jboss.com $
002: package org.hibernate.hql.ast.util;
003:
004: import org.hibernate.MappingException;
005: import org.hibernate.QueryException;
006: import org.hibernate.HibernateException;
007: import org.hibernate.dialect.Dialect;
008: import org.hibernate.hql.QueryTranslator;
009: import org.hibernate.hql.antlr.HqlSqlTokenTypes;
010: import org.hibernate.hql.antlr.SqlTokenTypes;
011: import org.hibernate.hql.ast.HqlSqlWalker;
012: import org.hibernate.hql.ast.InvalidPathException;
013: import org.hibernate.hql.ast.tree.DotNode;
014: import org.hibernate.hql.ast.tree.FromClause;
015: import org.hibernate.hql.ast.tree.IdentNode;
016: import org.hibernate.persister.entity.Queryable;
017: import org.hibernate.sql.InFragment;
018: import org.hibernate.type.LiteralType;
019: import org.hibernate.type.Type;
020: import org.hibernate.type.TypeFactory;
021: import org.hibernate.util.ReflectHelper;
022:
023: import antlr.SemanticException;
024: import antlr.collections.AST;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import java.math.BigDecimal;
030: import java.text.DecimalFormat;
031:
032: /**
033: * A delegate that handles literals and constants for HqlSqlWalker, performing the token replacement functions and
034: * classifying literals.
035: *
036: * @author josh Sep 2, 2004 7:15:30 AM
037: */
038: public class LiteralProcessor implements HqlSqlTokenTypes {
039: /**
040: * Indicates that Float and Double literal values should
041: * be treated using the SQL "exact" format (i.e., '.001')
042: */
043: public static final int EXACT = 0;
044: /**
045: * Indicates that Float and Double literal values should
046: * be treated using the SQL "approximate" format (i.e., '1E-3')
047: */
048: public static final int APPROXIMATE = 1;
049: /**
050: * In what format should Float and Double literal values be sent
051: * to the database?
052: * @see #EXACT, #APPROXIMATE
053: */
054: public static int DECIMAL_LITERAL_FORMAT = EXACT;
055:
056: private static final Log log = LogFactory
057: .getLog(LiteralProcessor.class);
058:
059: private HqlSqlWalker walker;
060:
061: public LiteralProcessor(HqlSqlWalker hqlSqlWalker) {
062: this .walker = hqlSqlWalker;
063: }
064:
065: public boolean isAlias(String alias) {
066: FromClause from = walker.getCurrentFromClause();
067: while (from.isSubQuery()) {
068: if (from.containsClassAlias(alias)) {
069: return true;
070: }
071: from = from.getParentFromClause();
072: }
073: return from.containsClassAlias(alias);
074: }
075:
076: public void processConstant(AST constant, boolean resolveIdent)
077: throws SemanticException {
078: // If the constant is an IDENT, figure out what it means...
079: boolean isIdent = (constant.getType() == IDENT || constant
080: .getType() == WEIRD_IDENT);
081: if (resolveIdent && isIdent && isAlias(constant.getText())) { // IDENT is a class alias in the FROM.
082: IdentNode ident = (IdentNode) constant;
083: // Resolve to an identity column.
084: ident.resolve(false, true);
085: } else { // IDENT might be the name of a class.
086: Queryable queryable = walker.getSessionFactoryHelper()
087: .findQueryableUsingImports(constant.getText());
088: if (isIdent && queryable != null) {
089: constant.setText(queryable.getDiscriminatorSQLValue());
090: }
091: // Otherwise, it's a literal.
092: else {
093: processLiteral(constant);
094: }
095: }
096: }
097:
098: public void lookupConstant(DotNode node) throws SemanticException {
099: String text = ASTUtil.getPathText(node);
100: Queryable persister = walker.getSessionFactoryHelper()
101: .findQueryableUsingImports(text);
102: if (persister != null) {
103: // the name of an entity class
104: final String discrim = persister.getDiscriminatorSQLValue();
105: node.setDataType(persister.getDiscriminatorType());
106: if (InFragment.NULL.equals(discrim)
107: || InFragment.NOT_NULL.equals(discrim)) {
108: throw new InvalidPathException(
109: "subclass test not allowed for null or not null discriminator: '"
110: + text + "'");
111: } else {
112: setSQLValue(node, text, discrim); //the class discriminator value
113: }
114: } else {
115: Object value = ReflectHelper.getConstantValue(text);
116: if (value == null) {
117: throw new InvalidPathException("Invalid path: '" + text
118: + "'");
119: } else {
120: setConstantValue(node, text, value);
121: }
122: }
123: }
124:
125: private void setSQLValue(DotNode node, String text, String value) {
126: if (log.isDebugEnabled()) {
127: log.debug("setSQLValue() " + text + " -> " + value);
128: }
129: node.setFirstChild(null); // Chop off the rest of the tree.
130: node.setType(SqlTokenTypes.SQL_TOKEN);
131: node.setText(value);
132: node.setResolvedConstant(text);
133: }
134:
135: private void setConstantValue(DotNode node, String text,
136: Object value) {
137: if (log.isDebugEnabled()) {
138: log.debug("setConstantValue() " + text + " -> " + value
139: + " " + value.getClass().getName());
140: }
141: node.setFirstChild(null); // Chop off the rest of the tree.
142: if (value instanceof String) {
143: node.setType(SqlTokenTypes.QUOTED_STRING);
144: } else if (value instanceof Character) {
145: node.setType(SqlTokenTypes.QUOTED_STRING);
146: } else if (value instanceof Byte) {
147: node.setType(SqlTokenTypes.NUM_INT);
148: } else if (value instanceof Short) {
149: node.setType(SqlTokenTypes.NUM_INT);
150: } else if (value instanceof Integer) {
151: node.setType(SqlTokenTypes.NUM_INT);
152: } else if (value instanceof Long) {
153: node.setType(SqlTokenTypes.NUM_LONG);
154: } else if (value instanceof Double) {
155: node.setType(SqlTokenTypes.NUM_DOUBLE);
156: } else if (value instanceof Float) {
157: node.setType(SqlTokenTypes.NUM_FLOAT);
158: } else {
159: node.setType(SqlTokenTypes.CONSTANT);
160: }
161: Type type;
162: try {
163: type = TypeFactory
164: .heuristicType(value.getClass().getName());
165: } catch (MappingException me) {
166: throw new QueryException(me);
167: }
168: if (type == null) {
169: throw new QueryException(
170: QueryTranslator.ERROR_CANNOT_DETERMINE_TYPE
171: + node.getText());
172: }
173: try {
174: LiteralType literalType = (LiteralType) type;
175: Dialect dialect = walker.getSessionFactoryHelper()
176: .getFactory().getDialect();
177: node.setText(literalType.objectToSQLString(value, dialect));
178: } catch (Exception e) {
179: throw new QueryException(
180: QueryTranslator.ERROR_CANNOT_FORMAT_LITERAL
181: + node.getText(), e);
182: }
183: node.setDataType(type);
184: node.setResolvedConstant(text);
185: }
186:
187: public void processBoolean(AST constant) {
188: // TODO: something much better - look at the type of the other expression!
189: // TODO: Have comparisonExpression and/or arithmeticExpression rules complete the resolution of boolean nodes.
190: String replacement = (String) walker.getTokenReplacements()
191: .get(constant.getText());
192: if (replacement != null) {
193: constant.setText(replacement);
194: } else {
195: boolean bool = "true".equals(constant.getText()
196: .toLowerCase());
197: Dialect dialect = walker.getSessionFactoryHelper()
198: .getFactory().getDialect();
199: constant.setText(dialect.toBooleanValueString(bool));
200: }
201: }
202:
203: private void processLiteral(AST constant) {
204: String replacement = (String) walker.getTokenReplacements()
205: .get(constant.getText());
206: if (replacement != null) {
207: if (log.isDebugEnabled()) {
208: log.debug("processConstant() : Replacing '"
209: + constant.getText() + "' with '" + replacement
210: + "'");
211: }
212: constant.setText(replacement);
213: }
214: }
215:
216: public void processNumeric(AST literal) {
217: if (literal.getType() == NUM_INT
218: || literal.getType() == NUM_LONG) {
219: literal.setText(determineIntegerRepresentation(literal
220: .getText(), literal.getType()));
221: } else if (literal.getType() == NUM_FLOAT
222: || literal.getType() == NUM_DOUBLE) {
223: literal.setText(determineDecimalRepresentation(literal
224: .getText(), literal.getType()));
225: } else {
226: log.warn("Unexpected literal token type ["
227: + literal.getType()
228: + "] passed for numeric processing");
229: }
230: }
231:
232: private String determineIntegerRepresentation(String text, int type) {
233: try {
234: if (type == NUM_INT) {
235: try {
236: return Integer.valueOf(text).toString();
237: } catch (NumberFormatException e) {
238: log
239: .trace("could not format incoming text ["
240: + text
241: + "] as a NUM_INT; assuming numeric overflow and attempting as NUM_LONG");
242: }
243: }
244: String literalValue = text;
245: if (literalValue.endsWith("l")
246: || literalValue.endsWith("L")) {
247: literalValue = literalValue.substring(0, literalValue
248: .length() - 1);
249: }
250: return Long.valueOf(literalValue).toString();
251: } catch (Throwable t) {
252: throw new HibernateException("Could not parse literal ["
253: + text + "] as integer", t);
254: }
255: }
256:
257: public String determineDecimalRepresentation(String text, int type) {
258: String literalValue = text;
259: if (type == NUM_FLOAT) {
260: if (literalValue.endsWith("f")
261: || literalValue.endsWith("F")) {
262: literalValue = literalValue.substring(0, literalValue
263: .length() - 1);
264: }
265: } else if (type == NUM_DOUBLE) {
266: if (literalValue.endsWith("d")
267: || literalValue.endsWith("D")) {
268: literalValue = literalValue.substring(0, literalValue
269: .length() - 1);
270: }
271: }
272:
273: BigDecimal number = null;
274: try {
275: number = new BigDecimal(literalValue);
276: } catch (Throwable t) {
277: throw new HibernateException("Could not parse literal ["
278: + text + "] as big-decimal", t);
279: }
280:
281: return formatters[DECIMAL_LITERAL_FORMAT].format(number);
282: }
283:
284: private static interface DecimalFormatter {
285: String format(BigDecimal number);
286: }
287:
288: private static class ExactDecimalFormatter implements
289: DecimalFormatter {
290: public String format(BigDecimal number) {
291: return number.toString();
292: }
293: }
294:
295: private static class ApproximateDecimalFormatter implements
296: DecimalFormatter {
297: private static final String FORMAT_STRING = "#0.0E0";
298:
299: public String format(BigDecimal number) {
300: try {
301: // TODO : what amount of significant digits need to be supported here?
302: // - from the DecimalFormat docs:
303: // [significant digits] = [minimum integer digits] + [maximum fraction digits]
304: DecimalFormat jdkFormatter = new DecimalFormat(
305: FORMAT_STRING);
306: jdkFormatter.setMinimumIntegerDigits(1);
307: jdkFormatter
308: .setMaximumFractionDigits(Integer.MAX_VALUE);
309: return jdkFormatter.format(number);
310: } catch (Throwable t) {
311: throw new HibernateException(
312: "Unable to format decimal literal in approximate format ["
313: + number.toString() + "]", t);
314: }
315: }
316: }
317:
318: private static final DecimalFormatter[] formatters = new DecimalFormatter[] {
319: new ExactDecimalFormatter(),
320: new ApproximateDecimalFormatter() };
321: }
|