/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bson.types;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;

import static java.math.MathContext.DECIMAL128;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of significand and an exponent range of -6143 to +6144.
See Also:
Since:3.4
/** * A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of significand and an exponent range * of -6143 to +6144. * * @since 3.4 * @see <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst">BSON Decimal128 * specification</a> * @see <a href="https://en.wikipedia.org/wiki/Binary_Integer_Decimal">binary integer decimal</a> * @see <a href="https://en.wikipedia.org/wiki/Decimal128_floating-point_format">decimal128 floating-point format</a> * @see <a href="http://ieeexplore.ieee.org/document/4610935/">754-2008 - IEEE Standard for Floating-Point Arithmetic</a> */
public final class Decimal128 extends Number implements Comparable<Decimal128> { private static final long serialVersionUID = 4570973266503637887L; private static final long INFINITY_MASK = 0x7800000000000000L; private static final long NaN_MASK = 0x7c00000000000000L; private static final long SIGN_BIT_MASK = 1L << 63; private static final int MIN_EXPONENT = -6176; private static final int MAX_EXPONENT = 6111; private static final int EXPONENT_OFFSET = 6176; private static final int MAX_BIT_LENGTH = 113; private static final BigInteger BIG_INT_TEN = new BigInteger("10"); private static final BigInteger BIG_INT_ONE = new BigInteger("1"); private static final BigInteger BIG_INT_ZERO = new BigInteger("0"); private static final Set<String> NaN_STRINGS = new HashSet<String>(singletonList("nan")); private static final Set<String> NEGATIVE_NaN_STRINGS = new HashSet<String>(singletonList("-nan")); private static final Set<String> POSITIVE_INFINITY_STRINGS = new HashSet<String>(asList("inf", "+inf", "infinity", "+infinity")); private static final Set<String> NEGATIVE_INFINITY_STRINGS = new HashSet<String>(asList("-inf", "-infinity"));
A constant holding the positive infinity of type Decimal128. It is equal to the value return by Decimal128.valueOf("Infinity").
/** * A constant holding the positive infinity of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("Infinity")}. */
public static final Decimal128 POSITIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK, 0);
A constant holding the negative infinity of type Decimal128. It is equal to the value return by Decimal128.valueOf("-Infinity").
/** * A constant holding the negative infinity of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("-Infinity")}. */
public static final Decimal128 NEGATIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK | SIGN_BIT_MASK, 0);
A constant holding a negative Not-a-Number (-NaN) value of type Decimal128. It is equal to the value return by Decimal128.valueOf("-NaN").
/** * A constant holding a negative Not-a-Number (-NaN) value of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("-NaN")}. */
public static final Decimal128 NEGATIVE_NaN = fromIEEE754BIDEncoding(NaN_MASK | SIGN_BIT_MASK, 0);
A constant holding a Not-a-Number (NaN) value of type Decimal128. It is equal to the value return by Decimal128.valueOf("NaN").
/** * A constant holding a Not-a-Number (NaN) value of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("NaN")}. */
public static final Decimal128 NaN = fromIEEE754BIDEncoding(NaN_MASK, 0);
A constant holding a postive zero value of type Decimal128. It is equal to the value return by Decimal128.valueOf("0").
/** * A constant holding a postive zero value of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("0")}. */
public static final Decimal128 POSITIVE_ZERO = fromIEEE754BIDEncoding(0x3040000000000000L, 0x0000000000000000L);
A constant holding a negative zero value of type Decimal128. It is equal to the value return by Decimal128.valueOf("-0").
/** * A constant holding a negative zero value of type {@code Decimal128}. It is equal to the value return by * {@code Decimal128.valueOf("-0")}. */
public static final Decimal128 NEGATIVE_ZERO = fromIEEE754BIDEncoding(0xb040000000000000L, 0x0000000000000000L); private final long high; private final long low;
Returns a Decimal128 value representing the given String.
Params:
  • value – the Decimal128 value represented as a String
Throws:
See Also:
Returns:the Decimal128 value representing the given String
/** * Returns a Decimal128 value representing the given String. * * @param value the Decimal128 value represented as a String * @return the Decimal128 value representing the given String * @throws NumberFormatException if the value is out of the Decimal128 range * @see * <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst#from-string-representation"> * From-String Specification</a> */
public static Decimal128 parse(final String value) { String lowerCasedValue = value.toLowerCase(); if (NaN_STRINGS.contains(lowerCasedValue)) { return NaN; } if (NEGATIVE_NaN_STRINGS.contains(lowerCasedValue)) { return NEGATIVE_NaN; } if (POSITIVE_INFINITY_STRINGS.contains(lowerCasedValue)) { return POSITIVE_INFINITY; } if (NEGATIVE_INFINITY_STRINGS.contains(lowerCasedValue)) { return NEGATIVE_INFINITY; } return new Decimal128(new BigDecimal(value), value.charAt(0) == '-'); }
Create an instance with the given high and low order bits representing this Decimal128 as an IEEE 754-2008 128-bit decimal floating point using the BID encoding scheme.
Params:
  • high – the high-order 64 bits
  • low – the low-order 64 bits
