--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.types.BinaryDecimal\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.iapi.types;\r
+\r
+import java.io.IOException;\r
+import java.io.ObjectInput;\r
+import java.io.ObjectOutput;\r
+import java.sql.ResultSet;\r
+import java.sql.SQLException;\r
+import java.sql.Types;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.io.ArrayInputStream;\r
+import org.apache.derby.iapi.services.io.StoredFormatIds;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+/**\r
+ * SQL DECIMAL using raw data. Provides the basis for the\r
+ * CDCDecimal implementation.\r
+ * <P>\r
+ * The on-disk format must match the SQLDecimal format so that\r
+ * databases are portable across J2ME and J2SE environments.\r
+ * <P>\r
+ * The format of the byte array is defined by the return of the\r
+ * java.math.BigInteger.toByteArray:, extracted here.\r
+ * \r
+ * Returns a byte array containing the two's-complement representation of this BigInteger.\r
+ * The byte array will be in big-endian byte-order: the most significant byte is in the zeroth element.\r
+ *\r
+ * This is the format for DECIMAL even if BigINteger is not available, e.g. OSGi ee.minimum.\r
+ */\r
+\r
+abstract class BinaryDecimal extends NumberDataType\r
+ implements VariableSizeDataValue\r
+{\r
+ /**\r
+ * An unscaled value of 1 in two's complement\r
+ */\r
+ private static final byte[] ONE_2C = {(byte) 0x01};\r
+ \r
+ /**\r
+ * The unscaled value as a binary two's complement array.\r
+ */\r
+ protected byte[] data2c;\r
+\r
+ /**\r
+ * The SQL scale, zero or positive, of the value\r
+ */\r
+ protected int sqlScale;\r
+ \r
+ \r
+ BinaryDecimal() {\r
+ }\r
+ \r
+ /*\r
+ ** Methods about the DECIMAL type itself.\r
+ */\r
+\r
+ /**\r
+ * DECIMAL implementation.\r
+ * Use DECIMAL to indicate to self that another\r
+ * passed in value is an instance of this type.\r
+ */\r
+ public final int typeToBigDecimal()\r
+ {\r
+ return java.sql.Types.DECIMAL;\r
+ }\r
+\r
+ /** @see DataValueDescriptor#typePrecedence */\r
+ public final int typePrecedence()\r
+ {\r
+ return TypeId.DECIMAL_PRECEDENCE;\r
+ }\r
+ \r
+ /* Return DECIMAL as the type name.\r
+ * @see org.apache.derby.iapi.types.DataValueDescriptor#getTypeName()\r
+ */\r
+ public final String getTypeName() {\r
+ return TypeId.DECIMAL_NAME;\r
+ }\r
+\r
+ /**\r
+ * Return my format identifier.\r
+ * \r
+ * @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId\r
+ */\r
+ public final int getTypeFormatId() {\r
+ return StoredFormatIds.SQL_DECIMAL_ID;\r
+ } \r
+ \r
+ /*\r
+ ** NULL handling.\r
+ */\r
+\r
+ /**\r
+ * see if the decimal value is null.\r
+ */\r
+ public boolean isNull()\r
+ {\r
+ return data2c == null;\r
+ } \r
+\r
+ public void restoreToNull()\r
+ {\r
+ data2c = null;\r
+ }\r
+\r
+ /* Check the leftmost bit, if set the value is negative.\r
+ * NULL values return false.\r
+ * @see org.apache.derby.iapi.types.NumberDataType#isNegative()\r
+ */\r
+ protected boolean isNegative() {\r
+ return !isNull() && ((data2c[0] & 0x80) != 0);\r
+ }\r
+ \r
+ \r
+ /*\r
+ ** Methods to convert values into this DECIMAL\r
+ */\r
+ \r
+ /**\r
+ * Set the value from a long.\r
+ */\r
+ public void setValue(long theValue)\r
+ {\r
+ byte[] rd = data2c;\r
+ if (rd == null || rd.length < 8)\r
+ rd = new byte[8];\r
+ \r
+ rd[0] = (byte)(theValue >>> 56);\r
+ rd[1] = (byte)(theValue >>> 48);\r
+ rd[2] = (byte)(theValue >>> 40);\r
+ rd[3] = (byte)(theValue >>> 32);\r
+ rd[4] = (byte)(theValue >>> 24);\r
+ rd[5] = (byte)(theValue >>> 16);\r
+ rd[6] = (byte)(theValue >>> 8);\r
+ rd[7] = (byte) theValue;\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ data2c = rd;\r
+ sqlScale = 0;\r
+ try {\r
+ if (theValue != getLong())\r
+ SanityManager.THROWASSERT("BinaryDecimal invalid long conversion before reduce in "\r
+ + theValue + " out " + getLong());\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ SanityManager.THROWASSERT(se);\r
+ }\r
+ }\r
+ \r
+ data2c = BinaryDecimal.reduceBytes2c(rd, 0, 8);\r
+ sqlScale = 0;\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ try {\r
+ if (theValue != getLong())\r
+ SanityManager.THROWASSERT("BinaryDecimal invalid long conversion after reduce in "\r
+ + theValue + " out " + getLong());\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ SanityManager.THROWASSERT(se);\r
+ }\r
+ } \r
+ }\r
+\r
+ /**\r
+ * Set the value from an int, just copy 'byte-by-byte'\r
+ * from the int to a four byte array. Then reduce.\r
+ * @see NumberDataValue#setValue\r
+ */\r
+ public final void setValue(int theValue)\r
+ {\r
+ byte[] rd = data2c;\r
+ if (rd == null || rd.length < 4)\r
+ rd = new byte[4];\r
+ \r
+ rd[0] = (byte)(theValue >>> 24);\r
+ rd[1] = (byte)(theValue >>> 16);\r
+ rd[2] = (byte)(theValue >>> 8);\r
+ rd[3] = (byte) theValue;\r
+ \r
+ data2c = BinaryDecimal.reduceBytes2c(rd, 0, 4);\r
+ sqlScale = 0;\r
+ }\r
+ \r
+ /**\r
+ * Set the value from a boolean\r
+ */\r
+ public void setValue(boolean theValue)\r
+ {\r
+ int intValue = theValue ? 1 : 0;\r
+ setValue(intValue);\r
+ }\r
+ \r
+ /**\r
+ * Convert from a double, normalize and then convert as a String.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public final void setValue(double theValue) throws StandardException\r
+ {\r
+ setCoreValue(NumberDataType.normalizeDOUBLE(theValue));\r
+ }\r
+\r
+ /**\r
+ * Convert from a float, normalize and then convert as a String.\r
+ *\r
+ */\r
+ public final void setValue(float theValue)\r
+ throws StandardException\r
+ {\r
+ setCoreValue((double)NumberDataType.normalizeREAL(theValue));\r
+ }\r
+ \r
+ private void setCoreValue(double theValue) throws StandardException {\r
+ setValue(Double.toString(theValue));\r
+ }\r
+ \r
+ /**\r
+ Called when setting a DECIMAL value internally or from\r
+ through a procedure or function.\r
+ Handles long in addition to BigDecimal to handle\r
+ identity being stored as a long but returned as a DECIMAL.\r
+ */\r
+ public void setValue(Number theValue) throws StandardException\r
+ {\r
+ if (SanityManager.ASSERT)\r
+ {\r
+ if (theValue != null &&\r
+ !(theValue instanceof java.lang.Long))\r
+ SanityManager.THROWASSERT("BinaryDecimal.setValue(Number) passed a " + theValue.getClass());\r
+ }\r
+ \r
+ if (theValue == null)\r
+ setToNull();\r
+ else\r
+ setValue(theValue.longValue());\r
+ }\r
+ \r
+ /**\r
+ * Set this DECIMAL value from another DataValueDescriptor\r
+ */\r
+ protected void setFrom(DataValueDescriptor dvd) throws StandardException\r
+ {\r
+ \r
+ switch (dvd.typeToBigDecimal())\r
+ {\r
+ case Types.CHAR:\r
+ case Types.DECIMAL: // TODO : direct copy\r
+ \r
+ setValue(dvd.getString());\r
+ break;\r
+ case Types.BIGINT:\r
+ setValue(dvd.getLong());\r
+ break;\r
+ default:\r
+ super.setFrom(dvd);\r
+ }\r
+ }\r
+ /*\r
+ ** Methods to get a value from this DECIMAL\r
+ */\r
+\r
+ /**\r
+ * Return a int from this value.\r
+ * \r
+ * @exception StandardException\r
+ * this value is out of range for an int\r
+ */\r
+ public final int getInt() throws StandardException\r
+ {\r
+ if (isNull())\r
+ return 0;\r
+\r
+ try {\r
+ long lv = getLong();\r
+\r
+ if ((lv >= Integer.MIN_VALUE) && (lv <= Integer.MAX_VALUE))\r
+ return (int) lv;\r
+\r
+ } catch (StandardException se) {\r
+ // out of range error but with incorrect messgae (BIGINT)\r
+ // fall through to correct message\r
+ }\r
+\r
+ throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER");\r
+ }\r
+\r
+ /**\r
+ * Return a byte from this value.\r
+ * \r
+ * @exception StandardException\r
+ * this value is out of range for a short\r
+ */\r
+ public final byte getByte() throws StandardException {\r
+ if (isNull())\r
+ return (byte) 0;\r
+\r
+ try {\r
+ long lv = getLong();\r
+\r
+ if ((lv >= Byte.MIN_VALUE) && (lv <= Byte.MAX_VALUE))\r
+ return (byte) lv;\r
+\r
+ } catch (StandardException se) {\r
+ // out of range error but with incorrect messgae (BIGINT)\r
+ // fall through to correct message\r
+ }\r
+\r
+ throw StandardException.newException(\r
+ SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TINYINT");\r
+ }\r
+\r
+ /**\r
+ * Return a short from this value.\r
+ * @exception StandardException this value is out of range for a short\r
+ */\r
+ public final short getShort() throws StandardException \r
+ {\r
+ if (isNull())\r
+ return (short)0;\r
+\r
+ try {\r
+ long lv = getLong();\r
+\r
+ if ((lv >= Short.MIN_VALUE) && (lv <= Short.MAX_VALUE))\r
+ return (short) lv;\r
+\r
+ } catch (StandardException se) {\r
+ // out of range error but with incorrect messgae (BIGINT)\r
+ // fall through to correct message\r
+ }\r
+\r
+ throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "SMALLINT");\r
+ }\r
+ \r
+ \r
+ /*\r
+ ** DECIMAL arithmetic methods.\r
+ */\r
+ \r
+ /**\r
+ * This method implements the + operator for DECIMAL.\r
+ *\r
+ * @param addend1 One of the addends\r
+ * @param addend2 The other addend\r
+ * @param result The result of a previous call to this method, null\r
+ * if not called yet\r
+ *\r
+ * @return A SQLDecimal containing the result of the addition\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public final NumberDataValue plus(NumberDataValue addend1,\r
+ NumberDataValue addend2,\r
+ NumberDataValue result)\r
+ throws StandardException\r
+ {\r
+ if (result == null)\r
+ {\r
+ result = (NumberDataValue) getNewNull();\r
+ }\r
+\r
+ if (addend1.isNull() || addend2.isNull())\r
+ {\r
+ result.setToNull();\r
+ return result;\r
+ }\r
+\r
+ return plusNN(addend1, addend2, result);\r
+ }\r
+ \r
+ /* (non-Javadoc)\r
+ * @see org.apache.derby.iapi.types.NumberDataValue#times(org.apache.derby.iapi.types.NumberDataValue, org.apache.derby.iapi.types.NumberDataValue, org.apache.derby.iapi.types.NumberDataValue)\r
+ */\r
+ public final NumberDataValue times(NumberDataValue left, NumberDataValue right, NumberDataValue result)\r
+ throws StandardException\r
+ {\r
+ if (result == null)\r
+ {\r
+ result = (NumberDataValue) getNewNull();\r
+ }\r
+\r
+ if (left.isNull() || right.isNull())\r
+ {\r
+ result.setToNull();\r
+ return result;\r
+ } \r
+ return timesNN(left, right, result);\r
+ }\r
+ public NumberDataValue divide(NumberDataValue dividend,\r
+ NumberDataValue divisor,\r
+ NumberDataValue result)\r
+throws StandardException\r
+{\r
+return divide(dividend, divisor, result, -1);\r
+}\r
+\r
+ /**\r
+ * This method implements the / operator for BigDecimal/BigDecimal\r
+ *\r
+ * @param dividend The numerator\r
+ * @param divisor The denominator\r
+ * @param result The result of a previous call to this method, null\r
+ * if not called yet\r
+ * @param scale The result scale, if < 0, calculate the scale according\r
+ * to the actual values' sizes\r
+ *\r
+ * @return A SQLDecimal containing the result of the division\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public final NumberDataValue divide(NumberDataValue dividend,\r
+ NumberDataValue divisor,\r
+ NumberDataValue result,\r
+ int scale)\r
+ throws StandardException\r
+ {\r
+ if (result == null)\r
+ {\r
+ result = (NumberDataValue) getNewNull();\r
+ }\r
+\r
+ if (dividend.isNull() || divisor.isNull())\r
+ {\r
+ result.setToNull();\r
+ return result;\r
+ }\r
+ \r
+ return divideNN(dividend, divisor, result, scale);\r
+ } \r
+ public final NumberDataValue minus(NumberDataValue left, NumberDataValue right, NumberDataValue result)\r
+ throws StandardException\r
+ {\r
+ if (result == null)\r
+ {\r
+ result = (NumberDataValue) getNewNull();\r
+ }\r
+\r
+ if (left.isNull() || right.isNull())\r
+ {\r
+ result.setToNull();\r
+ return result;\r
+ }\r
+ \r
+ return minusNN(left, right, result);\r
+ }\r
+ \r
+ /**\r
+ * Implement subtraction using addition and negation of the right value.\r
+ */\r
+ public NumberDataValue minusNN(NumberDataValue left, NumberDataValue right, NumberDataValue result)\r
+ throws StandardException\r
+ {\r
+ // Requires plusNN() correctly handles that its right argument and\r
+ // result can be references to the same object.\r
+ return plusNN(left, right.minus(result), result);\r
+ }\r
+ \r
+ /*\r
+ ** Abstract methods for handling non-null arithmetic.\r
+ ** Eventually move these methods into NumberDataType\r
+ ** and directly compile to them when arguments cannot\r
+ ** be null. A performance optimization.\r
+ */\r
+ \r
+ \r
+ /**\r
+ * Multiple two non-nullable values using DECIMAL arithmetic.\r
+ */\r
+ public abstract NumberDataValue timesNN(NumberDataValue left,\r
+ NumberDataValue right, NumberDataValue result)\r
+ throws StandardException;\r
+\r
+ /**\r
+ * Add two non-nullable values using DECIMAL arithmetic.\r
+ * For subclasses of BinaryDecimal, any implementation\r
+ * must handle the result and addend2 (right) being references\r
+ * to the same object.\r
+ */\r
+ public abstract NumberDataValue plusNN(NumberDataValue addend1,\r
+ NumberDataValue addend2, NumberDataValue result)\r
+ throws StandardException;\r
+\r
+ /**\r
+ * Divide two non-nullable values using DECIMAL arithmetic.\r
+ */\r
+ public abstract NumberDataValue divideNN(NumberDataValue dividend,\r
+ NumberDataValue divisor, NumberDataValue result, int scale)\r
+ throws StandardException;\r
+ \r
+ /*\r
+ ** Methods that act directly on twos complement byte arrays.\r
+ */\r
+\r
+ /**\r
+ * Compress the passed in byte array so that leading\r
+ * 0x00 and 0xff are removed when possible.\r
+ * E.g.\r
+ * 0x00000453 ->>> 0x0453\r
+ * 0xfffffff2 ->>> 0xf2\r
+ * 0xff192312 ->>> 0xff192312 (unchanged)\r
+ * 0xffff8039 ->>> 0x8039\r
+ * data2c is set to the compressed value.\r
+ * @param dataLength Valid length of data in data2c.\r
+ */\r
+ private static byte[] reduceBytes2c(byte[] rd, int offset, int dataLength)\r
+ {\r
+ // look for leading zeros, if the value\r
+ // is dataLength bytes long then look\r
+ // at up to the first (dataLength - 1) bytes\r
+ // to see if leading 0x00 can be removed.\r
+\r
+ int leading;\r
+ for (leading = 0; leading < (dataLength - 1); leading++)\r
+ {\r
+ if (rd[offset + leading] != (byte) 0)\r
+ break;\r
+ \r
+ // if the hi bit of the next byte is set\r
+ // then we cannot strip this 0x00 otherwise\r
+ // the number will turn negative.\r
+ if ((rd[offset + leading + 1] & 0x80) != 0)\r
+ break;\r
+ }\r
+\r
+ if (leading == 0)\r
+ {\r
+ // now a similar trick with 0xff, but a slight\r
+ // complication.\r
+ for (; leading < (dataLength - 1); leading++)\r
+ {\r
+ // Need to check the highest byte of the\r
+ // would-be remaining significant byte is\r
+ // set to indicate this is still a negative number\r
+ \r
+ if ((rd[offset + leading] == (byte) 0xff) && ((rd[offset + leading+1] & (byte) 0x80) != 0))\r
+ continue;\r
+ break;\r
+ }\r
+ }\r
+ \r
+ if ((leading != 0) || (rd.length != dataLength))\r
+ {\r
+ byte[] reduced = new byte[dataLength - leading];\r
+ System.arraycopy(rd, offset + leading, reduced, 0, reduced.length);\r
+ return reduced;\r
+ }\r
+ \r
+ return rd;\r
+ }\r
+\r
+ /**\r
+ * Return the SQL scale of this value, number of digits after the\r
+ * decimal point, or zero for a whole number.\r
+ */\r
+ public int getDecimalValueScale()\r
+ {\r
+ if (isNull())\r
+ return 0;\r
+ \r
+ return sqlScale;\r
+ } \r
+ \r
+ /*\r
+ ** I/O handling\r
+ */\r
+\r
+ /** \r
+ * Distill the Decimal to a byte array and\r
+ * Write out: <UL>\r
+ * <LI> scale (unsigned byte) </LI>\r
+ * <LI> length of byte array </LI>\r
+ * <LI> the byte array </LI> </UL>\r
+ *\r
+ */\r
+ public void writeExternal(ObjectOutput out) throws IOException \r
+ {\r
+ // never called when value is null\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(! isNull());\r
+\r
+ out.writeByte(sqlScale);\r
+ out.writeByte(data2c.length);\r
+ out.write(data2c);\r
+ }\r
+ \r
+ /** \r
+ * Note the use of data2c: we reuse the array if the\r
+ * incoming array is the same length or smaller than\r
+ * the array length. \r
+ * \r
+ * @see java.io.Externalizable#readExternal \r
+ */\r
+ public void readExternal(ObjectInput in) throws IOException \r
+ {\r
+ sqlScale = in.readUnsignedByte();\r
+ int size = in.readUnsignedByte();\r
+\r
+ /*\r
+ ** Allocate a new array if the data to read\r
+ ** is larger than the existing array, or if\r
+ ** we don't have an array yet.\r
+\r
+ Need to use readFully below and NOT just read because read does not\r
+ guarantee getting size bytes back, whereas readFully does (unless EOF).\r
+ */\r
+ if ((data2c == null) || size != data2c.length)\r
+ {\r
+ data2c = new byte[size];\r
+ }\r
+ in.readFully(data2c);\r
+\r
+ }\r
+ public void readExternalFromArray(ArrayInputStream in) throws IOException \r
+ {\r
+ sqlScale = in.readUnsignedByte();\r
+ int size = in.readUnsignedByte();\r
+\r
+ /*\r
+ ** Allocate a new array if the data to read\r
+ ** is larger than the existing array, or if\r
+ ** we don't have an array yet.\r
+\r
+ Need to use readFully below and NOT just read because read does not\r
+ guarantee getting size bytes back, whereas readFully does (unless EOF).\r
+ */\r
+ if ((data2c == null) || size != data2c.length)\r
+ {\r
+ data2c = new byte[size];\r
+ }\r
+ in.readFully(data2c);\r
+ }\r
+\r
+\r
+\r
+ public final int getLength()\r
+ {\r
+ return getDecimalValuePrecision();\r
+ }\r
+\r
+ /* (non-Javadoc)\r
+ * @see org.apache.derby.iapi.types.DataValueDescriptor#getClone()\r
+ */\r
+ public DataValueDescriptor getClone() {\r
+ BinaryDecimal dvd = (BinaryDecimal) getNewNull();\r
+ \r
+ if (this.data2c != null)\r
+ {\r
+ dvd.data2c = new byte[data2c.length];\r
+ System.arraycopy(data2c, 0, dvd.data2c, 0, data2c.length);\r
+ dvd.sqlScale = sqlScale;\r
+ }\r
+ \r
+ return dvd;\r
+ }\r
+\r
+ /* (non-Javadoc)\r
+ * @see org.apache.derby.iapi.types.DataValueDescriptor#setValueFromResultSet(java.sql.ResultSet, int, boolean)\r
+ */\r
+ public void setValueFromResultSet(ResultSet resultSet, int colNumber, boolean isNullable) throws StandardException, SQLException {\r
+ // TODO Auto-generated method stub\r
+ throw StandardException.newException(SQLState.NOT_IMPLEMENTED);\r
+ \r
+ }\r
+\r
+ /* (non-Javadoc)\r
+ * @see org.apache.derby.iapi.types.DataValueDescriptor#estimateMemoryUsage()\r
+ */\r
+ public int estimateMemoryUsage() {\r
+ // TODO Auto-generated method stub\r
+ return 0;\r
+ }\r
+ \r
+ public int hashCode()\r
+ {\r
+ if (isNull())\r
+ return 0;\r
+\r
+ try {\r
+ return (int) getLong();\r
+ } catch (StandardException se)\r
+ {\r
+ return 0;\r
+ }\r
+ }\r
+}\r