/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.sql.symbol; import java.math.BigDecimal; import java.text.Collator; import java.util.Comparator; import java.util.List; import java.util.Locale; import org.teiid.core.types.ClobType; import org.teiid.core.types.DataTypeManager; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.query.QueryPlugin; import org.teiid.query.function.FunctionMethods; import org.teiid.query.sql.LanguageVisitor; import org.teiid.query.sql.visitor.SQLStringVisitor; /** * This class represents a literal value in a SQL string. The Constant object has a value * and a type for that value. In many cases, the type can be derived from the type of the * value, but that is not true if the value is null. In that case, the type is unknown * and is set to the null type until the type is resolved at a later point. */ public class Constant implements Expression, Comparable<Constant> { public static final Constant NULL_CONSTANT = new Constant(null); private Object value; private Class<?> type; private boolean multiValued; private boolean bindEligible; public static final Comparator<Object> COMPARATOR = getComparator(DataTypeManager.COLLATION_LOCALE, DataTypeManager.PAD_SPACE); static Comparator<Object> getComparator(String localeString, final boolean padSpace) { if (localeString == null) { return getComparator(padSpace); } String[] parts = localeString.split("_"); //$NON-NLS-1$ Locale locale = null; if (parts.length == 1) { locale = new Locale(parts[0]); } else if (parts.length == 2) { locale = new Locale(parts[0], parts[1]); } else if (parts.length == 3) { locale = new Locale(parts[0], parts[1], parts[2]); } else { LogManager.logError(LogConstants.CTX_DQP, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30564, localeString)); return getComparator(padSpace); } final Collator c = Collator.getInstance(locale); LogManager.logError(LogConstants.CTX_DQP, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30565, locale)); return new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { Class<?> clazz = o1.getClass(); if (clazz == String.class) { String s1 = (String)o1; String s2 = (String)o2; if (padSpace) { s1 = FunctionMethods.rightTrim(s1, ' ', false); s2 = FunctionMethods.rightTrim(s2, ' ', false); } return c.compare(s1, s2); } return ((Comparable<Object>)o1).compareTo(o2); } }; } static Comparator<Object> getComparator(boolean padSpace) { if (!padSpace) { return new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { return ((Comparable<Object>)o1).compareTo(o2); } }; } return new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { Class<?> clazz = o1.getClass(); if (clazz == String.class) { CharSequence s1 = (CharSequence)o1; CharSequence s2 = (CharSequence)o2; return comparePadded(s1, s2); } else if (clazz == ClobType.class) { CharSequence s1 = ((ClobType)o1).getCharSequence(); CharSequence s2 = ((ClobType)o2).getCharSequence(); return comparePadded(s1, s2); } return ((Comparable<Object>)o1).compareTo(o2); } }; } /** * Construct a typed constant. The specified value is not verified to be a value * of the specified type. If this is not true, stuff probably won't work later on. * * @param value Constant value, may be null * @param type Type for the constant, should never be null */ public Constant(Object value, Class<?> type) { this.value = value; // Check that type is valid, then set it if(type == null) { throw new IllegalArgumentException(QueryPlugin.Util.getString("ERR.015.010.0014")); //$NON-NLS-1$ } this.type = type; } /** * Construct a constant with a value, which may be null. The data type * is determined automatically from the type of the value. * @param value Constant value, may be null */ public Constant(Object value) { this.value = value; if (this.value == null) { this.type = DataTypeManager.DefaultDataClasses.NULL; } else { this.type = this.value.getClass(); Class<?> originalType = type; while (type.isArray() && !type.getComponentType().isPrimitive()) { type = type.getComponentType(); } if (DataTypeManager.getAllDataTypeClasses().contains(type)) { //array of a runtime-type this.type = originalType; } else if (originalType.isArray() && !originalType.getComponentType().isPrimitive()) { this.type = DataTypeManager.getArrayType(DataTypeManager.DefaultDataClasses.OBJECT); } else { this.type = DataTypeManager.DefaultDataClasses.OBJECT; } } } /** * Get type of constant, if known * @return Java class name of type */ public Class<?> getType() { return this.type; } /** * TODO: remove me when a null type is supported * @param type */ public void setType(Class<?> type) { this.type = type; } /** * Get value of constant * @return Constant value */ public Object getValue() { return this.value; } /** * Return true if the constant is null. * @return True if value is null */ public boolean isNull() { return value==null; } public void setMultiValued(List<?> value) { this.multiValued = true; this.value = value; } public boolean isMultiValued() { return multiValued; } public void acceptVisitor(LanguageVisitor visitor) { visitor.visit(this); } /** * Compare this constant to another constant for equality. * @param obj Other object * @return True if constants are equal */ public boolean equals(Object obj) { if(this == obj) { return true; } if(!(obj instanceof Constant)) { return false; } Constant other = (Constant) obj; // Check null values first if(other.isNull()) { if (this.isNull()) { return true; } return false; } if (this.isNull()) { return false; } // Check type - types are never null if(! other.getType().equals(this.getType())) { return false; } if (this.value instanceof BigDecimal) { if (this.value == other.value) { return true; } if (!(other.value instanceof BigDecimal)) { return false; } return ((BigDecimal)this.value).compareTo((BigDecimal)other.value) == 0; } return multiValued == other.multiValued && other.getValue().equals(this.getValue()); } /** * Define hash code to be that of the underlying object to make it stable. * @return Hash code, based on value */ public int hashCode() { if(this.value != null && !isMultiValued()) { if (this.value instanceof BigDecimal) { BigDecimal bd = (BigDecimal)this.value; int xsign = bd.signum(); if (xsign == 0) return 0; bd = bd.stripTrailingZeros(); return bd.hashCode(); } return this.value.hashCode(); } return 0; } /** * Return a shallow copy of this object - value is NOT cloned! * @return Shallow copy of object */ public Object clone() { Constant copy = new Constant(getValue(), getType()); copy.multiValued = multiValued; copy.bindEligible = bindEligible; return copy; } /** * Return a String representation of this object using SQLStringVisitor. * @return String representation using SQLStringVisitor */ public String toString() { return SQLStringVisitor.getSQLString(this); } @Override public int compareTo(Constant o) { if (isNull()) { if (o.isNull()) { return 0; } return -1; } if (o.isNull()) { return 1; } return COMPARATOR.compare(this.value, o.getValue()); } final static int comparePadded(CharSequence s1, CharSequence s2) { int len1 = s1.length(); int len2 = s2.length(); int n = Math.min(len1, len2); int i = 0; int result = 0; for (; i < n; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { return c1 - c2; } } result = len1 - len2; for (int j = i; j < len1; j++) { if (s1.charAt(j) != ' ') { return result; } } for (int j = i; j < len2; j++) { if (s2.charAt(j) != ' ') { return result; } } return 0; } public boolean isBindEligible() { return bindEligible; } public void setBindEligible(boolean bindEligible) { this.bindEligible = bindEligible; } }