Returns:the Decimal128 value representing the given high and low order bits
/** * Create an instance with the given high and low order bits representing this Decimal128 as an IEEE 754-2008 128-bit decimal * floating point using the BID encoding scheme. * * @param high the high-order 64 bits * @param low the low-order 64 bits * @return the Decimal128 value representing the given high and low order bits */
public static Decimal128 fromIEEE754BIDEncoding(final long high, final long low) { return new Decimal128(high, low); }
Constructs a Decimal128 value representing the given long.
Params:
  • value – the Decimal128 value represented as a long
/** * Constructs a Decimal128 value representing the given long. * * @param value the Decimal128 value represented as a long */
public Decimal128(final long value) { this(new BigDecimal(value, DECIMAL128)); }
Constructs a Decimal128 value representing the given BigDecimal.
Params:
  • value – the Decimal128 value represented as a BigDecimal
Throws:
/** * Constructs a Decimal128 value representing the given BigDecimal. * * @param value the Decimal128 value represented as a BigDecimal * @throws NumberFormatException if the value is out of the Decimal128 range */
public Decimal128(final BigDecimal value) { this(value, value.signum() == -1); } private Decimal128(final long high, final long low) { this.high = high; this.low = low; } // isNegative is necessary to detect -0, which can't be represented with a BigDecimal private Decimal128(final BigDecimal initialValue, final boolean isNegative) { long localHigh = 0; long localLow = 0; BigDecimal value = clampAndRound(initialValue); long exponent = -value.scale(); if ((exponent < MIN_EXPONENT) || (exponent > MAX_EXPONENT)) { throw new AssertionError("Exponent is out of range for Decimal128 encoding: " + exponent); } if (value.unscaledValue().bitLength() > MAX_BIT_LENGTH) { throw new AssertionError("Unscaled roundedValue is out of range for Decimal128 encoding:" + value.unscaledValue()); } BigInteger significand = value.unscaledValue().abs(); int bitLength = significand.bitLength(); for (int i = 0; i < Math.min(64, bitLength); i++) { if (significand.testBit(i)) { localLow |= 1L << i; } } for (int i = 64; i < bitLength; i++) { if (significand.testBit(i)) { localHigh |= 1L << (i - 64); } } long biasedExponent = exponent + EXPONENT_OFFSET; localHigh |= biasedExponent << 49; if (value.signum() == -1 || isNegative) { localHigh |= SIGN_BIT_MASK; } high = localHigh; low = localLow; } private BigDecimal clampAndRound(final BigDecimal initialValue) { BigDecimal value; if (-initialValue.scale() > MAX_EXPONENT) { int diff = -initialValue.scale() - MAX_EXPONENT; if (initialValue.unscaledValue().equals(BIG_INT_ZERO)) { value = new BigDecimal(initialValue.unscaledValue(), -MAX_EXPONENT); } else if (diff + initialValue.precision() > 34) { throw new NumberFormatException("Exponent is out of range for Decimal128 encoding of " + initialValue); } else { BigInteger multiplier = BIG_INT_TEN.pow(diff); value = new BigDecimal(initialValue.unscaledValue().multiply(multiplier), initialValue.scale() + diff); } } else if (-initialValue.scale() < MIN_EXPONENT) { // Increasing a very negative exponent may require decreasing precision, which is rounding // Only round exactly (by removing precision that is all zeroes). An exception is thrown if the rounding would be inexact: // Exact: .000...0011000 => 11000E-6177 => 1100E-6176 => .000001100 // Inexact: .000...0011001 => 11001E-6177 => 1100E-6176 => .000001100 int diff = initialValue.scale() + MIN_EXPONENT; int undiscardedPrecision = ensureExactRounding(initialValue, diff); BigInteger divisor = undiscardedPrecision == 0 ? BIG_INT_ONE : BIG_INT_TEN.pow(diff); value = new BigDecimal(initialValue.unscaledValue().divide(divisor), initialValue.scale() - diff); } else { value = initialValue.round(DECIMAL128); int extraPrecision = initialValue.precision() - value.precision(); if (extraPrecision > 0) { // Again, only round exactly ensureExactRounding(initialValue, extraPrecision); } } return value; } private int ensureExactRounding(final BigDecimal initialValue, final int extraPrecision) { String significand = initialValue.unscaledValue().abs().toString(); int undiscardedPrecision = Math.max(0, significand.length() - extraPrecision); for (int i = undiscardedPrecision; i < significand.length(); i++) { if (significand.charAt(i) != '0') { throw new NumberFormatException("Conversion to Decimal128 would require inexact rounding of " + initialValue); } } return undiscardedPrecision; }
Gets the high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding scheme.
Returns:the high-order 64 bits of this Decimal128
/** * Gets the high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding * scheme. * * @return the high-order 64 bits of this Decimal128 */
public long getHigh() { return high; }
Gets the low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding scheme.
Returns:the low-order 64 bits of this Decimal128
/** * Gets the low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding * scheme. * * @return the low-order 64 bits of this Decimal128 */
public long getLow() { return low; }
Gets a BigDecimal that is equivalent to this Decimal128.
Throws:
  • ArithmeticException – if the Decimal128 value is NaN, Infinity, -Infinity, or -0, none of which can be represented as a BigDecimal
