/* Copyright 2013 Jonatan Jönsson * * 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 se.softhouse.common.numbers; import static java.lang.String.format; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static se.softhouse.common.numbers.NumberType.BIG_DECIMAL; import static se.softhouse.common.numbers.NumberType.BIG_INTEGER; import static se.softhouse.common.numbers.NumberType.BYTE; import static se.softhouse.common.numbers.NumberType.INTEGER; import static se.softhouse.common.numbers.NumberType.LONG; import static se.softhouse.common.numbers.NumberType.OUT_OF_RANGE; import static se.softhouse.common.numbers.NumberType.SHORT; import static se.softhouse.common.strings.StringsUtil.NEWLINE; import static se.softhouse.common.testlib.Locales.SWEDISH; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Locale; import org.junit.Test; import com.google.common.testing.NullPointerTester; import com.google.common.testing.NullPointerTester.Visibility; /** * Tests for {@link NumberType} */ public class NumberTypeTest { static final BigInteger biggerThanLong = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); @Test public void testMappingOfByteFields() { assertThat(BYTE.maxValue()).isEqualTo(Byte.MAX_VALUE); assertThat(BYTE.minValue()).isEqualTo(Byte.MIN_VALUE); } @Test public void testMappingOfShortFields() { assertThat(SHORT.maxValue()).isEqualTo(Short.MAX_VALUE); assertThat(SHORT.minValue()).isEqualTo(Short.MIN_VALUE); } @Test public void testMappingOfIntegerFields() { assertThat(INTEGER.maxValue()).isEqualTo(Integer.MAX_VALUE); assertThat(INTEGER.minValue()).isEqualTo(Integer.MIN_VALUE); } @Test public void testMappingOfLongFields() { assertThat(LONG.maxValue()).isEqualTo(Long.MAX_VALUE); assertThat(LONG.minValue()).isEqualTo(Long.MIN_VALUE); } @Test public void testNumbersAtBounds() { for(NumberType<?> type : NumberType.TYPES) { assertThat(type.parse("0").intValue()).isZero(); assertThat(type.parse("1").intValue()).isEqualTo(1); assertThat(type.parse("-1").intValue()).isEqualTo(-1); if(NumberType.UNLIMITED_TYPES.contains(type)) { testUnlimitedType(type); } else { // Numbers at the bounds assertThat(type.parse(type.minValue().toString())).isEqualTo(type.minValue()); assertThat(type.parse(type.maxValue().toString())).isEqualTo(type.maxValue()); } // Default values assertThat(type.defaultValue().intValue()).isZero(); } } private void testUnlimitedType(NumberType<?> type) { try { type.minValue(); fail("Unlimited types should not support minValue"); } catch(UnsupportedOperationException expected) { } try { type.maxValue(); fail("Unlimited types should not support maxValue"); } catch(UnsupportedOperationException expected) { } } @Test public void testThatParseCanHandleLargeNumbers() throws Exception { BigInteger bigInteger = BIG_INTEGER.parse(biggerThanLong.toString()); assertThat(bigInteger).isEqualTo(biggerThanLong); BigDecimal biggerThanLongWithFraction = new BigDecimal(biggerThanLong).add(new BigDecimal(2.5)); BigDecimal bigDecimal = BIG_DECIMAL.parse(biggerThanLongWithFraction.toString()); assertThat(bigDecimal).isEqualTo(biggerThanLongWithFraction); } @Test public void testThatDescriptionOfValidValuesReturnsHumanReadableStrings() throws Exception { assertThat(BYTE.descriptionOfValidValues(SWEDISH)).isEqualTo("-128 to 127"); assertThat(SHORT.descriptionOfValidValues(SWEDISH)).isEqualTo("-32 768 to 32 767"); assertThat(INTEGER.descriptionOfValidValues(SWEDISH)).isEqualTo("-2 147 483 648 to 2 147 483 647"); assertThat(LONG.descriptionOfValidValues(SWEDISH)).isEqualTo("-9 223 372 036 854 775 808 to 9 223 372 036 854 775 807"); assertThat(BIG_INTEGER.descriptionOfValidValues(SWEDISH)).isEqualTo("an arbitrary integer number (practically no limits)"); assertThat(BIG_DECIMAL.descriptionOfValidValues(SWEDISH)).isEqualTo("an arbitrary decimal number (practically no limits)"); } @Test(expected = IllegalArgumentException.class) public void testThatEmptyInputThrows() throws Exception { BYTE.parse(""); } @Test public void testThatNumbersTruncatesFractions() throws Exception { assertThat(BYTE.from(2.5)).isEqualTo((byte) 2); assertThat(SHORT.from(2.5)).isEqualTo((short) 2); assertThat(INTEGER.from(2.5)).isEqualTo(2); assertThat(LONG.from(2.5)).isEqualTo(2); assertThat(BIG_INTEGER.from(BigDecimal.valueOf(2.5))).isEqualTo(BigInteger.valueOf(2)); } @Test public void testThatBigDecimalUsesAsHighPrecisionAsPossible() throws Exception { assertThat(BIG_DECIMAL.from(Double.MAX_VALUE)).isEqualTo(BigDecimal.valueOf(Double.MAX_VALUE)); assertThat(BIG_DECIMAL.from(2.5)).isEqualTo(BigDecimal.valueOf(2.5)); assertThat(BIG_DECIMAL.from(Long.MAX_VALUE)).isEqualTo(BigDecimal.valueOf(((Long) Long.MAX_VALUE).doubleValue())); assertThat(BIG_DECIMAL.from(biggerThanLong)).isEqualTo(new BigDecimal(biggerThanLong)); } @Test public void testThatBigIntegerUsesLongValueOnNumber() throws Exception { assertThat(BIG_INTEGER.from(Long.MAX_VALUE)).isEqualTo(BigInteger.valueOf(Long.MAX_VALUE)); assertThat(BIG_INTEGER.from(Double.MAX_VALUE)).isEqualTo(BigInteger.valueOf(((Double) Double.MAX_VALUE).longValue())); } @Test public void testThatFromReturnsTheArgumentIfItsOfTheSameType() throws Exception { BigInteger integer = new BigInteger("1"); assertThat(BIG_INTEGER.from(integer)).isSameAs(integer); BigDecimal decimal = new BigDecimal("2.5"); assertThat(BIG_DECIMAL.from(decimal)).isSameAs(decimal); } @Test public void testThatToStringReturnsName() { for(NumberType<?> type : NumberType.TYPES) { assertThat(type.toString()).isEqualTo(type.name()); } } @Test public void testInvalidShortNumbers() { List<Integer> invalidInput = Arrays.asList(Short.MIN_VALUE - 1, Short.MAX_VALUE + 1); for(Integer input : invalidInput) { try { SHORT.parse(input.toString()); fail("Invalid short input not detected: " + input); } catch(IllegalArgumentException expected) { assertThat(expected).hasMessage(format(OUT_OF_RANGE, input, "-32,768", "32,767")); } } } @Test public void testInvalidIntegerNumbers() { List<Long> invalidInput = Arrays.asList((long) Integer.MIN_VALUE - 1, (long) Integer.MAX_VALUE + 1); for(Long input : invalidInput) { try { INTEGER.parse(input.toString()); fail("Invalid integer input not detected: " + input); } catch(IllegalArgumentException expected) { assertThat(expected).hasMessage(format(OUT_OF_RANGE, input, "-2,147,483,648", "2,147,483,647")); } } } @Test public void testInvalidLongNumbers() { List<BigInteger> invalidInput = Arrays.asList(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), biggerThanLong); for(BigInteger input : invalidInput) { try { LONG.parse(input.toString()); fail("Invalid long input not detected: " + input); } catch(IllegalArgumentException expected) { assertThat(expected).hasMessage(format(OUT_OF_RANGE, input, "-9,223,372,036,854,775,808", "9,223,372,036,854,775,807")); } } } @Test public void testThatNullContractsAreFollowed() { for(NumberType<?> numberType : NumberType.TYPES) { new NullPointerTester().testInstanceMethods(numberType, Visibility.PUBLIC); } new NullPointerTester().testStaticMethods(NumberType.class, Visibility.PACKAGE); } @Test public void testThatUnparsableIntegerGeneratesProperErrorMessage() throws Exception { try { INTEGER.parse("123a", Locale.ENGLISH); fail("a should not be a parsable number"); } catch(IllegalArgumentException e) { /** * @formatter.off */ assertThat(e).hasMessage("'123a' is not a valid integer (Localization: English)" + NEWLINE + " ^"); /** * @formatter.on */ } } @Test public void testThatUnparsableBigIntegerGeneratesProperErrorMessage() throws Exception { try { BIG_INTEGER.parse("12.3", Locale.ENGLISH); fail("12.3 should not be a parsable big-integer"); } catch(IllegalArgumentException e) { /** * @formatter.off */ assertThat(e).hasMessage("'12.3' is not a valid big-integer (Localization: English)" + NEWLINE + " ^"); /** * @formatter.on */ } } @Test public void testThatDecimalSeparatorCausesParseErrorForDiscreetTypes() throws Exception { List<NumberType<?>> discreetTypes = Arrays.<NumberType<?>>asList(BYTE, SHORT, INTEGER, LONG, BIG_INTEGER); for(NumberType<?> discreetType : discreetTypes) { try { discreetType.parse("12.3", Locale.ENGLISH); fail("12.3 should not be a parsable " + discreetType); } catch(IllegalArgumentException e) { assertThat(e.getMessage()).contains("is not a valid " + discreetType); } } } @Test public void testThatSmallEnoughDataTypesAreInLongRange() throws Exception { assertThat(LONG.inRange((byte) 42)).isTrue(); assertThat(LONG.inRange((short) 42)).isTrue(); assertThat(LONG.inRange(42)).isTrue(); assertThat(LONG.inRange(BigDecimal.valueOf(Long.MAX_VALUE))).isTrue(); assertThat(LONG.inRange(BigInteger.valueOf(Long.MIN_VALUE))).isTrue(); assertThat(LONG.inRange(biggerThanLong)).isFalse(); } @Test public void testThatBigDecimalMustBeDiscreetForItToFitInABigInteger() throws Exception { assertThat(BIG_INTEGER.inRange(BigDecimal.valueOf(1.23))).isFalse(); assertThat(BIG_INTEGER.inRange(BigDecimal.valueOf(123))).isTrue(); assertThat(BIG_INTEGER.inRange(biggerThanLong)).isTrue(); } }