Returns:a BigDecimal that is equivalent to this Decimal128
/** * Gets a BigDecimal that is equivalent to this Decimal128. * * @return a BigDecimal that is equivalent to this Decimal128 * @throws ArithmeticException if the Decimal128 value is NaN, Infinity, -Infinity, or -0, none of which can be represented as a * BigDecimal */
public BigDecimal bigDecimalValue() { if (isNaN()) { throw new ArithmeticException("NaN can not be converted to a BigDecimal"); } if (isInfinite()) { throw new ArithmeticException("Infinity can not be converted to a BigDecimal"); } BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); // If the BigDecimal is 0, but the Decimal128 is negative, that means we have -0. if (isNegative() && bigDecimal.signum() == 0) { throw new ArithmeticException("Negative zero can not be converted to a BigDecimal"); } return bigDecimal; } // Make sure that the argument comes from a call to bigDecimalValueNoNegativeZeroCheck on this instance private boolean hasDifferentSign(final BigDecimal bigDecimal) { return isNegative() && bigDecimal.signum() == 0; } private boolean isZero(final BigDecimal bigDecimal) { return !isNaN() && !isInfinite() && bigDecimal.compareTo(BigDecimal.ZERO) == 0; } private BigDecimal bigDecimalValueNoNegativeZeroCheck() { int scale = -getExponent(); if (twoHighestCombinationBitsAreSet()) { return BigDecimal.valueOf(0, scale); } return new BigDecimal(new BigInteger(isNegative() ? -1 : 1, getBytes()), scale); } // May have leading zeros. Strip them before considering making this method public private byte[] getBytes() { byte[] bytes = new byte[15]; long mask = 0x00000000000000ff; for (int i = 14; i >= 7; i--) { bytes[i] = (byte) ((low & mask) >>> ((14 - i) << 3)); mask = mask << 8; } mask = 0x00000000000000ff; for (int i = 6; i >= 1; i--) { bytes[i] = (byte) ((high & mask) >>> ((6 - i) << 3)); mask = mask << 8; } mask = 0x0001000000000000L; bytes[0] = (byte) ((high & mask) >>> 48); return bytes; } private int getExponent() { if (twoHighestCombinationBitsAreSet()) { return (int) ((high & 0x1fffe00000000000L) >>> 47) - EXPONENT_OFFSET; } else { return (int) ((high & 0x7fff800000000000L) >>> 49) - EXPONENT_OFFSET; } } private boolean twoHighestCombinationBitsAreSet() { return (high & 3L << 61) == 3L << 61; }
Returns true if this Decimal128 is negative.
Returns:true if this Decimal128 is negative
/** * Returns true if this Decimal128 is negative. * * @return true if this Decimal128 is negative */
public boolean isNegative() { return (high & SIGN_BIT_MASK) == SIGN_BIT_MASK; }
Returns true if this Decimal128 is infinite.
Returns:true if this Decimal128 is infinite
/** * Returns true if this Decimal128 is infinite. * * @return true if this Decimal128 is infinite */
public boolean isInfinite() { return (high & INFINITY_MASK) == INFINITY_MASK; }
Returns true if this Decimal128 is finite.
Returns:true if this Decimal128 is finite
/** * Returns true if this Decimal128 is finite. * * @return true if this Decimal128 is finite */
public boolean isFinite() { return !isInfinite(); }
Returns true if this Decimal128 is Not-A-Number (NaN).
Returns:true if this Decimal128 is Not-A-Number
/** * Returns true if this Decimal128 is Not-A-Number (NaN). * * @return true if this Decimal128 is Not-A-Number */
public boolean isNaN() { return (high & NaN_MASK) == NaN_MASK; } @Override public int compareTo(final Decimal128 o) { if (isNaN()) { return o.isNaN() ? 0 : 1; } if (isInfinite()) { if (isNegative()) { if (o.isInfinite() && o.isNegative()) { return 0; } else { return -1; } } else { if (o.isNaN()) { return -1; } else if (o.isInfinite() && !o.isNegative()) { return 0; } else { return 1; } } } BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); BigDecimal otherBigDecimal = o.bigDecimalValueNoNegativeZeroCheck(); if (isZero(bigDecimal) && o.isZero(otherBigDecimal)) { if (hasDifferentSign(bigDecimal)) { if (o.hasDifferentSign(otherBigDecimal)) { return 0; } else { return -1; } } else if (o.hasDifferentSign(otherBigDecimal)) { return 1; } } if (o.isNaN()) { return -1; } else if (o.isInfinite()) { if (o.isNegative()) { return 1; } else { return -1; } } else { return bigDecimal.compareTo(otherBigDecimal); } }
Converts this Decimal128 to a int. This conversion is analogous to the narrowing primitive conversion from double to int as defined in The Java™ Language Specification: any fractional part of this Decimal128 will be discarded, and if the resulting integral value is too big to fit in a int, only the low-order 32 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this Decimal128 value as well as return a result with the opposite sign. Note that #NEGATIVE_ZERO is converted to 0.
Returns:this Decimal128 converted to a int.
Since:3.10
/** * Converts this {@code Decimal128} to a {@code int}. This conversion is analogous to the <i>narrowing primitive conversion</i> from * {@code double} to {@code int} as defined in <cite>The Java&trade; Language Specification</cite>: any fractional part of this * {@code Decimal128} will be discarded, and if the resulting integral value is too big to fit in a {@code int}, only the * low-order 32 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this * {@code Decimal128} value as well as return a result with the opposite sign. Note that {@code #NEGATIVE_ZERO} is converted to * {@code 0}. * * @return this {@code Decimal128} converted to a {@code int}. * @since 3.10 */
@Override public int intValue() { return (int) doubleValue(); }
Converts this Decimal128 to a long. This conversion is analogous to the narrowing primitive conversion from double to long as defined in The Java™ Language Specification: any fractional part of this Decimal128 will be discarded, and if the resulting integral value is too big to fit in a long, only the low-order 64 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this Decimal128 value as well as return a result with the opposite sign. Note that #NEGATIVE_ZERO is converted to 0L.
Returns:this Decimal128 converted to a long.
Since:3.10
/** * Converts this {@code Decimal128} to a {@code long}. This conversion is analogous to the <i>narrowing primitive conversion</i> from * {@code double} to {@code long} as defined in <cite>The Java&trade; Language Specification</cite>: any fractional part of this * {@code Decimal128} will be discarded, and if the resulting integral value is too big to fit in a {@code long}, only the * low-order 64 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this * {@code Decimal128} value as well as return a result with the opposite sign. Note that {@code #NEGATIVE_ZERO} is converted to * {@code 0L}. * * @return this {@code Decimal128} converted to a {@code long}. * @since 3.10 */
@Override public long longValue() { return (long) doubleValue(); }
Converts this Decimal128 to a float. This conversion is similar to the narrowing primitive conversion from double to float as defined in The Java™ Language Specification: if this Decimal128 has too great a magnitude to represent as a float, it will be converted to Float.NEGATIVE_INFINITY or Float.POSITIVE_INFINITY as appropriate. Note that even when the return value is finite, this conversion can lose information about the precision of the Decimal128 value. Note that #NEGATIVE_ZERO is converted to 0.0f.
Returns:this Decimal128 converted to a float.
Since:3.10
/** * Converts this {@code Decimal128} to a {@code float}. This conversion is similar to the <i>narrowing primitive conversion</i> from * {@code double} to {@code float} as defined in <cite>The Java&trade; Language Specification</cite>: if this {@code Decimal128} has * too great a magnitude to represent as a {@code float}, it will be converted to {@link Float#NEGATIVE_INFINITY} or * {@link Float#POSITIVE_INFINITY} as appropriate. Note that even when the return value is finite, this conversion can lose * information about the precision of the {@code Decimal128} value. Note that {@code #NEGATIVE_ZERO} is converted to {@code 0.0f}. * * @return this {@code Decimal128} converted to a {@code float}. * @since 3.10 */
@Override public float floatValue() { return (float) doubleValue(); }
Converts this Decimal128 to a double. This conversion is similar to the narrowing primitive conversion from double to float as defined in The Java™ Language Specification: if this Decimal128 has too great a magnitude to represent as a double, it will be converted to Double.NEGATIVE_INFINITY or Double.POSITIVE_INFINITY as appropriate. Note that even when the return value is finite, this conversion can lose information about the precision of the Decimal128 value. Note that #NEGATIVE_ZERO is converted to 0.0d.
Returns:this Decimal128 converted to a double.
Since:3.10
/** * Converts this {@code Decimal128} to a {@code double}. This conversion is similar to the <i>narrowing primitive conversion</i> from * {@code double} to {@code float} as defined in <cite>The Java&trade; Language Specification</cite>: if this {@code Decimal128} has * too great a magnitude to represent as a {@code double}, it will be converted to {@link Double#NEGATIVE_INFINITY} or * {@link Double#POSITIVE_INFINITY} as appropriate. Note that even when the return value is finite, this conversion can lose * information about the precision of the {@code Decimal128} value. Note that {@code #NEGATIVE_ZERO} is converted to {@code 0.0d}. * * @return this {@code Decimal128} converted to a {@code double}. * @since 3.10 */
@Override public double doubleValue() { if (isNaN()) { return Double.NaN; } if (isInfinite()) { if (isNegative()) { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; } } BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); if (hasDifferentSign(bigDecimal)) { return -0.0d; } return bigDecimal.doubleValue(); }
Returns true if the encoded representation of this instance is the same as the encoded representation of o.

One consequence is that, whereas Double.NaN != Double.NaN, new Decimal128("NaN").equals(new Decimal128("NaN") returns true.

Another consequence is that, as with BigDecimal, new Decimal128("1.0").equals(new Decimal128("1.00") returns false, because the precision is not the same and therefore the representation is not the same.

Params:
  • o – the object to compare for equality
Returns:true if the instances are equal
/** * Returns true if the encoded representation of this instance is the same as the encoded representation of {@code o}. * <p> * One consequence is that, whereas {@code Double.NaN != Double.NaN}, * {@code new Decimal128("NaN").equals(new Decimal128("NaN")} returns true. * </p> * <p> * Another consequence is that, as with BigDecimal, {@code new Decimal128("1.0").equals(new Decimal128("1.00")} returns false, * because the precision is not the same and therefore the representation is not the same. * </p> * * @param o the object to compare for equality * @return true if the instances are equal */
@Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Decimal128 that = (Decimal128) o; if (high != that.high) { return false; } if (low != that.low) { return false; } return true; } @Override public int hashCode() { int result = (int) (low ^ (low >>> 32)); result = 31 * result + (int) (high ^ (high >>> 32)); return result; }
Returns the String representation of the Decimal128 value.
See Also:
Returns:the String representation
/** * Returns the String representation of the Decimal128 value. * * @return the String representation * @see <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst#to-string-representation"> * To-String Sprecification</a> */
@Override public String toString() { if (isNaN()) { return "NaN"; } if (isInfinite()) { if (isNegative()) { return "-Infinity"; } else { return "Infinity"; } } return toStringWithBigDecimal(); } private String toStringWithBigDecimal() { StringBuilder buffer = new StringBuilder(); BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); String significand = bigDecimal.unscaledValue().abs().toString(); if (isNegative()) { buffer.append('-'); } int exponent = -bigDecimal.scale(); int adjustedExponent = exponent + (significand.length() - 1); if (exponent <= 0 && adjustedExponent >= -6) { if (exponent == 0) { buffer.append(significand); } else { int pad = -exponent - significand.length(); if (pad >= 0) { buffer.append('0'); buffer.append('.'); for (int i = 0; i < pad; i++) { buffer.append('0'); } buffer.append(significand, 0, significand.length()); } else { buffer.append(significand, 0, -pad); buffer.append('.'); buffer.append(significand, -pad, -pad - exponent); } } } else { buffer.append(significand.charAt(0)); if (significand.length() > 1) { buffer.append('.'); buffer.append(significand, 1, significand.length()); } buffer.append('E'); if (adjustedExponent > 0) { buffer.append('+'); } buffer.append(adjustedExponent); } return buffer.toString(); } }