commit dfcf4e96b9286414ed63c2a566f3ed586363c0a0 Author: Claudio Maggioni Date: Tue Dec 6 15:35:18 2022 +0100 Added sources from apache commons lang diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2140a37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80b27e1 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# SDM assigment 04 + +Classes come from [https://github.com/apache/commons-lang](https://github.com/apache/commons-lang) +revision `770e72d2f78361b14f3fe27caea41e5977d3c638` + +## Run Tests + +``` +mvn clean test +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f68e80b --- /dev/null +++ b/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + org.example + sdm04 + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + org.assertj + assertj-core + 3.23.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.9.0 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + 1.1.0 + + + + + + \ No newline at end of file diff --git a/src/main/java/ch/usi/inf/sdm/sdm04/CharRange.java b/src/main/java/ch/usi/inf/sdm/sdm04/CharRange.java new file mode 100644 index 0000000..1eb547c --- /dev/null +++ b/src/main/java/ch/usi/inf/sdm/sdm04/CharRange.java @@ -0,0 +1,379 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * A contiguous range of characters, optionally negated. + * + *

Instances are immutable.

+ * + *

#ThreadSafe#

+ * + * @since 1.0 + */ +// TODO: This is no longer public and will be removed later as CharSet is moved +// to depend on Range. +final class CharRange implements Iterable, Serializable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see Serializable + */ + private static final long serialVersionUID = 8270183163158333422L; + + /** + * The first character, inclusive, in the range. + */ + private final char start; + + /** + * The last character, inclusive, in the range. + */ + private final char end; + + /** + * True if the range is everything except the characters specified. + */ + private final boolean negated; + + /** + * Cached toString. + */ + private transient String iToString; + + /** + * Empty array. + */ + static final CharRange[] EMPTY_ARRAY = {}; + + /** + * Constructs a {@link CharRange} over a set of characters, + * optionally negating the range. + * + *

A negated range includes everything except that defined by the + * start and end characters.

+ * + *

If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @param negated true to express everything except the range + */ + private CharRange(char start, char end, final boolean negated) { + if (start > end) { + final char temp = start; + start = end; + end = temp; + } + + this.start = start; + this.end = end; + this.negated = negated; + } + + /** + * Constructs a {@link CharRange} over a single character. + * + * @param ch only character in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange is(final char ch) { + return new CharRange(ch, ch, false); + } + + /** + * Constructs a negated {@link CharRange} over a single character. + * + *

A negated range includes everything except that defined by the + * single character.

+ * + * @param ch only character in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNot(final char ch) { + return new CharRange(ch, ch, true); + } + + /** + * Constructs a {@link CharRange} over a set of characters. + * + *

If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isIn(final char start, final char end) { + return new CharRange(start, end, false); + } + + /** + * Constructs a negated {@link CharRange} over a set of characters. + * + *

A negated range includes everything except that defined by the + * start and end characters.

+ * + *

If start and end are in the wrong order, they are reversed. + * Thus {@code a-e} is the same as {@code e-a}.

+ * + * @param start first character, inclusive, in this range + * @param end last character, inclusive, in this range + * @return the new CharRange object + * @since 2.5 + */ + public static CharRange isNotIn(final char start, final char end) { + return new CharRange(start, end, true); + } + + // Accessors + /** + * Gets the start character for this character range. + * + * @return the start char (inclusive) + */ + public char getStart() { + return this.start; + } + + /** + * Gets the end character for this character range. + * + * @return the end char (inclusive) + */ + public char getEnd() { + return this.end; + } + + /** + * Is this {@link CharRange} negated. + * + *

A negated range includes everything except that defined by the + * start and end characters.

+ * + * @return {@code true} if negated + */ + public boolean isNegated() { + return negated; + } + + // Contains + /** + * Is the character specified contained in this range. + * + * @param ch the character to check + * @return {@code true} if this range contains the input character + */ + public boolean contains(final char ch) { + return (ch >= start && ch <= end) != negated; + } + + /** + * Are all the characters of the passed in range contained in + * this range. + * + * @param range the range to check against + * @return {@code true} if this range entirely contains the input range + * @throws NullPointerException if {@code null} input + */ + public boolean contains(final CharRange range) { + Objects.requireNonNull(range, "range"); + if (negated) { + if (range.negated) { + return start >= range.start && end <= range.end; + } + return range.end < start || range.start > end; + } + if (range.negated) { + return start == 0 && end == Character.MAX_VALUE; + } + return start <= range.start && end >= range.end; + } + + // Basics + /** + * Compares two CharRange objects, returning true if they represent + * exactly the same range of characters defined in the same way. + * + * @param obj the object to compare to + * @return true if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CharRange)) { + return false; + } + final CharRange other = (CharRange) obj; + return start == other.start && end == other.end && negated == other.negated; + } + + /** + * Gets a hashCode compatible with the equals method. + * + * @return a suitable hashCode + */ + @Override + public int hashCode() { + return 83 + start + 7 * end + (negated ? 1 : 0); + } + + /** + * Gets a string representation of the character range. + * + * @return string representation of this range + */ + @Override + public String toString() { + if (iToString == null) { + final StringBuilder buf = new StringBuilder(4); + if (isNegated()) { + buf.append('^'); + } + buf.append(start); + if (start != end) { + buf.append('-'); + buf.append(end); + } + iToString = buf.toString(); + } + return iToString; + } + + /** + * Returns an iterator which can be used to walk through the characters described by this range. + * + *

#NotThreadSafe# the iterator is not thread-safe

+ * + * @return an iterator to the chars represented by this range + * @since 2.5 + */ + @Override + public Iterator iterator() { + return new CharacterIterator(this); + } + + /** + * Character {@link Iterator}. + *

#NotThreadSafe#

+ */ + private static class CharacterIterator implements Iterator { + /** + * The current character + */ + private char current; + + private final CharRange range; + private boolean hasNext; + + /** + * Constructs a new iterator for the character range. + * + * @param r The character range + */ + private CharacterIterator(final CharRange r) { + range = r; + hasNext = true; + + if (range.negated) { + if (range.start == 0) { + if (range.end == Character.MAX_VALUE) { + // This range is an empty set + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = 0; + } + } else { + current = range.start; + } + } + + /** + * Prepares the next character in the range. + */ + private void prepareNext() { + if (range.negated) { + if (current == Character.MAX_VALUE) { + hasNext = false; + } else if (current + 1 == range.start) { + if (range.end == Character.MAX_VALUE) { + hasNext = false; + } else { + current = (char) (range.end + 1); + } + } else { + current = (char) (current + 1); + } + } else if (current < range.end) { + current = (char) (current + 1); + } else { + hasNext = false; + } + } + + /** + * Has the iterator not reached the end character yet? + * + * @return {@code true} if the iterator has yet to reach the character date + */ + @Override + public boolean hasNext() { + return hasNext; + } + + /** + * Returns the next character in the iteration + * + * @return {@link Character} for the next character + */ + @Override + public Character next() { + if (!hasNext) { + throw new NoSuchElementException(); + } + final char cur = current; + prepareNext(); + return Character.valueOf(cur); + } + + /** + * Always throws UnsupportedOperationException. + * + * @throws UnsupportedOperationException Always thrown. + * @see Iterator#remove() + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/ch/usi/inf/sdm/sdm04/math/Fraction.java b/src/main/java/ch/usi/inf/sdm/sdm04/math/Fraction.java new file mode 100644 index 0000000..e863d27 --- /dev/null +++ b/src/main/java/ch/usi/inf/sdm/sdm04/math/Fraction.java @@ -0,0 +1,911 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04.math; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * {@link Fraction} is a {@link Number} implementation that + * stores fractions accurately. + * + *

This class is immutable, and interoperable with most methods that accept + * a {@link Number}.

+ * + *

Note that this class is intended for common use cases, it is int + * based and thus suffers from various overflow issues. For a BigInteger based + * equivalent, please see the Commons Math BigFraction class.

+ * + * @since 2.0 + */ +public final class Fraction extends Number implements Comparable { + + /** + * Required for serialization support. Lang version 2.0. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 65382027393090L; + + /** + * {@link Fraction} representation of 0. + */ + public static final Fraction ZERO = new Fraction(0, 1); + /** + * {@link Fraction} representation of 1. + */ + public static final Fraction ONE = new Fraction(1, 1); + /** + * {@link Fraction} representation of 1/2. + */ + public static final Fraction ONE_HALF = new Fraction(1, 2); + /** + * {@link Fraction} representation of 1/3. + */ + public static final Fraction ONE_THIRD = new Fraction(1, 3); + /** + * {@link Fraction} representation of 2/3. + */ + public static final Fraction TWO_THIRDS = new Fraction(2, 3); + /** + * {@link Fraction} representation of 1/4. + */ + public static final Fraction ONE_QUARTER = new Fraction(1, 4); + /** + * {@link Fraction} representation of 2/4. + */ + public static final Fraction TWO_QUARTERS = new Fraction(2, 4); + /** + * {@link Fraction} representation of 3/4. + */ + public static final Fraction THREE_QUARTERS = new Fraction(3, 4); + /** + * {@link Fraction} representation of 1/5. + */ + public static final Fraction ONE_FIFTH = new Fraction(1, 5); + /** + * {@link Fraction} representation of 2/5. + */ + public static final Fraction TWO_FIFTHS = new Fraction(2, 5); + /** + * {@link Fraction} representation of 3/5. + */ + public static final Fraction THREE_FIFTHS = new Fraction(3, 5); + /** + * {@link Fraction} representation of 4/5. + */ + public static final Fraction FOUR_FIFTHS = new Fraction(4, 5); + + + /** + * The numerator number part of the fraction (the three in three sevenths). + */ + private final int numerator; + /** + * The denominator number part of the fraction (the seven in three sevenths). + */ + private final int denominator; + + /** + * Cached output hashCode (class is immutable). + */ + private transient int hashCode; + /** + * Cached output toString (class is immutable). + */ + private transient String toString; + /** + * Cached output toProperString (class is immutable). + */ + private transient String toProperString; + + /** + * Constructs a {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + */ + private Fraction(final int numerator, final int denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + /** + * Creates a {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. + * + *

Any negative signs are resolved to be on the numerator.

+ * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is {@code zero} + * or the denominator is {@code negative} and the numerator is {@code Integer#MIN_VALUE} + */ + public static Fraction getFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + return new Fraction(numerator, denominator); + } + + /** + * Creates a {@link Fraction} instance with the 3 parts + * of a fraction X Y/Z. + * + *

The negative sign must be passed in on the whole number part.

+ * + * @param whole the whole number, for example the one in 'one and three sevenths' + * @param numerator the numerator, for example the three in 'one and three sevenths' + * @param denominator the denominator, for example the seven in 'one and three sevenths' + * @return a new fraction instance + * @throws ArithmeticException if the denominator is {@code zero} + * @throws ArithmeticException if the denominator is negative + * @throws ArithmeticException if the numerator is negative + * @throws ArithmeticException if the resulting numerator exceeds + * {@code Integer.MAX_VALUE} + */ + public static Fraction getFraction(final int whole, final int numerator, final int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (denominator < 0) { + throw new ArithmeticException("The denominator must not be negative"); + } + if (numerator < 0) { + throw new ArithmeticException("The numerator must not be negative"); + } + final long numeratorValue; + if (whole < 0) { + numeratorValue = whole * (long) denominator - numerator; + } else { + numeratorValue = whole * (long) denominator + numerator; + } + if (numeratorValue < Integer.MIN_VALUE || numeratorValue > Integer.MAX_VALUE) { + throw new ArithmeticException("Numerator too large to represent as an Integer."); + } + return new Fraction((int) numeratorValue, denominator); + } + + /** + * Creates a reduced {@link Fraction} instance with the 2 parts + * of a fraction Y/Z. + * + *

For example, if the input parameters represent 2/4, then the created + * fraction will be 1/2.

+ * + *

Any negative signs are resolved to be on the numerator.

+ * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws ArithmeticException if the denominator is {@code zero} + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (numerator == 0) { + return ZERO; // normalize zero. + } + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { + numerator /= 2; + denominator /= 2; + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + final int gcd = greatestCommonDivisor(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + * Creates a {@link Fraction} instance from a {@code double} value. + * + *

This method uses the + * continued fraction algorithm, computing a maximum of + * 25 convergents and bounding the denominator by 10,000.

+ * + * @param value the double value to convert + * @return a new fraction instance that is close to the value + * @throws ArithmeticException if {@code |value| > Integer.MAX_VALUE} + * or {@code value = NaN} + * @throws ArithmeticException if the calculated denominator is {@code zero} + * @throws ArithmeticException if the algorithm does not converge + */ + public static Fraction getFraction(double value) { + final int sign = value < 0 ? -1 : 1; + value = Math.abs(value); + if (value > Integer.MAX_VALUE || Double.isNaN(value)) { + throw new ArithmeticException("The value must not be greater than Integer.MAX_VALUE or NaN"); + } + final int wholeNumber = (int) value; + value -= wholeNumber; + + int numer0 = 0; // the pre-previous + int denom0 = 1; // the pre-previous + int numer1 = 1; // the previous + int denom1 = 0; // the previous + int numer2; // the current, setup in calculation + int denom2; // the current, setup in calculation + int a1 = (int) value; + int a2; + double x1 = 1; + double x2; + double y1 = value - a1; + double y2; + double delta1, delta2 = Double.MAX_VALUE; + double fraction; + int i = 1; + do { + delta1 = delta2; + a2 = (int) (x1 / y1); + x2 = y1; + y2 = x1 - a2 * y1; + numer2 = a1 * numer1 + numer0; + denom2 = a1 * denom1 + denom0; + fraction = (double) numer2 / (double) denom2; + delta2 = Math.abs(value - fraction); + a1 = a2; + x1 = x2; + y1 = y2; + numer0 = numer1; + denom0 = denom1; + numer1 = numer2; + denom1 = denom2; + i++; + } while (delta1 > delta2 && denom2 <= 10000 && denom2 > 0 && i < 25); + if (i == 25) { + throw new ArithmeticException("Unable to convert double to fraction"); + } + return getReducedFraction((numer0 + wholeNumber * denom0) * sign, denom0); + } + + /** + * Creates a Fraction from a {@link String}. + * + *

The formats accepted are:

+ * + *
    + *
  1. {@code double} String containing a dot
  2. + *
  3. 'X Y/Z'
  4. + *
  5. 'Y/Z'
  6. + *
  7. 'X' (a simple whole number)
  8. + *
+ *

and a .

+ * + * @param str the string to parse, must not be {@code null} + * @return the new {@link Fraction} instance + * @throws NullPointerException if the string is {@code null} + * @throws NumberFormatException if the number format is invalid + */ + + public static Fraction getFraction(String str) { + Objects.requireNonNull(str, "str"); + // parse double format + int pos = str.indexOf('.'); + if (pos >= 0) { + return getFraction(Double.parseDouble(str)); + } + + // parse X Y/Z format + pos = str.indexOf(' '); + if (pos > 0) { + final int whole = Integer.parseInt(str.substring(0, pos)); + str = str.substring(pos + 1); + pos = str.indexOf('/'); + if (pos < 0) { + throw new NumberFormatException("The fraction could not be parsed as the format X Y/Z"); + } + final int numer = Integer.parseInt(str.substring(0, pos)); + final int denom = Integer.parseInt(str.substring(pos + 1)); + return getFraction(whole, numer, denom); + } + + // parse Y/Z format + pos = str.indexOf('/'); + if (pos < 0) { + // simple whole number + return getFraction(Integer.parseInt(str), 1); + } + final int numer = Integer.parseInt(str.substring(0, pos)); + final int denom = Integer.parseInt(str.substring(pos + 1)); + return getFraction(numer, denom); + } + + /** + * Gets the numerator part of the fraction. + * + *

This method may return a value greater than the denominator, an + * improper fraction, such as the seven in 7/4.

+ * + * @return the numerator fraction part + */ + + public int getNumerator() { + return numerator; + } + + /** + * Gets the denominator part of the fraction. + * + * @return the denominator fraction part + */ + public int getDenominator() { + return denominator; + } + + /** + * Gets the proper numerator, always positive. + * + *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 3 from the proper fraction.

+ * + *

If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive proper numerator, 3.

+ * + * @return the numerator fraction part of a proper fraction, always positive + */ + public int getProperNumerator() { + return Math.abs(numerator % denominator); + } + + /** + * Gets the proper whole part of the fraction. + * + *

An improper fraction 7/4 can be resolved into a proper one, 1 3/4. + * This method returns the 1 from the proper fraction.

+ * + *

If the fraction is negative such as -7/4, it can be resolved into + * -1 3/4, so this method returns the positive whole part -1.

+ * + * @return the whole fraction part of a proper fraction, that includes the sign + */ + public int getProperWhole() { + return numerator / denominator; + } + + /** + * Gets the fraction as an {@code int}. This returns the whole number + * part of the fraction. + * + * @return the whole number fraction part + */ + + @Override + public int intValue() { + return numerator / denominator; + } + + /** + * Gets the fraction as a {@code long}. This returns the whole number + * part of the fraction. + * + * @return the whole number fraction part + */ + @Override + public long longValue() { + return (long) numerator / denominator; + } + + /** + * Gets the fraction as a {@code float}. This calculates the fraction + * as the numerator divided by denominator. + * + * @return the fraction as a {@code float} + */ + @Override + public float floatValue() { + return (float) numerator / (float) denominator; + } + + /** + * Gets the fraction as a {@code double}. This calculates the fraction + * as the numerator divided by denominator. + * + * @return the fraction as a {@code double} + */ + @Override + public double doubleValue() { + return (double) numerator / (double) denominator; + } + + /** + * Reduce the fraction to the smallest values for the numerator and + * denominator, returning the result. + * + *

For example, if this fraction represents 2/4, then the result + * will be 1/2.

+ * + * @return a new reduced fraction instance, or this if no simplification possible + */ + public Fraction reduce() { + if (numerator == 0) { + return equals(ZERO) ? this : ZERO; + } + final int gcd = greatestCommonDivisor(Math.abs(numerator), denominator); + if (gcd == 1) { + return this; + } + return getFraction(numerator / gcd, denominator / gcd); + } + + /** + * Gets a fraction that is the inverse (1/fraction) of this one. + * + *

The returned fraction is not reduced.

+ * + * @return a new fraction instance with the numerator and denominator + * inverted. + * @throws ArithmeticException if the fraction represents zero. + */ + public Fraction invert() { + if (numerator == 0) { + throw new ArithmeticException("Unable to invert zero."); + } + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate numerator"); + } + if (numerator<0) { + return new Fraction(-denominator, -numerator); + } + return new Fraction(denominator, numerator); + } + + /** + * Gets a fraction that is the negative (-fraction) of this one. + * + *

The returned fraction is not reduced.

+ * + * @return a new fraction instance with the opposite signed numerator + */ + public Fraction negate() { + // the positive range is one smaller than the negative range of an int. + if (numerator==Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: too large to negate"); + } + return new Fraction(-numerator, denominator); + } + + /** + * Gets a fraction that is the positive equivalent of this one. + *

More precisely: {@code (fraction >= 0 ? this : -fraction)}

+ * + *

The returned fraction is not reduced.

+ * + * @return {@code this} if it is positive, or a new positive fraction + * instance with the opposite signed numerator + */ + public Fraction abs() { + if (numerator >= 0) { + return this; + } + return negate(); + } + + /** + * Gets a fraction that is raised to the passed in power. + * + *

The returned fraction is in reduced form.

+ * + * @param power the power to raise the fraction to + * @return {@code this} if the power is one, {@link #ONE} if the power + * is zero (even if the fraction equals ZERO) or a new fraction instance + * raised to the appropriate power + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction pow(final int power) { + if (power == 1) { + return this; + } + if (power == 0) { + return ONE; + } + if (power < 0) { + if (power == Integer.MIN_VALUE) { // MIN_VALUE can't be negated. + return this.invert().pow(2).pow(-(power / 2)); + } + return this.invert().pow(-power); + } + final Fraction f = this.multiplyBy(this); + if (power % 2 == 0) { // if even... + return f.pow(power / 2); + } + return f.pow(power / 2).multiplyBy(this); + } + + /** + * Gets the greatest common divisor of the absolute value of + * two numbers, using the "binary gcd" method which avoids + * division and modulo operations. See Knuth 4.5.2 algorithm B. + * This algorithm is due to Josef Stein (1961). + * + * @param u a non-zero number + * @param v a non-zero number + * @return the greatest common divisor, never zero + */ + private static int greatestCommonDivisor(int u, int v) { + // From Commons Math: + if (u == 0 || v == 0) { + if (u == Integer.MIN_VALUE || v == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: gcd is 2^31"); + } + return Math.abs(u) + Math.abs(v); + } + // if either operand is abs 1, return 1: + if (Math.abs(u) == 1 || Math.abs(v) == 1) { + return 1; + } + // keep u and v negative, as negative integers range down to + // -2^31, while positive numbers can only be as large as 2^31-1 + // (i.e. we can't necessarily negate a negative number without + // overflow) + if (u > 0) { + u = -u; + } // make u negative + if (v > 0) { + v = -v; + } // make v negative + // B1. [Find power of 2] + int k = 0; + while ((u & 1) == 0 && (v & 1) == 0 && k < 31) { // while u and v are both even... + u /= 2; + v /= 2; + k++; // cast out twos. + } + if (k == 31) { + throw new ArithmeticException("overflow: gcd is 2^31"); + } + // B2. Initialize: u and v have been divided by 2^k and at least + // one is odd. + int t = (u & 1) == 1 ? v : -(u / 2)/* B3 */; + // t negative: u was odd, v may be even (t replaces v) + // t positive: u was even, v is odd (t replaces u) + do { + /* assert u<0 && v<0; */ + // B4/B3: cast out twos from t. + while ((t & 1) == 0) { // while t is even. + t /= 2; // cast out twos + } + // B5 [reset max(u,v)] + if (t > 0) { + u = -t; + } else { + v = t; + } + // B6/B3. at this point both u and v should be odd. + t = (v - u) / 2; + // |u| larger: t positive (replace u) + // |v| larger: t negative (replace v) + } while (t != 0); + return -u * (1 << k); // gcd is u*2^k + } + + /** + * Multiply two integers, checking for overflow. + * + * @param x a factor + * @param y a factor + * @return the product {@code x*y} + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int mulAndCheck(final int x, final int y) { + final long m = (long) x * (long) y; + if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: mul"); + } + return (int) m; + } + + /** + * Multiply two non-negative integers, checking for overflow. + * + * @param x a non-negative factor + * @param y a non-negative factor + * @return the product {@code x*y} + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int mulPosAndCheck(final int x, final int y) { + /* assert x>=0 && y>=0; */ + final long m = (long) x * (long) y; + if (m > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: mulPos"); + } + return (int) m; + } + + /** + * Add two integers, checking for overflow. + * + * @param x an addend + * @param y an addend + * @return the sum {@code x+y} + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int addAndCheck(final int x, final int y) { + final long s = (long) x + (long) y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: add"); + } + return (int) s; + } + + /** + * Subtract two integers, checking for overflow. + * + * @param x the minuend + * @param y the subtrahend + * @return the difference {@code x-y} + * @throws ArithmeticException if the result can not be represented as + * an int + */ + private static int subAndCheck(final int x, final int y) { + final long s = (long) x - (long) y; + if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) { + throw new ArithmeticException("overflow: add"); + } + return (int) s; + } + + /** + * Adds the value of this fraction to another, returning the result in reduced form. + * The algorithm follows Knuth, 4.5.1. + * + * @param fraction the fraction to add, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws IllegalArgumentException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction add(final Fraction fraction) { + return addSub(fraction, true /* add */); + } + + /** + * Subtracts the value of another fraction from the value of this one, + * returning the result in reduced form. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws IllegalArgumentException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an {@code int}. + */ + public Fraction subtract(final Fraction fraction) { + return addSub(fraction, false /* subtract */); + } + + /** + * Implement add and subtract using algorithm described in Knuth 4.5.1. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @param isAdd true to add, false to subtract + * @return a {@link Fraction} instance with the resulting values + * @throws IllegalArgumentException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an {@code int}. + */ + private Fraction addSub(final Fraction fraction, final boolean isAdd) { + Objects.requireNonNull(fraction, "fraction"); + // zero is identity for addition. + if (numerator == 0) { + return isAdd ? fraction : fraction.negate(); + } + if (fraction.numerator == 0) { + return this; + } + // if denominators are randomly distributed, d1 will be 1 about 61% + // of the time. + final int d1 = greatestCommonDivisor(denominator, fraction.denominator); + if (d1 == 1) { + // result is ( (u*v' +/- u'v) / u'v') + final int uvp = mulAndCheck(numerator, fraction.denominator); + final int upv = mulAndCheck(fraction.numerator, denominator); + return new Fraction(isAdd ? addAndCheck(uvp, upv) : subAndCheck(uvp, upv), mulPosAndCheck(denominator, + fraction.denominator)); + } + // the quantity 't' requires 65 bits of precision; see knuth 4.5.1 + // exercise 7. we're going to use a BigInteger. + // t = u(v'/d1) +/- v(u'/d1) + final BigInteger uvp = BigInteger.valueOf(numerator).multiply(BigInteger.valueOf(fraction.denominator / d1)); + final BigInteger upv = BigInteger.valueOf(fraction.numerator).multiply(BigInteger.valueOf(denominator / d1)); + final BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv); + // but d2 doesn't need extra precision because + // d2 = gcd(t,d1) = gcd(t mod d1, d1) + final int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue(); + final int d2 = tmodd1 == 0 ? d1 : greatestCommonDivisor(tmodd1, d1); + + // result is (t/d2) / (u'/d1)(v'/d2) + final BigInteger w = t.divide(BigInteger.valueOf(d2)); + if (w.bitLength() > 31) { + throw new ArithmeticException("overflow: numerator too large after multiply"); + } + return new Fraction(w.intValue(), mulPosAndCheck(denominator / d1, fraction.denominator / d2)); + } + + /** + * Multiplies the value of this fraction by another, returning the + * result in reduced form. + * + * @param fraction the fraction to multiply by, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws NullPointerException if the fraction is {@code null} + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction multiplyBy(final Fraction fraction) { + Objects.requireNonNull(fraction, "fraction"); + if (numerator == 0 || fraction.numerator == 0) { + return ZERO; + } + // knuth 4.5.1 + // make sure we don't overflow unless the result *must* overflow. + final int d1 = greatestCommonDivisor(numerator, fraction.denominator); + final int d2 = greatestCommonDivisor(fraction.numerator, denominator); + return getReducedFraction(mulAndCheck(numerator / d1, fraction.numerator / d2), + mulPosAndCheck(denominator / d2, fraction.denominator / d1)); + } + + /** + * Divide the value of this fraction by another. + * + * @param fraction the fraction to divide by, must not be {@code null} + * @return a {@link Fraction} instance with the resulting values + * @throws NullPointerException if the fraction is {@code null} + * @throws ArithmeticException if the fraction to divide by is zero + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * {@code Integer.MAX_VALUE} + */ + public Fraction divideBy(final Fraction fraction) { + Objects.requireNonNull(fraction, "fraction"); + if (fraction.numerator == 0) { + throw new ArithmeticException("The fraction to divide by must not be zero"); + } + return multiplyBy(fraction.invert()); + } + + /** + * Compares this fraction to another object to test if they are equal.. + * + *

To be equal, both values must be equal. Thus 2/4 is not equal to 1/2.

+ * + * @param obj the reference object with which to compare + * @return {@code true} if this object is equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Fraction)) { + return false; + } + final Fraction other = (Fraction) obj; + return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator(); + } + + /** + * Gets a hashCode for the fraction. + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (hashCode == 0) { + // hash code update should be atomic. + hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator(); + } + return hashCode; + } + + /** + * Compares this object to another based on size. + * + *

Note: this class has a natural ordering that is inconsistent + * with equals, because, for example, equals treats 1/2 and 2/4 as + * different, whereas compareTo treats them as equal. + * + * @param other the object to compare to + * @return -1 if this is less, 0 if equal, +1 if greater + * @throws ClassCastException if the object is not a {@link Fraction} + * @throws NullPointerException if the object is {@code null} + */ + @Override + public int compareTo(final Fraction other) { + if (this == other) { + return 0; + } + if (numerator == other.numerator && denominator == other.denominator) { + return 0; + } + + // otherwise see which is less + final long first = (long) numerator * (long) other.denominator; + final long second = (long) other.numerator * (long) denominator; + return Long.compare(first, second); + } + + /** + * Gets the fraction as a {@link String}. + * + *

The format used is 'numerator/denominator' always. + * + * @return a {@link String} form of the fraction + */ + @Override + public String toString() { + if (toString == null) { + toString = getNumerator() + "/" + getDenominator(); + } + return toString; + } + + /** + * Gets the fraction as a proper {@link String} in the format X Y/Z. + * + *

The format used in 'wholeNumber numerator/denominator'. + * If the whole number is zero it will be omitted. If the numerator is zero, + * only the whole number is returned.

+ * + * @return a {@link String} form of the fraction + */ + public String toProperString() { + if (toProperString == null) { + if (numerator == 0) { + toProperString = "0"; + } else if (numerator == denominator) { + toProperString = "1"; + } else if (numerator == -1 * denominator) { + toProperString = "-1"; + } else if ((numerator > 0 ? -numerator : numerator) < -denominator) { + // note that we do the magnitude comparison test above with + // NEGATIVE (not positive) numbers, since negative numbers + // have a larger range. otherwise numerator==Integer.MIN_VALUE + // is handled incorrectly. + final int properNumerator = getProperNumerator(); + if (properNumerator == 0) { + toProperString = Integer.toString(getProperWhole()); + } else { + toProperString = getProperWhole() + " " + properNumerator + "/" + getDenominator(); + } + } else { + toProperString = getNumerator() + "/" + getDenominator(); + } + } + return toProperString; + } +} diff --git a/src/main/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtils.java b/src/main/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtils.java new file mode 100644 index 0000000..0629c42 --- /dev/null +++ b/src/main/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtils.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04.math; + +import org.apache.commons.lang3.Validate; + +import java.util.Objects; + +/** + * Provides IEEE-754r variants of NumberUtils methods. + * + *

See: https://en.wikipedia.org/wiki/IEEE_754r

+ * + * @since 2.4 + */ +public class IEEE754rUtils { + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(double[]) to min(double...) + */ + public static double min(final double... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + * Returns the minimum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from min(float[]) to min(float...) + */ + public static float min(final float... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns min + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = min(array[i], min); + } + + return min; + } + + /** + * Gets the minimum of three {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static double min(final double a, final double b, final double c) { + return min(min(a, b), c); + } + + /** + * Gets the minimum of two {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static double min(final double a, final double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + return Math.min(a, b); + } + + /** + * Gets the minimum of three {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the smallest of the values + */ + public static float min(final float a, final float b, final float c) { + return min(min(a, b), c); + } + + /** + * Gets the minimum of two {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the smallest of the values + */ + public static float min(final float a, final float b) { + if (Float.isNaN(a)) { + return b; + } + if (Float.isNaN(b)) { + return a; + } + return Math.min(a, b); + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(double[]) to max(double...) + */ + public static double max(final double... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns max + double max = array[0]; + for (int j = 1; j < array.length; j++) { + max = max(array[j], max); + } + + return max; + } + + /** + * Returns the maximum value in an array. + * + * @param array an array, must not be null or empty + * @return the minimum value in the array + * @throws NullPointerException if {@code array} is {@code null} + * @throws IllegalArgumentException if {@code array} is empty + * @since 3.4 Changed signature from max(float[]) to max(float...) + */ + public static float max(final float... array) { + Objects.requireNonNull(array, "array"); + Validate.isTrue(array.length != 0, "Array cannot be empty."); + + // Finds and returns max + float max = array[0]; + for (int j = 1; j < array.length; j++) { + max = max(array[j], max); + } + + return max; + } + + /** + * Gets the maximum of three {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static double max(final double a, final double b, final double c) { + return max(max(a, b), c); + } + + /** + * Gets the maximum of two {@code double} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static double max(final double a, final double b) { + if (Double.isNaN(a)) { + return b; + } + if (Double.isNaN(b)) { + return a; + } + return Math.max(a, b); + } + + /** + * Gets the maximum of three {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @param c value 3 + * @return the largest of the values + */ + public static float max(final float a, final float b, final float c) { + return max(max(a, b), c); + } + + /** + * Gets the maximum of two {@code float} values. + * + *

NaN is only returned if all numbers are NaN as per IEEE-754r.

+ * + * @param a value 1 + * @param b value 2 + * @return the largest of the values + */ + public static float max(final float a, final float b) { + if (Float.isNaN(a)) { + return b; + } + if (Float.isNaN(b)) { + return a; + } + return Math.max(a, b); + } + +} diff --git a/src/main/java/ch/usi/inf/sdm/sdm04/util/FluentBitSet.java b/src/main/java/ch/usi/inf/sdm/sdm04/util/FluentBitSet.java new file mode 100644 index 0000000..1e5b6cc --- /dev/null +++ b/src/main/java/ch/usi/inf/sdm/sdm04/util/FluentBitSet.java @@ -0,0 +1,611 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04.util; + +import java.io.Serializable; +import java.util.BitSet; +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * A fluent {@link BitSet} with additional operations. + *

+ * Originally from Apache Commons VFS with more added to act as a fluent replacement for {@link BitSet}. + *

+ * + * @since 3.13.0 + */ +public final class FluentBitSet implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Working BitSet. + */ + private final BitSet bitSet; + + /** + * Creates a new bit set. All bits are initially {@code false}. + */ + public FluentBitSet() { + this(new BitSet()); + } + + /** + * Creates a new instance for the given bit set. + * + * @param set The bit set to wrap. + */ + public FluentBitSet(final BitSet set) { + this.bitSet = Objects.requireNonNull(set, "set"); + } + + /** + * Creates a bit set whose initial size is large enough to explicitly represent bits with indices in the range {@code 0} + * through {@code nbits-1}. All bits are initially {@code false}. + * + * @param nbits the initial size of the bit set. + * @throws NegativeArraySizeException if the specified initial size is negative. + */ + public FluentBitSet(final int nbits) { + this(new BitSet(nbits)); + } + + /** + * Performs a logical AND of this target bit set with the argument bit set. This bit set is modified so that each + * bit in it has the value {@code true} if and only if it both initially had the value {@code true} and the + * corresponding bit in the bit set argument also had the value {@code true}. + * + * @param set a bit set. + * @return this. + */ + public FluentBitSet and(final BitSet set) { + bitSet.and(set); + return this; + } + + /** + * Performs a logical AND of this target bit set with the argument bit set. This bit set is modified so that each + * bit in it has the value {@code true} if and only if it both initially had the value {@code true} and the + * corresponding bit in the bit set argument also had the value {@code true}. + * + * @param set a bit set. + * @return this. + */ + public FluentBitSet and(final FluentBitSet set) { + bitSet.and(set.bitSet); + return this; + } + + /** + * Clears all of the bits in this {@link BitSet} whose corresponding bit is set in the specified {@link BitSet}. + * + * @param set the {@link BitSet} with which to mask this {@link BitSet}. + * @return this. + */ + public FluentBitSet andNot(final BitSet set) { + bitSet.andNot(set); + return this; + } + + /** + * Clears all of the bits in this {@link BitSet} whose corresponding bit is set in the specified {@link BitSet}. + * + * @param set the {@link BitSet} with which to mask this {@link BitSet}. + * @return this. + */ + public FluentBitSet andNot(final FluentBitSet set) { + this.bitSet.andNot(set.bitSet); + return this; + } + + /** + * Gets the wrapped bit set. + * + * @return the wrapped bit set. + */ + public BitSet bitSet() { + return bitSet; + } + + /** + * Returns the number of bits set to {@code true} in this {@link BitSet}. + * + * @return the number of bits set to {@code true} in this {@link BitSet}. + */ + public int cardinality() { + return bitSet.cardinality(); + } + + /** + * Sets all of the bits in this BitSet to {@code false}. + * + * @return this. + */ + public FluentBitSet clear() { + bitSet.clear(); + return this; + } + + /** + * Sets the bits specified by the indexes to {@code false}. + * + * @param bitIndexArray the index of the bit to be cleared. + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public FluentBitSet clear(final int... bitIndexArray) { + for (final int e : bitIndexArray) { + this.bitSet.clear(e); + } + return this; + } + + /** + * Sets the bit specified by the index to {@code false}. + * + * @param bitIndex the index of the bit to be cleared. + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public FluentBitSet clear(final int bitIndex) { + bitSet.clear(bitIndex); + return this; + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to + * {@code false}. + * + * @param fromIndex index of the first bit to be cleared. + * @param toIndex index after the last bit to be cleared. + * @return this. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex}. + */ + public FluentBitSet clear(final int fromIndex, final int toIndex) { + bitSet.clear(fromIndex, toIndex); + return this; + } + + /** + * Cloning this {@link BitSet} produces a new {@link BitSet} that is equal to it. The clone of the bit set is another + * bit set that has exactly the same bits set to {@code true} as this bit set. + * + * @return a clone of this bit set + * @see #size() + */ + @Override + public Object clone() { + return new FluentBitSet((BitSet) bitSet.clone()); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FluentBitSet)) { + return false; + } + final FluentBitSet other = (FluentBitSet) obj; + return Objects.equals(bitSet, other.bitSet); + } + + /** + * Sets the bit at the specified index to the complement of its current value. + * + * @param bitIndex the index of the bit to flip. + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public FluentBitSet flip(final int bitIndex) { + bitSet.flip(bitIndex); + return this; + } + + /** + * Sets each bit from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to the + * complement of its current value. + * + * @param fromIndex index of the first bit to flip. + * @param toIndex index after the last bit to flip. + * @return this. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex}. + */ + public FluentBitSet flip(final int fromIndex, final int toIndex) { + bitSet.flip(fromIndex, toIndex); + return this; + } + + /** + * Returns the value of the bit with the specified index. The value is {@code true} if the bit with the index + * {@code bitIndex} is currently set in this {@link BitSet}; otherwise, the result is {@code false}. + * + * @param bitIndex the bit index. + * @return the value of the bit with the specified index. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public boolean get(final int bitIndex) { + return bitSet.get(bitIndex); + } + + /** + * Returns a new {@link BitSet} composed of bits from this {@link BitSet} from {@code fromIndex} (inclusive) to + * {@code toIndex} (exclusive). + * + * @param fromIndex index of the first bit to include. + * @param toIndex index after the last bit to include. + * @return a new {@link BitSet} from a range of this {@link BitSet}. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex}. + */ + public FluentBitSet get(final int fromIndex, final int toIndex) { + return new FluentBitSet(bitSet.get(fromIndex, toIndex)); + } + + @Override + public int hashCode() { + return bitSet.hashCode(); + } + + /** + * Returns true if the specified {@link BitSet} has any bits set to {@code true} that are also set to {@code true} in + * this {@link BitSet}. + * + * @param set {@link BitSet} to intersect with. + * @return boolean indicating whether this {@link BitSet} intersects the specified {@link BitSet}. + */ + public boolean intersects(final BitSet set) { + return bitSet.intersects(set); + } + + /** + * Returns true if the specified {@link BitSet} has any bits set to {@code true} that are also set to {@code true} in + * this {@link BitSet}. + * + * @param set {@link BitSet} to intersect with. + * @return boolean indicating whether this {@link BitSet} intersects the specified {@link BitSet}. + */ + public boolean intersects(final FluentBitSet set) { + return bitSet.intersects(set.bitSet); + } + + /** + * Returns true if this {@link BitSet} contains no bits that are set to {@code true}. + * + * @return boolean indicating whether this {@link BitSet} is empty. + */ + public boolean isEmpty() { + return bitSet.isEmpty(); + } + + /** + * Returns the "logical size" of this {@link BitSet}: the index of the highest set bit in the {@link BitSet} plus one. + * Returns zero if the {@link BitSet} contains no set bits. + * + * @return the logical size of this {@link BitSet}. + */ + public int length() { + return bitSet.length(); + } + + /** + * Returns the index of the first bit that is set to {@code false} that occurs on or after the specified starting index. + * + * @param fromIndex the index to start checking from (inclusive). + * @return the index of the next clear bit. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public int nextClearBit(final int fromIndex) { + return bitSet.nextClearBit(fromIndex); + } + + /** + * Returns the index of the first bit that is set to {@code true} that occurs on or after the specified starting index. + * If no such bit exists then {@code -1} is returned. + *

+ * To iterate over the {@code true} bits in a {@link BitSet}, use the following loop: + *

+ * + *
+     * {@code
+     * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+     *     // operate on index i here
+     *     if (i == Integer.MAX_VALUE) {
+     *         break; // or (i+1) would overflow
+     *     }
+     * }}
+     * 
+ * + * @param fromIndex the index to start checking from (inclusive). + * @return the index of the next set bit, or {@code -1} if there is no such bit. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public int nextSetBit(final int fromIndex) { + return bitSet.nextSetBit(fromIndex); + } + + /** + * Performs a logical OR of this bit set with the bit set argument. This bit set is modified so that a bit in it + * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in + * the bit set argument has the value {@code true}. + * + * @param set a bit set. + * @return this. + */ + public FluentBitSet or(final BitSet set) { + bitSet.or(set); + return this; + } + + /** + * Performs a logical OR of this bit set with the bit set arguments. This bit set is modified so that a bit in it + * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in + * the bit set argument has the value {@code true}. + * + * @param set a bit set. + * @return this. + */ + public FluentBitSet or(final FluentBitSet... set) { + for (final FluentBitSet e : set) { + this.bitSet.or(e.bitSet); + } + return this; + } + + /** + * Performs a logical OR of this bit set with the bit set argument. This bit set is modified so that a bit in it + * has the value {@code true} if and only if it either already had the value {@code true} or the corresponding bit in + * the bit set argument has the value {@code true}. + * + * @param set a bit set. + * @return this. + */ + public FluentBitSet or(final FluentBitSet set) { + this.bitSet.or(set.bitSet); + return this; + } + + /** + * Returns the index of the nearest bit that is set to {@code false} that occurs on or before the specified starting + * index. If no such bit exists, or if {@code -1} is given as the starting index, then {@code -1} is returned. + * + * @param fromIndex the index to start checking from (inclusive). + * @return the index of the previous clear bit, or {@code -1} if there is no such bit. + * @throws IndexOutOfBoundsException if the specified index is less than {@code -1}. + */ + public int previousClearBit(final int fromIndex) { + return bitSet.previousClearBit(fromIndex); + } + + /** + * Returns the index of the nearest bit that is set to {@code true} that occurs on or before the specified starting + * index. If no such bit exists, or if {@code -1} is given as the starting index, then {@code -1} is returned. + * + *

+ * To iterate over the {@code true} bits in a {@link BitSet}, use the following loop: + * + *

+     *  {@code
+     * for (int i = bs.length(); (i = bs.previousSetBit(i-1)) >= 0; ) {
+     *     // operate on index i here
+     * }}
+     * 
+ * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the previous set bit, or {@code -1} if there is no such bit + * @throws IndexOutOfBoundsException if the specified index is less than {@code -1} + */ + public int previousSetBit(final int fromIndex) { + return bitSet.previousSetBit(fromIndex); + } + + /** + * Sets the bit at the specified indexes to {@code true}. + * + * @param bitIndexArray a bit index array. + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + public FluentBitSet set(final int... bitIndexArray) { + for (final int e : bitIndexArray) { + bitSet.set(e); + } + return this; + } + + /** + * Sets the bit at the specified index to {@code true}. + * + * @param bitIndex a bit index + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative + */ + public FluentBitSet set(final int bitIndex) { + bitSet.set(bitIndex); + return this; + } + + /** + * Sets the bit at the specified index to the specified value. + * + * @param bitIndex a bit index. + * @param value a boolean value to set. + * @return this. + * @throws IndexOutOfBoundsException if the specified index is negative. + */ + + public FluentBitSet set(final int bitIndex, final boolean value) { + bitSet.set(bitIndex, value); + return this; + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to + * {@code true}. + * + * @param fromIndex index of the first bit to be set. + * @param toIndex index after the last bit to be set. + * @return this. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex}. + */ + public FluentBitSet set(final int fromIndex, final int toIndex) { + bitSet.set(fromIndex, toIndex); + return this; + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to the + * specified value. + * + * @param fromIndex index of the first bit to be set. + * @param toIndex index after the last bit to be set. + * @param value value to set the selected bits to. + * @return this. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex}. + */ + public FluentBitSet set(final int fromIndex, final int toIndex, final boolean value) { + bitSet.set(fromIndex, toIndex, value); + return this; + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the specified {@code toIndex} (exclusive) to + * {@code true}. + * + * @param fromIndex index of the first bit to be set + * @param toIndex index of the last bit to be set + * @return this. + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, or {@code toIndex} is negative, or + * {@code fromIndex} is larger than {@code toIndex} + */ + public FluentBitSet setInclusive(final int fromIndex, final int toIndex) { + bitSet.set(fromIndex, toIndex + 1); + return this; + } + + /** + * Returns the number of bits of space actually in use by this {@link BitSet} to represent bit values. The maximum + * element in the set is the size - 1st element. + * + * @return the number of bits currently in this bit set. + */ + public int size() { + return bitSet.size(); + } + + /** + * Returns a stream of indices for which this {@link BitSet} contains a bit in the set state. The indices are returned + * in order, from lowest to highest. The size of the stream is the number of bits in the set state, equal to the value + * returned by the {@link #cardinality()} method. + * + *

+ * The bit set must remain constant during the execution of the terminal stream operation. Otherwise, the result of the + * terminal stream operation is undefined. + *

+ * + * @return a stream of integers representing set indices. + * @since 1.8 + */ + public IntStream stream() { + return bitSet.stream(); + } + + /** + * Returns a new byte array containing all the bits in this bit set. + * + *

+ * More precisely, if: + *

+ *
    + *
  1. {@code byte[] bytes = s.toByteArray();}
  2. + *
  3. then {@code bytes.length == (s.length()+7)/8} and
  4. + *
  5. {@code s.get(n) == ((bytes[n/8] & (1<<(n%8))) != 0)}
  6. + *
  7. for all {@code n < 8 * bytes.length}.
  8. + *
+ * + * @return a byte array containing a little-endian representation of all the bits in this bit set + */ + public byte[] toByteArray() { + return bitSet.toByteArray(); + } + + /** + * Returns a new byte array containing all the bits in this bit set. + * + *

+ * More precisely, if: + *

+ *
    + *
  1. {@code long[] longs = s.toLongArray();}
  2. + *
  3. then {@code longs.length == (s.length()+63)/64} and
  4. + *
  5. {@code s.get(n) == ((longs[n/64] & (1L<<(n%64))) != 0)}
  6. + *
  7. for all {@code n < 64 * longs.length}.
  8. + *
+ * + * @return a byte array containing a little-endian representation of all the bits in this bit set + */ + public long[] toLongArray() { + return bitSet.toLongArray(); + } + + @Override + public String toString() { + return bitSet.toString(); + } + + /** + * Performs a logical XOR of this bit set with the bit set argument. This bit set is modified so that a bit in it + * has the value {@code true} if and only if one of the following statements holds: + *
    + *
  • The bit initially has the value {@code true}, and the corresponding bit in the argument has the value + * {@code false}. + *
  • The bit initially has the value {@code false}, and the corresponding bit in the argument has the value + * {@code true}. + *
+ * + * @param set a bit set + * @return this. + */ + public FluentBitSet xor(final BitSet set) { + bitSet.xor(set); + return this; + } + + /** + * Performs a logical XOR of this bit set with the bit set argument. This bit set is modified so that a bit in it + * has the value {@code true} if and only if one of the following statements holds: + *
    + *
  • The bit initially has the value {@code true}, and the corresponding bit in the argument has the value + * {@code false}. + *
  • The bit initially has the value {@code false}, and the corresponding bit in the argument has the value + * {@code true}. + *
+ * + * @param set a bit set + * @return this. + */ + public FluentBitSet xor(final FluentBitSet set) { + bitSet.xor(set.bitSet); + return this; + } + +} diff --git a/src/test/java/ch/usi/inf/sdm/sdm04/CharRangeTest.java b/src/test/java/ch/usi/inf/sdm/sdm04/CharRangeTest.java new file mode 100644 index 0000000..5391729 --- /dev/null +++ b/src/test/java/ch/usi/inf/sdm/sdm04/CharRangeTest.java @@ -0,0 +1,382 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 ch.usi.inf.sdm.sdm04; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.lang3.SerializationUtils; +import org.junit.jupiter.api.Test; + +/** + * Unit tests {@link CharRange}. + */ +public class CharRangeTest { + + @Test + public void testClass() { + // class changed to non-public in 3.0 + assertFalse(Modifier.isPublic(CharRange.class.getModifiers())); + assertTrue(Modifier.isFinal(CharRange.class.getModifiers())); + } + + @Test + public void testConstructorAccessors_is() { + final CharRange rangea = CharRange.is('a'); + assertEquals('a', rangea.getStart()); + assertEquals('a', rangea.getEnd()); + assertFalse(rangea.isNegated()); + assertEquals("a", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isNot() { + final CharRange rangea = CharRange.isNot('a'); + assertEquals('a', rangea.getStart()); + assertEquals('a', rangea.getEnd()); + assertTrue(rangea.isNegated()); + assertEquals("^a", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isIn_Same() { + final CharRange rangea = CharRange.isIn('a', 'a'); + assertEquals('a', rangea.getStart()); + assertEquals('a', rangea.getEnd()); + assertFalse(rangea.isNegated()); + assertEquals("a", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isIn_Normal() { + final CharRange rangea = CharRange.isIn('a', 'e'); + assertEquals('a', rangea.getStart()); + assertEquals('e', rangea.getEnd()); + assertFalse(rangea.isNegated()); + assertEquals("a-e", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isIn_Reversed() { + final CharRange rangea = CharRange.isIn('e', 'a'); + assertEquals('a', rangea.getStart()); + assertEquals('e', rangea.getEnd()); + assertFalse(rangea.isNegated()); + assertEquals("a-e", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isNotIn_Same() { + final CharRange rangea = CharRange.isNotIn('a', 'a'); + assertEquals('a', rangea.getStart()); + assertEquals('a', rangea.getEnd()); + assertTrue(rangea.isNegated()); + assertEquals("^a", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isNotIn_Normal() { + final CharRange rangea = CharRange.isNotIn('a', 'e'); + assertEquals('a', rangea.getStart()); + assertEquals('e', rangea.getEnd()); + assertTrue(rangea.isNegated()); + assertEquals("^a-e", rangea.toString()); + } + + @Test + public void testConstructorAccessors_isNotIn_Reversed() { + final CharRange rangea = CharRange.isNotIn('e', 'a'); + assertEquals('a', rangea.getStart()); + assertEquals('e', rangea.getEnd()); + assertTrue(rangea.isNegated()); + assertEquals("^a-e", rangea.toString()); + } + + @Test + public void testEquals_Object() { + final CharRange rangea = CharRange.is('a'); + final CharRange rangeae = CharRange.isIn('a', 'e'); + final CharRange rangenotbf = CharRange.isIn('b', 'f'); + + assertNotEquals(null, rangea); + + assertEquals(rangea, rangea); + assertEquals(rangea, CharRange.is('a')); + assertEquals(rangeae, rangeae); + assertEquals(rangeae, CharRange.isIn('a', 'e')); + assertEquals(rangenotbf, rangenotbf); + assertEquals(rangenotbf, CharRange.isIn('b', 'f')); + + assertNotEquals(rangea, rangeae); + assertNotEquals(rangea, rangenotbf); + assertNotEquals(rangeae, rangea); + assertNotEquals(rangeae, rangenotbf); + assertNotEquals(rangenotbf, rangea); + assertNotEquals(rangenotbf, rangeae); + } + + @Test + public void testHashCode() { + final CharRange rangea = CharRange.is('a'); + final CharRange rangeae = CharRange.isIn('a', 'e'); + final CharRange rangenotbf = CharRange.isIn('b', 'f'); + + assertEquals(rangea.hashCode(), rangea.hashCode()); + assertEquals(rangea.hashCode(), CharRange.is('a').hashCode()); + assertEquals(rangeae.hashCode(), rangeae.hashCode()); + assertEquals(rangeae.hashCode(), CharRange.isIn('a', 'e').hashCode()); + assertEquals(rangenotbf.hashCode(), rangenotbf.hashCode()); + assertEquals(rangenotbf.hashCode(), CharRange.isIn('b', 'f').hashCode()); + + assertNotEquals(rangea.hashCode(), rangeae.hashCode()); + assertNotEquals(rangea.hashCode(), rangenotbf.hashCode()); + assertNotEquals(rangeae.hashCode(), rangea.hashCode()); + assertNotEquals(rangeae.hashCode(), rangenotbf.hashCode()); + assertNotEquals(rangenotbf.hashCode(), rangea.hashCode()); + assertNotEquals(rangenotbf.hashCode(), rangeae.hashCode()); + } + + @Test + public void testContains_Char() { + CharRange range = CharRange.is('c'); + assertFalse(range.contains('b')); + assertTrue(range.contains('c')); + assertFalse(range.contains('d')); + assertFalse(range.contains('e')); + + range = CharRange.isIn('c', 'd'); + assertFalse(range.contains('b')); + assertTrue(range.contains('c')); + assertTrue(range.contains('d')); + assertFalse(range.contains('e')); + + range = CharRange.isIn('d', 'c'); + assertFalse(range.contains('b')); + assertTrue(range.contains('c')); + assertTrue(range.contains('d')); + assertFalse(range.contains('e')); + + range = CharRange.isNotIn('c', 'd'); + assertTrue(range.contains('b')); + assertFalse(range.contains('c')); + assertFalse(range.contains('d')); + assertTrue(range.contains('e')); + assertTrue(range.contains((char) 0)); + assertTrue(range.contains(Character.MAX_VALUE)); + } + + @Test + public void testContains_Charrange() { + final CharRange a = CharRange.is('a'); + final CharRange b = CharRange.is('b'); + final CharRange c = CharRange.is('c'); + final CharRange c2 = CharRange.is('c'); + final CharRange d = CharRange.is('d'); + final CharRange e = CharRange.is('e'); + final CharRange cd = CharRange.isIn('c', 'd'); + final CharRange bd = CharRange.isIn('b', 'd'); + final CharRange bc = CharRange.isIn('b', 'c'); + final CharRange ab = CharRange.isIn('a', 'b'); + final CharRange de = CharRange.isIn('d', 'e'); + final CharRange ef = CharRange.isIn('e', 'f'); + final CharRange ae = CharRange.isIn('a', 'e'); + + // normal/normal + assertFalse(c.contains(b)); + assertTrue(c.contains(c)); + assertTrue(c.contains(c2)); + assertFalse(c.contains(d)); + + assertFalse(c.contains(cd)); + assertFalse(c.contains(bd)); + assertFalse(c.contains(bc)); + assertFalse(c.contains(ab)); + assertFalse(c.contains(de)); + + assertTrue(cd.contains(c)); + assertTrue(bd.contains(c)); + assertTrue(bc.contains(c)); + assertFalse(ab.contains(c)); + assertFalse(de.contains(c)); + + assertTrue(ae.contains(b)); + assertTrue(ae.contains(ab)); + assertTrue(ae.contains(bc)); + assertTrue(ae.contains(cd)); + assertTrue(ae.contains(de)); + + final CharRange notb = CharRange.isNot('b'); + final CharRange notc = CharRange.isNot('c'); + final CharRange notd = CharRange.isNot('d'); + final CharRange notab = CharRange.isNotIn('a', 'b'); + final CharRange notbc = CharRange.isNotIn('b', 'c'); + final CharRange notbd = CharRange.isNotIn('b', 'd'); + final CharRange notcd = CharRange.isNotIn('c', 'd'); + final CharRange notde = CharRange.isNotIn('d', 'e'); + final CharRange notae = CharRange.isNotIn('a', 'e'); + final CharRange all = CharRange.isIn((char) 0, Character.MAX_VALUE); + final CharRange allbutfirst = CharRange.isIn((char) 1, Character.MAX_VALUE); + + // normal/negated + assertFalse(c.contains(notc)); + assertFalse(c.contains(notbd)); + assertTrue(all.contains(notc)); + assertTrue(all.contains(notbd)); + assertFalse(allbutfirst.contains(notc)); + assertFalse(allbutfirst.contains(notbd)); + + // negated/normal + assertTrue(notc.contains(a)); + assertTrue(notc.contains(b)); + assertFalse(notc.contains(c)); + assertTrue(notc.contains(d)); + assertTrue(notc.contains(e)); + + assertTrue(notc.contains(ab)); + assertFalse(notc.contains(bc)); + assertFalse(notc.contains(bd)); + assertFalse(notc.contains(cd)); + assertTrue(notc.contains(de)); + assertFalse(notc.contains(ae)); + assertFalse(notc.contains(all)); + assertFalse(notc.contains(allbutfirst)); + + assertTrue(notbd.contains(a)); + assertFalse(notbd.contains(b)); + assertFalse(notbd.contains(c)); + assertFalse(notbd.contains(d)); + assertTrue(notbd.contains(e)); + + assertTrue(notcd.contains(ab)); + assertFalse(notcd.contains(bc)); + assertFalse(notcd.contains(bd)); + assertFalse(notcd.contains(cd)); + assertFalse(notcd.contains(de)); + assertFalse(notcd.contains(ae)); + assertTrue(notcd.contains(ef)); + assertFalse(notcd.contains(all)); + assertFalse(notcd.contains(allbutfirst)); + + // negated/negated + assertFalse(notc.contains(notb)); + assertTrue(notc.contains(notc)); + assertFalse(notc.contains(notd)); + + assertFalse(notc.contains(notab)); + assertTrue(notc.contains(notbc)); + assertTrue(notc.contains(notbd)); + assertTrue(notc.contains(notcd)); + assertFalse(notc.contains(notde)); + + assertFalse(notbd.contains(notb)); + assertFalse(notbd.contains(notc)); + assertFalse(notbd.contains(notd)); + + assertFalse(notbd.contains(notab)); + assertFalse(notbd.contains(notbc)); + assertTrue(notbd.contains(notbd)); + assertFalse(notbd.contains(notcd)); + assertFalse(notbd.contains(notde)); + assertTrue(notbd.contains(notae)); + } + + @Test + public void testContainsNullArg() { + final CharRange range = CharRange.is('a'); + final NullPointerException e = assertThrows(NullPointerException.class, () -> range.contains(null)); + assertEquals("range", e.getMessage()); + } + + @Test + public void testIterator() { + final CharRange a = CharRange.is('a'); + final CharRange ad = CharRange.isIn('a', 'd'); + final CharRange nota = CharRange.isNot('a'); + final CharRange emptySet = CharRange.isNotIn((char) 0, Character.MAX_VALUE); + final CharRange notFirst = CharRange.isNotIn((char) 1, Character.MAX_VALUE); + final CharRange notLast = CharRange.isNotIn((char) 0, (char) (Character.MAX_VALUE - 1)); + + final Iterator aIt = a.iterator(); + assertNotNull(aIt); + assertTrue(aIt.hasNext()); + assertEquals(Character.valueOf('a'), aIt.next()); + assertFalse(aIt.hasNext()); + + final Iterator adIt = ad.iterator(); + assertNotNull(adIt); + assertTrue(adIt.hasNext()); + assertEquals(Character.valueOf('a'), adIt.next()); + assertEquals(Character.valueOf('b'), adIt.next()); + assertEquals(Character.valueOf('c'), adIt.next()); + assertEquals(Character.valueOf('d'), adIt.next()); + assertFalse(adIt.hasNext()); + + final Iterator notaIt = nota.iterator(); + assertNotNull(notaIt); + assertTrue(notaIt.hasNext()); + while (notaIt.hasNext()) { + final Character c = notaIt.next(); + assertNotEquals('a', c.charValue()); + } + + final Iterator emptySetIt = emptySet.iterator(); + assertNotNull(emptySetIt); + assertFalse(emptySetIt.hasNext()); + assertThrows(NoSuchElementException.class, emptySetIt::next); + + final Iterator notFirstIt = notFirst.iterator(); + assertNotNull(notFirstIt); + assertTrue(notFirstIt.hasNext()); + assertEquals(Character.valueOf((char) 0), notFirstIt.next()); + assertFalse(notFirstIt.hasNext()); + assertThrows(NoSuchElementException.class, notFirstIt::next); + + final Iterator notLastIt = notLast.iterator(); + assertNotNull(notLastIt); + assertTrue(notLastIt.hasNext()); + assertEquals(Character.valueOf(Character.MAX_VALUE), notLastIt.next()); + assertFalse(notLastIt.hasNext()); + assertThrows(NoSuchElementException.class, notLastIt::next); + } + + @Test + public void testSerialization() { + CharRange range = CharRange.is('a'); + assertEquals(range, SerializationUtils.clone(range)); + range = CharRange.isIn('a', 'e'); + assertEquals(range, SerializationUtils.clone(range)); + range = CharRange.isNotIn('a', 'e'); + assertEquals(range, SerializationUtils.clone(range)); + } + + @Test + public void testIteratorRemove() { + final CharRange a = CharRange.is('a'); + final Iterator aIt = a.iterator(); + assertThrows(UnsupportedOperationException.class, aIt::remove); + } +} diff --git a/src/test/java/ch/usi/inf/sdm/sdm04/math/FractionTest.java b/src/test/java/ch/usi/inf/sdm/sdm04/math/FractionTest.java new file mode 100644 index 0000000..6efc3cf --- /dev/null +++ b/src/test/java/ch/usi/inf/sdm/sdm04/math/FractionTest.java @@ -0,0 +1,1123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 ch.usi.inf.sdm.sdm04.math; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test cases for the {@link Fraction} class + */ +public class FractionTest { + + private static final int SKIP = 500; //53 + + @Test + public void testAbs() { + Fraction f; + + f = Fraction.getFraction(50, 75); + f = f.abs(); + assertEquals(50, f.getNumerator()); + assertEquals(75, f.getDenominator()); + + f = Fraction.getFraction(-50, 75); + f = f.abs(); + assertEquals(50, f.getNumerator()); + assertEquals(75, f.getDenominator()); + + f = Fraction.getFraction(Integer.MAX_VALUE, 1); + f = f.abs(); + assertEquals(Integer.MAX_VALUE, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction(Integer.MAX_VALUE, -1); + f = f.abs(); + assertEquals(Integer.MAX_VALUE, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).abs()); + } + + @Test + public void testAdd() { + Fraction f; + Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(1, 5); + f = f1.add(f2); + assertEquals(4, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(2, 5); + f = f1.add(f2); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(3, 5); + f = f1.add(f2); + assertEquals(6, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(-4, 5); + f = f1.add(f2); + assertEquals(-1, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1); + f2 = Fraction.ONE; + f = f1.add(f2); + assertEquals(Integer.MAX_VALUE, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(1, 2); + f = f1.add(f2); + assertEquals(11, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + f1 = Fraction.getFraction(3, 8); + f2 = Fraction.getFraction(1, 6); + f = f1.add(f2); + assertEquals(13, f.getNumerator()); + assertEquals(24, f.getDenominator()); + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(1, 5); + f = f1.add(f2); + assertSame(f2, f); + f = f2.add(f1); + assertSame(f2, f); + + f1 = Fraction.getFraction(-1, 13*13*2*2); + f2 = Fraction.getFraction(-2, 13*17*2); + final Fraction fr = f1.add(f2); + assertEquals(13*13*17*2*2, fr.getDenominator()); + assertEquals(-17 - 2*13*2, fr.getNumerator()); + + assertThrows(NullPointerException.class, () -> fr.add(null)); + + // if this fraction is added naively, it will overflow. + // check that it doesn't. + f1 = Fraction.getFraction(1, 32768*3); + f2 = Fraction.getFraction(1, 59049); + f = f1.add(f2); + assertEquals(52451, f.getNumerator()); + assertEquals(1934917632, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MIN_VALUE, 3); + f2 = Fraction.ONE_THIRD; + f = f1.add(f2); + assertEquals(Integer.MIN_VALUE+1, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1); + f2 = Fraction.ONE; + f = f1.add(f2); + assertEquals(Integer.MAX_VALUE, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + final Fraction overflower = f; + assertThrows(ArithmeticException.class, () -> overflower.add(Fraction.ONE)); // should overflow + + // denominator should not be a multiple of 2 or 3 to trigger overflow + assertThrows( + ArithmeticException.class, + () -> Fraction.getFraction(Integer.MIN_VALUE, 5).add(Fraction.getFraction(-1, 5))); + + final Fraction maxValue = Fraction.getFraction(-Integer.MAX_VALUE, 1); + assertThrows(ArithmeticException.class, () -> maxValue.add(maxValue)); + + final Fraction negativeMaxValue = Fraction.getFraction(-Integer.MAX_VALUE, 1); + assertThrows(ArithmeticException.class, () -> negativeMaxValue.add(negativeMaxValue)); + + final Fraction f3 = Fraction.getFraction(3, 327680); + final Fraction f4 = Fraction.getFraction(2, 59049); + assertThrows(ArithmeticException.class, () -> f3.add(f4)); // should overflow + } + + @Test + public void testCompareTo() { + final Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + assertEquals(0, f1.compareTo(f1)); + + final Fraction fr = f1; + assertThrows(NullPointerException.class, () -> fr.compareTo(null)); + + f2 = Fraction.getFraction(2, 5); + assertTrue(f1.compareTo(f2) > 0); + assertEquals(0, f2.compareTo(f2)); + + f2 = Fraction.getFraction(4, 5); + assertTrue(f1.compareTo(f2) < 0); + assertEquals(0, f2.compareTo(f2)); + + f2 = Fraction.getFraction(3, 5); + assertEquals(0, f1.compareTo(f2)); + assertEquals(0, f2.compareTo(f2)); + + f2 = Fraction.getFraction(6, 10); + assertEquals(0, f1.compareTo(f2)); + assertEquals(0, f2.compareTo(f2)); + + f2 = Fraction.getFraction(-1, 1, Integer.MAX_VALUE); + assertTrue(f1.compareTo(f2) > 0); + assertEquals(0, f2.compareTo(f2)); + + } + + @Test + public void testConstants() { + assertEquals(0, Fraction.ZERO.getNumerator()); + assertEquals(1, Fraction.ZERO.getDenominator()); + + assertEquals(1, Fraction.ONE.getNumerator()); + assertEquals(1, Fraction.ONE.getDenominator()); + + assertEquals(1, Fraction.ONE_HALF.getNumerator()); + assertEquals(2, Fraction.ONE_HALF.getDenominator()); + + assertEquals(1, Fraction.ONE_THIRD.getNumerator()); + assertEquals(3, Fraction.ONE_THIRD.getDenominator()); + + assertEquals(2, Fraction.TWO_THIRDS.getNumerator()); + assertEquals(3, Fraction.TWO_THIRDS.getDenominator()); + + assertEquals(1, Fraction.ONE_QUARTER.getNumerator()); + assertEquals(4, Fraction.ONE_QUARTER.getDenominator()); + + assertEquals(2, Fraction.TWO_QUARTERS.getNumerator()); + assertEquals(4, Fraction.TWO_QUARTERS.getDenominator()); + + assertEquals(3, Fraction.THREE_QUARTERS.getNumerator()); + assertEquals(4, Fraction.THREE_QUARTERS.getDenominator()); + + assertEquals(1, Fraction.ONE_FIFTH.getNumerator()); + assertEquals(5, Fraction.ONE_FIFTH.getDenominator()); + + assertEquals(2, Fraction.TWO_FIFTHS.getNumerator()); + assertEquals(5, Fraction.TWO_FIFTHS.getDenominator()); + + assertEquals(3, Fraction.THREE_FIFTHS.getNumerator()); + assertEquals(5, Fraction.THREE_FIFTHS.getDenominator()); + + assertEquals(4, Fraction.FOUR_FIFTHS.getNumerator()); + assertEquals(5, Fraction.FOUR_FIFTHS.getDenominator()); + } + + @Test + public void testConversions() { + final Fraction f; + + f = Fraction.getFraction(3, 7, 8); + assertEquals(3, f.intValue()); + assertEquals(3L, f.longValue()); + assertEquals(3.875f, f.floatValue(), 0.00001f); + assertEquals(3.875d, f.doubleValue(), 0.00001d); + } + + @Test + public void testDivide() { + Fraction f; + Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(2, 5); + f = f1.divideBy(f2); + assertEquals(3, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(3, 5).divideBy(Fraction.ZERO)); + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(2, 7); + f = f1.divideBy(f2); + assertSame(Fraction.ZERO, f); + + f1 = Fraction.getFraction(2, 7); + f2 = Fraction.ONE; + f = f1.divideBy(f2); + assertEquals(2, f.getNumerator()); + assertEquals(7, f.getDenominator()); + + f1 = Fraction.getFraction(1, Integer.MAX_VALUE); + f = f1.divideBy(f1); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MIN_VALUE, Integer.MAX_VALUE); + f2 = Fraction.getFraction(1, Integer.MAX_VALUE); + final Fraction fr = f1.divideBy(f2); + assertEquals(Integer.MIN_VALUE, fr.getNumerator()); + assertEquals(1, fr.getDenominator()); + + assertThrows(NullPointerException.class, () -> fr.divideBy(null)); + + final Fraction smallest = Fraction.getFraction(1, Integer.MAX_VALUE); + assertThrows(ArithmeticException.class, () -> smallest.divideBy(smallest.invert())); // Should overflow + + final Fraction negative = Fraction.getFraction(1, -Integer.MAX_VALUE); + assertThrows(ArithmeticException.class, () -> negative.divideBy(negative.invert())); // Should overflow + } + + + @Test + public void testEquals() { + Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + assertNotEquals(null, f1); + assertNotEquals(f1, new Object()); + assertNotEquals(f1, Integer.valueOf(6)); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(2, 5); + assertNotEquals(f1, f2); + assertEquals(f1, f1); + assertEquals(f2, f2); + + f2 = Fraction.getFraction(3, 5); + assertEquals(f1, f2); + + f2 = Fraction.getFraction(6, 10); + assertNotEquals(f1, f2); + } + + @Test + public void testFactory_double() { + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.NaN)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.POSITIVE_INFINITY)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Double.NEGATIVE_INFINITY)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction((double) Integer.MAX_VALUE + 1)); + + // zero + Fraction f = Fraction.getFraction(0.0d); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // one + f = Fraction.getFraction(1.0d); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // one half + f = Fraction.getFraction(0.5d); + assertEquals(1, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + // negative + f = Fraction.getFraction(-0.875d); + assertEquals(-7, f.getNumerator()); + assertEquals(8, f.getDenominator()); + + // over 1 + f = Fraction.getFraction(1.25d); + assertEquals(5, f.getNumerator()); + assertEquals(4, f.getDenominator()); + + // two thirds + f = Fraction.getFraction(0.66666d); + assertEquals(2, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + // small + f = Fraction.getFraction(1.0d/10001d); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // normal + Fraction f2 = null; + for (int i = 1; i <= 100; i++) { // denominator + for (int j = 1; j <= i; j++) { // numerator + f = Fraction.getFraction((double) j / (double) i); + + f2 = Fraction.getReducedFraction(j, i); + assertEquals(f2.getNumerator(), f.getNumerator()); + assertEquals(f2.getDenominator(), f.getDenominator()); + } + } + // save time by skipping some tests! ( + for (int i = 1001; i <= 10000; i+=SKIP) { // denominator + for (int j = 1; j <= i; j++) { // numerator + f = Fraction.getFraction((double) j / (double) i); + f2 = Fraction.getReducedFraction(j, i); + assertEquals(f2.getNumerator(), f.getNumerator()); + assertEquals(f2.getDenominator(), f.getDenominator()); + } + } + } + + @Test + public void testFactory_int_int() { + Fraction f; + + // zero + f = Fraction.getFraction(0, 1); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction(0, 2); + assertEquals(0, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + // normal + f = Fraction.getFraction(1, 1); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction(2, 1); + assertEquals(2, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction(23, 345); + assertEquals(23, f.getNumerator()); + assertEquals(345, f.getDenominator()); + + // improper + f = Fraction.getFraction(22, 7); + assertEquals(22, f.getNumerator()); + assertEquals(7, f.getDenominator()); + + // negatives + f = Fraction.getFraction(-6, 10); + assertEquals(-6, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + f = Fraction.getFraction(6, -10); + assertEquals(-6, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + f = Fraction.getFraction(-6, -10); + assertEquals(6, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + // zero denominator + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(2, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-3, 0)); + + // very large: can't represent as unsimplified fraction, although + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(4, Integer.MIN_VALUE)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, Integer.MIN_VALUE)); + } + + @Test + public void testFactory_int_int_int() { + Fraction f; + + // zero + f = Fraction.getFraction(0, 0, 2); + assertEquals(0, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction(2, 0, 2); + assertEquals(4, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction(0, 1, 2); + assertEquals(1, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + // normal + f = Fraction.getFraction(1, 1, 2); + assertEquals(3, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + // negatives + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, -6, -10)); + + // negative whole + f = Fraction.getFraction(-1, 6, 10); + assertEquals(-16, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -6, 10)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, 6, -10)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -6, -10)); + + // zero denominator + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 1, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 2, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, -3, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MAX_VALUE, 1, 2)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-Integer.MAX_VALUE, 1, 2)); + + // very large + f = Fraction.getFraction(-1, 0, Integer.MAX_VALUE); + assertEquals(-Integer.MAX_VALUE, f.getNumerator()); + assertEquals(Integer.MAX_VALUE, f.getDenominator()); + + // negative denominators not allowed in this constructor. + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 4, Integer.MIN_VALUE)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(1, 1, Integer.MAX_VALUE)); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(-1, 2, Integer.MAX_VALUE)); + } + + @Test + public void testFactory_String() { + assertThrows(NullPointerException.class, () -> Fraction.getFraction(null)); + } + + @Test + public void testFactory_String_double() { + Fraction f; + + f = Fraction.getFraction("0.0"); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction("0.2"); + assertEquals(1, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f = Fraction.getFraction("0.5"); + assertEquals(1, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction("0.66666"); + assertEquals(2, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2.3R")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2147483648")); // too big + assertThrows(NumberFormatException.class, () -> Fraction.getFraction(".")); + } + + @Test + public void testFactory_String_improper() { + Fraction f; + + f = Fraction.getFraction("0/1"); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction("1/5"); + assertEquals(1, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f = Fraction.getFraction("1/2"); + assertEquals(1, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction("2/3"); + assertEquals(2, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + f = Fraction.getFraction("7/3"); + assertEquals(7, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + f = Fraction.getFraction("2/4"); + assertEquals(2, f.getNumerator()); + assertEquals(4, f.getDenominator()); + + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2/d")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2e/3")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2/")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("/")); + } + + @Test + public void testFactory_String_proper() { + Fraction f; + + f = Fraction.getFraction("0 0/1"); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getFraction("1 1/5"); + assertEquals(6, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f = Fraction.getFraction("7 1/2"); + assertEquals(15, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction("1 2/4"); + assertEquals(6, f.getNumerator()); + assertEquals(4, f.getDenominator()); + + f = Fraction.getFraction("-7 1/2"); + assertEquals(-15, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getFraction("-1 2/4"); + assertEquals(-6, f.getNumerator()); + assertEquals(4, f.getDenominator()); + + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 3")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("a 3")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 b/4")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction("2 ")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction(" 3")); + assertThrows(NumberFormatException.class, () -> Fraction.getFraction(" ")); + } + + @Test + public void testGets() { + Fraction f; + + f = Fraction.getFraction(3, 5, 6); + assertEquals(23, f.getNumerator()); + assertEquals(3, f.getProperWhole()); + assertEquals(5, f.getProperNumerator()); + assertEquals(6, f.getDenominator()); + + f = Fraction.getFraction(-3, 5, 6); + assertEquals(-23, f.getNumerator()); + assertEquals(-3, f.getProperWhole()); + assertEquals(5, f.getProperNumerator()); + assertEquals(6, f.getDenominator()); + + f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1); + assertEquals(Integer.MIN_VALUE, f.getNumerator()); + assertEquals(Integer.MIN_VALUE, f.getProperWhole()); + assertEquals(0, f.getProperNumerator()); + assertEquals(1, f.getDenominator()); + } + + @Test + public void testHashCode() { + final Fraction f1 = Fraction.getFraction(3, 5); + Fraction f2 = Fraction.getFraction(3, 5); + + assertEquals(f1.hashCode(), f2.hashCode()); + + f2 = Fraction.getFraction(2, 5); + assertTrue(f1.hashCode() != f2.hashCode()); + + f2 = Fraction.getFraction(6, 10); + assertTrue(f1.hashCode() != f2.hashCode()); + } + + @Test + public void testInvert() { + Fraction f; + + f = Fraction.getFraction(50, 75); + f = f.invert(); + assertEquals(75, f.getNumerator()); + assertEquals(50, f.getDenominator()); + + f = Fraction.getFraction(4, 3); + f = f.invert(); + assertEquals(3, f.getNumerator()); + assertEquals(4, f.getDenominator()); + + f = Fraction.getFraction(-15, 47); + f = f.invert(); + assertEquals(-47, f.getNumerator()); + assertEquals(15, f.getDenominator()); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(0, 3).invert()); + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).invert()); + + f = Fraction.getFraction(Integer.MAX_VALUE, 1); + f = f.invert(); + assertEquals(1, f.getNumerator()); + assertEquals(Integer.MAX_VALUE, f.getDenominator()); + } + + @Test + public void testMultiply() { + Fraction f; + Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(2, 5); + f = f1.multiplyBy(f2); + assertEquals(6, f.getNumerator()); + assertEquals(25, f.getDenominator()); + + f1 = Fraction.getFraction(6, 10); + f2 = Fraction.getFraction(6, 10); + f = f1.multiplyBy(f2); + assertEquals(9, f.getNumerator()); + assertEquals(25, f.getDenominator()); + f = f.multiplyBy(f2); + assertEquals(27, f.getNumerator()); + assertEquals(125, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(-2, 5); + f = f1.multiplyBy(f2); + assertEquals(-6, f.getNumerator()); + assertEquals(25, f.getDenominator()); + + f1 = Fraction.getFraction(-3, 5); + f2 = Fraction.getFraction(-2, 5); + f = f1.multiplyBy(f2); + assertEquals(6, f.getNumerator()); + assertEquals(25, f.getDenominator()); + + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(2, 7); + f = f1.multiplyBy(f2); + assertSame(Fraction.ZERO, f); + + f1 = Fraction.getFraction(2, 7); + f2 = Fraction.ONE; + f = f1.multiplyBy(f2); + assertEquals(2, f.getNumerator()); + assertEquals(7, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MAX_VALUE, 1); + f2 = Fraction.getFraction(Integer.MIN_VALUE, Integer.MAX_VALUE); + f = f1.multiplyBy(f2); + assertEquals(Integer.MIN_VALUE, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + final Fraction fr = f; + assertThrows(NullPointerException.class, () -> fr.multiplyBy(null)); + + final Fraction fr1 = Fraction.getFraction(1, Integer.MAX_VALUE); + assertThrows(ArithmeticException.class, () -> fr1.multiplyBy(fr1)); + + final Fraction fr2 = Fraction.getFraction(1, -Integer.MAX_VALUE); + assertThrows(ArithmeticException.class, () -> fr2.multiplyBy(fr2)); + } + + @Test + public void testNegate() { + Fraction f; + + f = Fraction.getFraction(50, 75); + f = f.negate(); + assertEquals(-50, f.getNumerator()); + assertEquals(75, f.getDenominator()); + + f = Fraction.getFraction(-50, 75); + f = f.negate(); + assertEquals(50, f.getNumerator()); + assertEquals(75, f.getDenominator()); + + // large values + f = Fraction.getFraction(Integer.MAX_VALUE-1, Integer.MAX_VALUE); + f = f.negate(); + assertEquals(Integer.MIN_VALUE+2, f.getNumerator()); + assertEquals(Integer.MAX_VALUE, f.getDenominator()); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).negate()); + } + + @Test + public void testPow() { + Fraction f; + + f = Fraction.getFraction(3, 5); + assertEquals(Fraction.ONE, f.pow(0)); + + f = Fraction.getFraction(3, 5); + assertSame(f, f.pow(1)); + assertEquals(f, f.pow(1)); + + f = Fraction.getFraction(3, 5); + f = f.pow(2); + assertEquals(9, f.getNumerator()); + assertEquals(25, f.getDenominator()); + + f = Fraction.getFraction(3, 5); + f = f.pow(3); + assertEquals(27, f.getNumerator()); + assertEquals(125, f.getDenominator()); + + f = Fraction.getFraction(3, 5); + f = f.pow(-1); + assertEquals(5, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + f = Fraction.getFraction(3, 5); + f = f.pow(-2); + assertEquals(25, f.getNumerator()); + assertEquals(9, f.getDenominator()); + + // check unreduced fractions stay that way. + f = Fraction.getFraction(6, 10); + assertEquals(Fraction.ONE, f.pow(0)); + + f = Fraction.getFraction(6, 10); + assertEquals(f, f.pow(1)); + assertNotEquals(f.pow(1), Fraction.getFraction(3, 5)); + + f = Fraction.getFraction(6, 10); + f = f.pow(2); + assertEquals(9, f.getNumerator()); + assertEquals(25, f.getDenominator()); + + f = Fraction.getFraction(6, 10); + f = f.pow(3); + assertEquals(27, f.getNumerator()); + assertEquals(125, f.getDenominator()); + + f = Fraction.getFraction(6, 10); + f = f.pow(-1); + assertEquals(10, f.getNumerator()); + assertEquals(6, f.getDenominator()); + + f = Fraction.getFraction(6, 10); + f = f.pow(-2); + assertEquals(25, f.getNumerator()); + assertEquals(9, f.getDenominator()); + + // zero to any positive power is still zero. + f = Fraction.getFraction(0, 1231); + f = f.pow(1); + assertEquals(0, f.compareTo(Fraction.ZERO)); + assertEquals(0, f.getNumerator()); + assertEquals(1231, f.getDenominator()); + f = f.pow(2); + assertEquals(0, f.compareTo(Fraction.ZERO)); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // zero to negative powers should throw an exception + final Fraction fr = f; + assertThrows(ArithmeticException.class, () -> fr.pow(-1)); + assertThrows(ArithmeticException.class, () -> fr.pow(Integer.MIN_VALUE)); + + // one to any power is still one. + f = Fraction.getFraction(1, 1); + f = f.pow(0); + assertEquals(f, Fraction.ONE); + f = f.pow(1); + assertEquals(f, Fraction.ONE); + f = f.pow(-1); + assertEquals(f, Fraction.ONE); + f = f.pow(Integer.MAX_VALUE); + assertEquals(f, Fraction.ONE); + f = f.pow(Integer.MIN_VALUE); + assertEquals(f, Fraction.ONE); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MAX_VALUE, 1).pow(2)); + + // Numerator growing too negative during the pow operation. + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).pow(3)); + + assertThrows(ArithmeticException.class, () -> Fraction.getFraction(65536, 1).pow(2)); + } + + @Test + public void testReduce() { + Fraction f; + + f = Fraction.getFraction(50, 75); + Fraction result = f.reduce(); + assertEquals(2, result.getNumerator()); + assertEquals(3, result.getDenominator()); + + f = Fraction.getFraction(-2, -3); + result = f.reduce(); + assertEquals(2, result.getNumerator()); + assertEquals(3, result.getDenominator()); + + f = Fraction.getFraction(2, -3); + result = f.reduce(); + assertEquals(-2, result.getNumerator()); + assertEquals(3, result.getDenominator()); + + f = Fraction.getFraction(-2, 3); + result = f.reduce(); + assertEquals(-2, result.getNumerator()); + assertEquals(3, result.getDenominator()); + assertSame(f, result); + + f = Fraction.getFraction(2, 3); + result = f.reduce(); + assertEquals(2, result.getNumerator()); + assertEquals(3, result.getDenominator()); + assertSame(f, result); + + f = Fraction.getFraction(0, 1); + result = f.reduce(); + assertEquals(0, result.getNumerator()); + assertEquals(1, result.getDenominator()); + assertSame(f, result); + + f = Fraction.getFraction(0, 100); + result = f.reduce(); + assertEquals(0, result.getNumerator()); + assertEquals(1, result.getDenominator()); + assertSame(result, Fraction.ZERO); + + f = Fraction.getFraction(Integer.MIN_VALUE, 2); + result = f.reduce(); + assertEquals(Integer.MIN_VALUE / 2, result.getNumerator()); + assertEquals(1, result.getDenominator()); + } + + @Test + public void testReducedFactory_int_int() { + Fraction f; + + // zero + f = Fraction.getReducedFraction(0, 1); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // normal + f = Fraction.getReducedFraction(1, 1); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getReducedFraction(2, 1); + assertEquals(2, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // improper + f = Fraction.getReducedFraction(22, 7); + assertEquals(22, f.getNumerator()); + assertEquals(7, f.getDenominator()); + + // negatives + f = Fraction.getReducedFraction(-6, 10); + assertEquals(-3, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f = Fraction.getReducedFraction(6, -10); + assertEquals(-3, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f = Fraction.getReducedFraction(-6, -10); + assertEquals(3, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + // zero denominator + assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(1, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(2, 0)); + assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(-3, 0)); + + // reduced + f = Fraction.getReducedFraction(0, 2); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getReducedFraction(2, 2); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f = Fraction.getReducedFraction(2, 4); + assertEquals(1, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getReducedFraction(15, 10); + assertEquals(3, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + f = Fraction.getReducedFraction(121, 22); + assertEquals(11, f.getNumerator()); + assertEquals(2, f.getDenominator()); + + // Extreme values + // OK, can reduce before negating + f = Fraction.getReducedFraction(-2, Integer.MIN_VALUE); + assertEquals(1, f.getNumerator()); + assertEquals(-(Integer.MIN_VALUE / 2), f.getDenominator()); + + // Can't reduce, negation will throw + assertThrows(ArithmeticException.class, () -> Fraction.getReducedFraction(-7, Integer.MIN_VALUE)); + + // LANG-662 + f = Fraction.getReducedFraction(Integer.MIN_VALUE, 2); + assertEquals(Integer.MIN_VALUE / 2, f.getNumerator()); + assertEquals(1, f.getDenominator()); + } + + @Test + public void testSubtract() { + Fraction f; + Fraction f1; + Fraction f2; + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(1, 5); + f = f1.subtract(f2); + assertEquals(2, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(7, 5); + f2 = Fraction.getFraction(2, 5); + f = f1.subtract(f2); + assertEquals(1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(3, 5); + f = f1.subtract(f2); + assertEquals(0, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(-4, 5); + f = f1.subtract(f2); + assertEquals(7, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(4, 5); + f = f1.subtract(f2); + assertEquals(-4, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(-4, 5); + f = f1.subtract(f2); + assertEquals(4, f.getNumerator()); + assertEquals(5, f.getDenominator()); + + f1 = Fraction.getFraction(3, 5); + f2 = Fraction.getFraction(1, 2); + f = f1.subtract(f2); + assertEquals(1, f.getNumerator()); + assertEquals(10, f.getDenominator()); + + f1 = Fraction.getFraction(0, 5); + f2 = Fraction.getFraction(1, 5); + f = f2.subtract(f1); + assertSame(f2, f); + + final Fraction fr = f; + assertThrows(NullPointerException.class, () -> fr.subtract(null)); + + // if this fraction is subtracted naively, it will overflow. + // check that it doesn't. + f1 = Fraction.getFraction(1, 32768*3); + f2 = Fraction.getFraction(1, 59049); + f = f1.subtract(f2); + assertEquals(-13085, f.getNumerator()); + assertEquals(1934917632, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MIN_VALUE, 3); + f2 = Fraction.ONE_THIRD.negate(); + f = f1.subtract(f2); + assertEquals(Integer.MIN_VALUE+1, f.getNumerator()); + assertEquals(3, f.getDenominator()); + + f1 = Fraction.getFraction(Integer.MAX_VALUE, 1); + f2 = Fraction.ONE; + f = f1.subtract(f2); + assertEquals(Integer.MAX_VALUE-1, f.getNumerator()); + assertEquals(1, f.getDenominator()); + + // Should overflow + assertThrows( + ArithmeticException.class, + () -> Fraction.getFraction(1, Integer.MAX_VALUE).subtract(Fraction.getFraction(1, Integer.MAX_VALUE - 1))); + f = f1.subtract(f2); + + // denominator should not be a multiple of 2 or 3 to trigger overflow + assertThrows( + ArithmeticException.class, + () -> Fraction.getFraction(Integer.MIN_VALUE, 5).subtract(Fraction.getFraction(1, 5))); + + assertThrows( + ArithmeticException.class, () -> Fraction.getFraction(Integer.MIN_VALUE, 1).subtract(Fraction.ONE)); + + assertThrows( + ArithmeticException.class, + () -> Fraction.getFraction(Integer.MAX_VALUE, 1).subtract(Fraction.ONE.negate())); + + // Should overflow + assertThrows( + ArithmeticException.class, + () -> Fraction.getFraction(3, 327680).subtract(Fraction.getFraction(2, 59049))); + } + + @Test + public void testToProperString() { + Fraction f; + + f = Fraction.getFraction(3, 5); + final String str = f.toProperString(); + assertEquals("3/5", str); + assertSame(str, f.toProperString()); + + f = Fraction.getFraction(7, 5); + assertEquals("1 2/5", f.toProperString()); + + f = Fraction.getFraction(14, 10); + assertEquals("1 4/10", f.toProperString()); + + f = Fraction.getFraction(4, 2); + assertEquals("2", f.toProperString()); + + f = Fraction.getFraction(0, 2); + assertEquals("0", f.toProperString()); + + f = Fraction.getFraction(2, 2); + assertEquals("1", f.toProperString()); + + f = Fraction.getFraction(-7, 5); + assertEquals("-1 2/5", f.toProperString()); + + f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1); + assertEquals("-2147483648", f.toProperString()); + + f = Fraction.getFraction(-1, 1, Integer.MAX_VALUE); + assertEquals("-1 1/2147483647", f.toProperString()); + + assertEquals("-1", Fraction.getFraction(-1).toProperString()); + } + + @Test + public void testToString() { + Fraction f; + + f = Fraction.getFraction(3, 5); + final String str = f.toString(); + assertEquals("3/5", str); + assertSame(str, f.toString()); + + f = Fraction.getFraction(7, 5); + assertEquals("7/5", f.toString()); + + f = Fraction.getFraction(4, 2); + assertEquals("4/2", f.toString()); + + f = Fraction.getFraction(0, 2); + assertEquals("0/2", f.toString()); + + f = Fraction.getFraction(2, 2); + assertEquals("2/2", f.toString()); + + f = Fraction.getFraction(Integer.MIN_VALUE, 0, 1); + assertEquals("-2147483648/1", f.toString()); + + f = Fraction.getFraction(-1, 1, Integer.MAX_VALUE); + assertEquals("-2147483648/2147483647", f.toString()); + } +} diff --git a/src/test/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtilsTest.java b/src/test/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtilsTest.java new file mode 100644 index 0000000..aa392eb --- /dev/null +++ b/src/test/java/ch/usi/inf/sdm/sdm04/math/IEEE754rUtilsTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04.math; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests {@link org.apache.commons.lang3.math.IEEE754rUtils}. + */ +public class IEEE754rUtilsTest { + + @Test + public void testConstructorExists() { + new IEEE754rUtils(); + } + + @Test + public void testEnforceExceptions() { + assertThrows( + NullPointerException.class, + () -> IEEE754rUtils.min( (float[]) null), + "IllegalArgumentException expected for null input"); + + assertThrows( + IllegalArgumentException.class, + IEEE754rUtils::min, + "IllegalArgumentException expected for empty input"); + + assertThrows( + NullPointerException.class, + () -> IEEE754rUtils.max( (float[]) null), + "IllegalArgumentException expected for null input"); + + assertThrows( + IllegalArgumentException.class, + IEEE754rUtils::max, + "IllegalArgumentException expected for empty input"); + + assertThrows( + NullPointerException.class, + () -> IEEE754rUtils.min( (double[]) null), + "IllegalArgumentException expected for null input"); + + assertThrows( + IllegalArgumentException.class, + IEEE754rUtils::min, + "IllegalArgumentException expected for empty input"); + + assertThrows( + NullPointerException.class, + () -> IEEE754rUtils.max( (double[]) null), + "IllegalArgumentException expected for null input"); + + assertThrows( + IllegalArgumentException.class, + IEEE754rUtils::max, + "IllegalArgumentException expected for empty input"); + } + + @Test + public void testLang381() { + assertEquals(1.2, IEEE754rUtils.min(1.2, 2.5, Double.NaN), 0.01); + assertEquals(2.5, IEEE754rUtils.max(1.2, 2.5, Double.NaN), 0.01); + assertTrue(Double.isNaN(IEEE754rUtils.max(Double.NaN, Double.NaN, Double.NaN))); + assertEquals(1.2f, IEEE754rUtils.min(1.2f, 2.5f, Float.NaN), 0.01); + assertEquals(2.5f, IEEE754rUtils.max(1.2f, 2.5f, Float.NaN), 0.01); + assertTrue(Float.isNaN(IEEE754rUtils.max(Float.NaN, Float.NaN, Float.NaN))); + + final double[] a = { 1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN }; + assertEquals(42.0, IEEE754rUtils.max(a), 0.01); + assertEquals(1.2, IEEE754rUtils.min(a), 0.01); + + final double[] b = { Double.NaN, 1.2, Double.NaN, 3.7, 27.0, 42.0, Double.NaN }; + assertEquals(42.0, IEEE754rUtils.max(b), 0.01); + assertEquals(1.2, IEEE754rUtils.min(b), 0.01); + + final float[] aF = { 1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN }; + assertEquals(1.2f, IEEE754rUtils.min(aF), 0.01); + assertEquals(42.0f, IEEE754rUtils.max(aF), 0.01); + + final float[] bF = { Float.NaN, 1.2f, Float.NaN, 3.7f, 27.0f, 42.0f, Float.NaN }; + assertEquals(1.2f, IEEE754rUtils.min(bF), 0.01); + assertEquals(42.0f, IEEE754rUtils.max(bF), 0.01); + } + +} diff --git a/src/test/java/ch/usi/inf/sdm/sdm04/util/FluentBitSetTest.java b/src/test/java/ch/usi/inf/sdm/sdm04/util/FluentBitSetTest.java new file mode 100644 index 0000000..f05be62 --- /dev/null +++ b/src/test/java/ch/usi/inf/sdm/sdm04/util/FluentBitSetTest.java @@ -0,0 +1,1827 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 ch.usi.inf.sdm.sdm04.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.BitSet; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link FluentBitSet}. + *

+ * Test code originally from Apache Harmony for FluentBitSet and adapted. + *

+ */ +public class FluentBitSetTest { + + private BitSet eightBs; + private FluentBitSet eightFbs; + + /** + * BeforeEach. + */ + @BeforeEach + public void beforeEach() { + + eightFbs = newInstance(); + + for (int i = 0; i < 8; i++) { + eightFbs.set(i); + } + eightBs = eightFbs.bitSet(); + } + + private FluentBitSet newInstance() { + return new FluentBitSet(); + } + + private FluentBitSet newInstance(final int nbits) { + return new FluentBitSet(nbits); + } + + /** + * Tests {@link FluentBitSet#and(FluentBitSet)}. + */ + @Test + public void test_and() { + // Test for method void java.util.BitSet.and(BitSet) + final FluentBitSet bs = newInstance(128); + // Initialize the bottom half of the BitSet + + for (int i = 64; i < 128; i++) { + bs.set(i); + } + eightFbs.and(bs); + assertFalse(eightFbs.equals(bs), "AND failed to clear bits"); + eightFbs.set(3); + bs.set(3); + eightFbs.and(bs); + assertTrue(bs.get(3), "AND failed to maintain set bits"); + bs.and(eightFbs); + for (int i = 64; i < 128; i++) { + assertFalse(bs.get(i), "Failed to clear extra bits in the receiver BitSet"); + } + } + + /** + * Tests {@link FluentBitSet#and(BitSet)}. + */ + @Test + public void test_and_BitSet() { + // Test for method void java.util.BitSet.and(BitSet) + final FluentBitSet bs = newInstance(128); + // Initialize the bottom half of the BitSet + + for (int i = 64; i < 128; i++) { + bs.set(i); + } + eightFbs.and(bs.bitSet()); + assertFalse(eightFbs.equals(bs), "AND failed to clear bits"); + eightFbs.set(3); + bs.set(3); + eightFbs.and(bs.bitSet()); + assertTrue(bs.get(3), "AND failed to maintain set bits"); + bs.and(eightBs); + for (int i = 64; i < 128; i++) { + assertFalse(bs.get(i), "Failed to clear extra bits in the receiver BitSet"); + } + } + + /** + * Tests {@link FluentBitSet#andNot(BitSet)}. + */ + @Test + public void test_andNot() { + FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + bs.clear(5); + final FluentBitSet bs2 = newInstance(); + bs2.set(2); + bs2.set(3); + bs.andNot(bs2); + assertEquals("{0, 1, 4, 6, 7}", bs.toString(), "Incorrect bitset after andNot"); + + bs = newInstance(0); + bs.andNot(bs2); + assertEquals(0, bs.size(), "Incorrect size"); + } + + /** + * Tests {@link FluentBitSet#andNot(BitSet)}. + */ + @Test + public void test_andNot_BitSet() { + FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + bs.clear(5); + final FluentBitSet bs2 = newInstance(); + bs2.set(2); + bs2.set(3); + bs.andNot(bs2.bitSet()); + assertEquals("{0, 1, 4, 6, 7}", bs.toString(), "Incorrect bitset after andNot"); + + bs = newInstance(0); + bs.andNot(bs2.bitSet()); + assertEquals(0, bs.size(), "Incorrect size"); + } + + /** + * Tests {@link FluentBitSet#cardinality()}. + */ + @Test + public void test_cardinality() { + // test for method int java.util.BitSet.cardinality() + final FluentBitSet bs = newInstance(500); + bs.set(5); + bs.set(32); + bs.set(63); + bs.set(64); + bs.set(71, 110); + bs.set(127, 130); + bs.set(193); + bs.set(450); + assertEquals(48, bs.cardinality(), "cardinality() returned wrong value"); + + bs.flip(0, 500); + assertEquals(452, bs.cardinality(), "cardinality() returned wrong value"); + + bs.clear(); + assertEquals(0, bs.cardinality(), "cardinality() returned wrong value"); + + bs.set(0, 500); + assertEquals(500, bs.cardinality(), "cardinality() returned wrong value"); + } + + /** + * Tests {@link FluentBitSet#clear()}. + */ + @Test + public void test_clear() { + eightFbs.clear(); + for (int i = 0; i < 8; i++) { + assertFalse(eightFbs.get(i), "Clear didn't clear bit " + i); + } + assertEquals(0, eightFbs.length(), "Test1: Wrong length"); + + final FluentBitSet bs = newInstance(3400); + bs.set(0, bs.size() - 1); // ensure all bits are 1's + bs.set(bs.size() - 1); + bs.clear(); + assertEquals(0, bs.length(), "Test2: Wrong length"); + assertTrue(bs.isEmpty(), "Test2: isEmpty() returned incorrect value"); + assertEquals(0, bs.cardinality(), "Test2: cardinality() returned incorrect value"); + } + + /** + * Tests {@link FluentBitSet#clear(int)}. + */ + @Test + public void test_clearI() { + // Test for method void java.util.BitSet.clear(int) + + eightFbs.clear(7); + assertFalse(eightFbs.get(7), "Failed to clear bit"); + + // Check to see all other bits are still set + for (int i = 0; i < 7; i++) { + assertTrue(eightFbs.get(i), "Clear cleared incorrect bits"); + } + + eightFbs.clear(165); + assertFalse(eightFbs.get(165), "Failed to clear bit"); + // Try out of range + assertThrows(IndexOutOfBoundsException.class, () -> eightFbs.clear(-1)); + + final FluentBitSet bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length,"); + assertEquals(0, bs.size(), "Test1: Wrong size,"); + + bs.clear(0); + assertEquals(0, bs.length(), "Test2: Wrong length,"); + assertEquals(0, bs.size(), "Test2: Wrong size,"); + + bs.clear(60); + assertEquals(0, bs.length(), "Test3: Wrong length,"); + assertEquals(0, bs.size(), "Test3: Wrong size,"); + + bs.clear(120); + assertEquals(0, bs.size(), "Test4: Wrong size,"); + assertEquals(0, bs.length(), "Test4: Wrong length,"); + + bs.set(25); + assertEquals(64, bs.size(), "Test5: Wrong size,"); + assertEquals(26, bs.length(), "Test5: Wrong length,"); + + bs.clear(80); + assertEquals(64, bs.size(), "Test6: Wrong size,"); + assertEquals(26, bs.length(), "Test6: Wrong length,"); + + bs.clear(25); + assertEquals(64, bs.size(), "Test7: Wrong size,"); + assertEquals(0, bs.length(), "Test7: Wrong length,"); + } + + /** + * Tests {@link FluentBitSet#clear(int, int)}. + */ + @Test + public void test_clearII() { + // Regression for HARMONY-98 + final FluentBitSet bitset = newInstance(); + for (int i = 0; i < 20; i++) { + bitset.set(i); + } + bitset.clear(10, 10); + + // Test for method void java.util.BitSet.clear(int, int) + // pos1 and pos2 are in the same bitset element + FluentBitSet bs = newInstance(16); + int initialSize = bs.size(); + bs.set(0, initialSize); + bs.clear(5); + bs.clear(15); + bs.clear(7, 11); + for (int i = 0; i < 7; i++) { + if (i == 5) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + } + for (int i = 7; i < 11; i++) { + assertFalse(bs.get(i), "Failed to clear bit " + i); + } + + for (int i = 11; i < initialSize; i++) { + if (i == 15) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + } + + for (int i = initialSize; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + // pos1 and pos2 is in the same bitset element, boundry testing + bs = newInstance(16); + initialSize = bs.size(); + bs.set(0, initialSize); + bs.clear(7, 64); + assertEquals(64, bs.size(), "Failed to grow BitSet"); + for (int i = 0; i < 7; i++) { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + for (int i = 7; i < 64; i++) { + assertFalse(bs.get(i), "Failed to clear bit " + i); + } + for (int i = 64; i < bs.size(); i++) { + assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i); + } + // more boundary testing + bs = newInstance(32); + initialSize = bs.size(); + bs.set(0, initialSize); + bs.clear(0, 64); + for (int i = 0; i < 64; i++) { + assertFalse(bs.get(i), "Failed to clear bit " + i); + } + for (int i = 64; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + bs = newInstance(32); + initialSize = bs.size(); + bs.set(0, initialSize); + bs.clear(0, 65); + for (int i = 0; i < 65; i++) { + assertFalse(bs.get(i), "Failed to clear bit " + i); + } + for (int i = 65; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + // pos1 and pos2 are in two sequential bitset elements + bs = newInstance(128); + initialSize = bs.size(); + bs.set(0, initialSize); + bs.clear(7); + bs.clear(110); + bs.clear(9, 74); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + } + for (int i = 9; i < 74; i++) { + assertFalse(bs.get(i), "Failed to clear bit " + i); + } + for (int i = 74; i < initialSize; i++) { + if (i == 110) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + } + for (int i = initialSize; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + // pos1 and pos2 are in two non-sequential bitset elements + bs = newInstance(256); + bs.set(0, 256); + bs.clear(7); + bs.clear(255); + bs.clear(9, 219); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + } + + for (int i = 9; i < 219; i++) { + assertFalse(bs.get(i), "failed to clear bit " + i); + } + + for (int i = 219; i < 255; i++) { + assertTrue(bs.get(i), "Shouldn't have cleared bit " + i); + } + + for (int i = 255; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + // test illegal args + bs = newInstance(10); + assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(-1, 3), + "Test1: Attempt to flip with negative index failed to generate exception"); + + assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(2, -1), + "Test2: Attempt to flip with negative index failed to generate exception"); + + bs.set(2, 4); + bs.clear(2, 2); + assertTrue(bs.get(2), "Bit got cleared incorrectly "); + + assertThrows(IndexOutOfBoundsException.class, () -> newInstance(10).clear(4, 2), + "Test4: Attempt to flip with illegal args failed to generate exception"); + + bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length,"); + assertEquals(0, bs.size(), "Test1: Wrong size,"); + + bs.clear(0, 2); + assertEquals(0, bs.length(), "Test2: Wrong length,"); + assertEquals(0, bs.size(), "Test2: Wrong size,"); + + bs.clear(60, 64); + assertEquals(0, bs.length(), "Test3: Wrong length,"); + assertEquals(0, bs.size(), "Test3: Wrong size,"); + + bs.clear(64, 120); + assertEquals(0, bs.length(), "Test4: Wrong length,"); + assertEquals(0, bs.size(), "Test4: Wrong size,"); + + bs.set(25); + assertEquals(26, bs.length(), "Test5: Wrong length,"); + assertEquals(64, bs.size(), "Test5: Wrong size,"); + + bs.clear(60, 64); + assertEquals(26, bs.length(), "Test6: Wrong length,"); + assertEquals(64, bs.size(), "Test6: Wrong size,"); + + bs.clear(64, 120); + assertEquals(64, bs.size(), "Test7: Wrong size,"); + assertEquals(26, bs.length(), "Test7: Wrong length,"); + + bs.clear(80); + assertEquals(64, bs.size(), "Test8: Wrong size,"); + assertEquals(26, bs.length(), "Test8: Wrong length,"); + + bs.clear(25); + assertEquals(64, bs.size(), "Test9: Wrong size,"); + assertEquals(0, bs.length(), "Test9: Wrong length,"); + } + + /** + * Tests {@link FluentBitSet#clear(int...)}. + */ + @Test + public void test_clearIntArray() { + // Test for method void java.util.BitSet.clear(int) + + eightFbs.clear(new int[] {7}); + assertFalse(eightFbs.get(7), "Failed to clear bit"); + + // Check to see all other bits are still set + for (int i = 0; i < 7; i++) { + assertTrue(eightFbs.get(i), "Clear cleared incorrect bits"); + } + + eightFbs.clear(165); + assertFalse(eightFbs.get(165), "Failed to clear bit"); + // Try out of range + assertThrows(IndexOutOfBoundsException.class, () -> eightFbs.clear(-1)); + + final FluentBitSet bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length,"); + assertEquals(0, bs.size(), "Test1: Wrong size,"); + + bs.clear(new int[] {0}); + assertEquals(0, bs.length(), "Test2: Wrong length,"); + assertEquals(0, bs.size(), "Test2: Wrong size,"); + + bs.clear(new int[] {60}); + assertEquals(0, bs.length(), "Test3: Wrong length,"); + assertEquals(0, bs.size(), "Test3: Wrong size,"); + + bs.clear(new int[] {120}); + assertEquals(0, bs.size(), "Test4: Wrong size,"); + assertEquals(0, bs.length(), "Test4: Wrong length,"); + + bs.set(25); + assertEquals(64, bs.size(), "Test5: Wrong size,"); + assertEquals(26, bs.length(), "Test5: Wrong length,"); + + bs.clear(new int[] {80}); + assertEquals(64, bs.size(), "Test6: Wrong size,"); + assertEquals(26, bs.length(), "Test6: Wrong length,"); + + bs.clear(new int[] {25}); + assertEquals(64, bs.size(), "Test7: Wrong size,"); + assertEquals(0, bs.length(), "Test7: Wrong length,"); + } + + /** + * Tests FluentBitSet#clone() + */ + @Test + public void test_clone() { + final FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + assertEquals(bs, eightFbs, "clone failed to return equal BitSet"); + } + + /** + * Tests {@link FluentBitSet#FluentBitSet()}. + */ + @Test + public void test_Constructor() { + final FluentBitSet bs = newInstance(); + assertEquals(64, bs.size(), "Create FluentBitSet of incorrect size"); + assertEquals("{}", bs.toString(), "New FluentBitSet had invalid string representation"); + } + + /** + * Tests {@link FluentBitSet#FluentBitSet(int)}. + */ + @Test + public void test_ConstructorInt() { + FluentBitSet bs = newInstance(128); + assertEquals(128, bs.size(), "Create FluentBitSet of incorrect size"); + assertEquals("{}", bs.toString(), "New FluentBitSet had invalid string representation: " + bs.toString()); + // All BitSets are created with elements of multiples of 64 + bs = newInstance(89); + assertEquals(128, bs.size(), "Failed to round FluentBitSet element size"); + + assertThrows(NegativeArraySizeException.class, () -> newInstance(-9)); + } + + /** + * Tests {@link FluentBitSet#equals(java.lang.Object)}. + */ + @Test + public void test_equals() { + FluentBitSet bs; + bs = (FluentBitSet) eightFbs.clone(); + assertEquals(eightFbs, eightFbs, "Same FluentBitSet returned false"); + assertEquals(bs, eightFbs, "Identical FluentBitSet returned false"); + bs.clear(6); + assertFalse(eightFbs.equals(bs), "Different BitSets returned true"); + assertFalse(eightFbs.equals(null), "Different BitSets returned true"); + assertFalse(eightFbs.equals(new Object()), "Different BitSets returned true"); + + bs = (FluentBitSet) eightFbs.clone(); + bs.set(128); + assertFalse(eightFbs.equals(bs), "Different sized FluentBitSet with higher bit set returned true"); + bs.clear(128); + assertTrue(eightFbs.equals(bs), "Different sized FluentBitSet with higher bits not set returned false"); + } + + /** + * Tests {@link FluentBitSet#flip(int)}. + */ + @Test + public void test_flipI() { + // Test for method void java.util.BitSet.flip(int) + FluentBitSet bs = newInstance(); + bs.clear(8); + bs.clear(9); + bs.set(10); + bs.flip(9); + assertFalse(bs.get(8), "Failed to flip bit"); + assertTrue(bs.get(9), "Failed to flip bit"); + assertTrue(bs.get(10), "Failed to flip bit"); + + bs.set(8); + bs.set(9); + bs.clear(10); + bs.flip(9); + assertTrue(bs.get(8), "Failed to flip bit"); + assertFalse(bs.get(9), "Failed to flip bit"); + assertFalse(bs.get(10), "Failed to flip bit"); + + assertThrows(IndexOutOfBoundsException.class, () -> newInstance().flip(-1), "Attempt to flip at negative index failed to generate exception"); + + // Try setting a bit on a 64 boundary + bs.flip(128); + assertEquals(192, bs.size(), "Failed to grow BitSet"); + assertTrue(bs.get(128), "Failed to flip bit"); + + bs = newInstance(64); + for (int i = bs.size(); --i >= 0;) { + bs.flip(i); + assertTrue(bs.get(i), "Test1: Incorrectly flipped bit" + i); + assertEquals(i + 1, bs.length(), "Incorrect length"); + for (int j = bs.size(); --j > i;) { + assertTrue(!bs.get(j), "Test2: Incorrectly flipped bit" + j); + } + for (int j = i; --j >= 0;) { + assertTrue(!bs.get(j), "Test3: Incorrectly flipped bit" + j); + } + bs.flip(i); + } + + final FluentBitSet bs0 = newInstance(0); + assertEquals(0, bs0.size(), "Test1: Wrong size"); + assertEquals(0, bs0.length(), "Test1: Wrong length"); + + bs0.flip(0); + assertEquals(bs0.size(), 64, "Test2: Wrong size"); + assertEquals(1, bs0.length(), "Test2: Wrong length"); + + bs0.flip(63); + assertEquals(64, bs0.size(), "Test3: Wrong size"); + assertEquals(64, bs0.length(), "Test3: Wrong length"); + + eightFbs.flip(7); + assertTrue(!eightFbs.get(7), "Failed to flip bit 7"); + + // Check to see all other bits are still set + for (int i = 0; i < 7; i++) { + assertTrue(eightFbs.get(i), "Flip flipped incorrect bits"); + } + + eightFbs.flip(127); + assertTrue(eightFbs.get(127), "Failed to flip bit 127"); + + eightFbs.flip(127); + assertTrue(!eightFbs.get(127), "Failed to flip bit 127"); + } + + /** + * Tests {@link FluentBitSet#clear(int, int)}. + */ + @Test + public void test_flipII() { + final FluentBitSet bitset = newInstance(); + for (int i = 0; i < 20; i++) { + bitset.set(i); + } + bitset.flip(10, 10); + + // Test for method void java.util.BitSet.flip(int, int) + // pos1 and pos2 are in the same bitset element + FluentBitSet bs = newInstance(16); + bs.set(7); + bs.set(10); + bs.flip(7, 11); + for (int i = 0; i < 7; i++) { + assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i); + } + assertFalse(bs.get(7), "Failed to flip bit 7"); + assertTrue(bs.get(8), "Failed to flip bit 8"); + assertTrue(bs.get(9), "Failed to flip bit 9"); + assertFalse(bs.get(10), "Failed to flip bit 10"); + for (int i = 11; i < bs.size(); i++) { + assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i); + } + + // pos1 and pos2 is in the same bitset element, boundry testing + bs = newInstance(16); + bs.set(7); + bs.set(10); + bs.flip(7, 64); + assertEquals(64, bs.size(), "Failed to grow BitSet"); + for (int i = 0; i < 7; i++) { + assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i); + } + assertFalse(bs.get(7), "Failed to flip bit 7"); + assertTrue(bs.get(8), "Failed to flip bit 8"); + assertTrue(bs.get(9), "Failed to flip bit 9"); + assertFalse(bs.get(10), "Failed to flip bit 10"); + for (int i = 11; i < 64; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(64), "Shouldn't have flipped bit 64"); + + // more boundary testing + bs = newInstance(32); + bs.flip(0, 64); + for (int i = 0; i < 64; i++) { + assertTrue(bs.get(i), "Failed to flip bit " + i); + } + assertFalse(bs.get(64), "Shouldn't have flipped bit 64"); + + bs = newInstance(32); + bs.flip(0, 65); + for (int i = 0; i < 65; i++) { + assertTrue(bs.get(i), "Failed to flip bit " + i); + } + assertFalse(bs.get(65), "Shouldn't have flipped bit 65"); + + // pos1 and pos2 are in two sequential bitset elements + bs = newInstance(128); + bs.set(7); + bs.set(10); + bs.set(72); + bs.set(110); + bs.flip(9, 74); + for (int i = 0; i < 7; i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + assertTrue(bs.get(7), "Shouldn't have flipped bit 7"); + assertFalse(bs.get(8), "Shouldn't have flipped bit 8"); + assertTrue(bs.get(9), "Failed to flip bit 9"); + assertFalse(bs.get(10), "Failed to flip bit 10"); + for (int i = 11; i < 72; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(72), "Failed to flip bit 72"); + assertTrue(bs.get(73), "Failed to flip bit 73"); + for (int i = 74; i < 110; i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + assertTrue(bs.get(110), "Shouldn't have flipped bit 110"); + for (int i = 111; i < bs.size(); i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + + // pos1 and pos2 are in two non-sequential bitset elements + bs = newInstance(256); + bs.set(7); + bs.set(10); + bs.set(72); + bs.set(110); + bs.set(181); + bs.set(220); + bs.flip(9, 219); + for (int i = 0; i < 7; i++) { + assertFalse(bs.get(i), "Shouldn't have flipped bit " + i); + } + assertTrue(bs.get(7), "Shouldn't have flipped bit 7"); + assertFalse(bs.get(8), "Shouldn't have flipped bit 8"); + assertTrue(bs.get(9), "Failed to flip bit 9"); + assertFalse(bs.get(10), "Failed to flip bit 10"); + for (int i = 11; i < 72; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(72), "Failed to flip bit 72"); + for (int i = 73; i < 110; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(110), "Failed to flip bit 110"); + for (int i = 111; i < 181; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(181), "Failed to flip bit 181"); + for (int i = 182; i < 219; i++) { + assertTrue(bs.get(i), "failed to flip bit " + i); + } + assertFalse(bs.get(219), "Shouldn't have flipped bit 219"); + assertTrue(bs.get(220), "Shouldn't have flipped bit 220"); + for (int i = 221; i < bs.size(); i++) { + assertTrue(!bs.get(i), "Shouldn't have flipped bit " + i); + } + + // test illegal args + bs = newInstance(10); + try { + bs.flip(-1, 3); + fail("Test1: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // correct behavior + } + + try { + bs.flip(2, -1); + fail("Test2: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // correct behavior + } + + try { + bs.flip(4, 2); + fail("Test4: Attempt to flip with illegal args failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // correct behavior + } + } + + /** + * Tests {@link FluentBitSet#get(int)}. + */ + @Test + public void test_getI() { + // Test for method boolean java.util.BitSet.get(int) + + FluentBitSet bs = newInstance(); + bs.set(8); + assertFalse(eightFbs.get(99), "Get returned true for index out of range"); + assertTrue(eightFbs.get(3), "Get returned false for set value"); + assertFalse(bs.get(0), "Get returned true for a non set value"); + + assertThrows(IndexOutOfBoundsException.class, () -> newInstance().get(-1), "Attempt to get at negative index failed to generate exception"); + + bs = newInstance(1); + assertFalse(bs.get(64), "Access greater than size"); + + bs = newInstance(); + bs.set(63); + assertTrue(bs.get(63), "Test highest bit"); + + bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length,"); + assertEquals(0, bs.size(), "Test1: Wrong size,"); + + bs.get(2); + assertEquals(0, bs.length(), "Test2: Wrong length,"); + assertEquals(0, bs.size(), "Test2: Wrong size,"); + + bs.get(70); + assertEquals(0, bs.length(), "Test3: Wrong length,"); + assertEquals(0, bs.size(), "Test3: Wrong size,"); + + } + + /** + * Tests {@link FluentBitSet#get(int, int)}. + */ + @Test + public void test_getII() { + final FluentBitSet bitset = newInstance(30); + bitset.get(3, 3); + + // Test for method boolean java.util.BitSet.get(int, int) + FluentBitSet bs, resultbs, correctbs; + bs = newInstance(512); + bs.set(3, 9); + bs.set(10, 20); + bs.set(60, 75); + bs.set(121); + bs.set(130, 140); + + // pos1 and pos2 are in the same bitset element, at index0 + resultbs = bs.get(3, 6); + correctbs = newInstance(3); + correctbs.set(0, 3); + assertEquals(correctbs, resultbs, "Test1: Returned incorrect BitSet"); + + // pos1 and pos2 are in the same bitset element, at index 1 + resultbs = bs.get(100, 125); + correctbs = newInstance(25); + correctbs.set(21); + assertEquals(correctbs, resultbs, "Test2: Returned incorrect BitSet"); + + // pos1 in bitset element at index 0, and pos2 in bitset element at + // index 1 + resultbs = bs.get(15, 125); + correctbs = newInstance(25); + correctbs.set(0, 5); + correctbs.set(45, 60); + correctbs.set(121 - 15); + assertEquals(correctbs, resultbs, "Test3: Returned incorrect BitSet"); + + // pos1 in bitset element at index 1, and pos2 in bitset element at + // index 2 + resultbs = bs.get(70, 145); + correctbs = newInstance(75); + correctbs.set(0, 5); + correctbs.set(51); + correctbs.set(60, 70); + assertEquals(correctbs, resultbs, "Test4: Returned incorrect BitSet"); + + // pos1 in bitset element at index 0, and pos2 in bitset element at + // index 2 + resultbs = bs.get(5, 145); + correctbs = newInstance(140); + correctbs.set(0, 4); + correctbs.set(5, 15); + correctbs.set(55, 70); + correctbs.set(116); + correctbs.set(125, 135); + assertEquals(correctbs, resultbs, "Test5: Returned incorrect BitSet"); + + // pos1 in bitset element at index 0, and pos2 in bitset element at + // index 3 + resultbs = bs.get(5, 250); + correctbs = newInstance(200); + correctbs.set(0, 4); + correctbs.set(5, 15); + correctbs.set(55, 70); + correctbs.set(116); + correctbs.set(125, 135); + assertEquals(correctbs, resultbs, "Test6: Returned incorrect BitSet"); + + assertEquals(bs.get(0, bs.size()), bs, "equality principle 1 "); + + // more tests + FluentBitSet bs2 = newInstance(129); + bs2.set(0, 20); + bs2.set(62, 65); + bs2.set(121, 123); + resultbs = bs2.get(1, 124); + correctbs = newInstance(129); + correctbs.set(0, 19); + correctbs.set(61, 64); + correctbs.set(120, 122); + assertEquals(correctbs, resultbs, "Test7: Returned incorrect BitSet"); + + // equality principle with some boundary conditions + bs2 = newInstance(128); + bs2.set(2, 20); + bs2.set(62); + bs2.set(121, 123); + bs2.set(127); + resultbs = bs2.get(0, bs2.size()); + assertEquals(resultbs, bs2, "equality principle 2 "); + + bs2 = newInstance(128); + bs2.set(2, 20); + bs2.set(62); + bs2.set(121, 123); + bs2.set(127); + bs2.flip(0, 128); + resultbs = bs2.get(0, bs.size()); + assertEquals(resultbs, bs2, "equality principle 3 "); + + bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length,"); + assertEquals(0, bs.size(), "Test1: Wrong size,"); + + bs.get(0, 2); + assertEquals(0, bs.length(), "Test2: Wrong length,"); + assertEquals(0, bs.size(), "Test2: Wrong size,"); + + bs.get(60, 64); + assertEquals(0, bs.length(), "Test3: Wrong length,"); + assertEquals(0, bs.size(), "Test3: Wrong size,"); + + bs.get(64, 120); + assertEquals(0, bs.length(), "Test4: Wrong length,"); + assertEquals(0, bs.size(), "Test4: Wrong size,"); + + bs.set(25); + assertEquals(26, bs.length(), "Test5: Wrong length,"); + assertEquals(64, bs.size(), "Test5: Wrong size,"); + + bs.get(60, 64); + assertEquals(26, bs.length(), "Test6: Wrong length,"); + assertEquals(64, bs.size(), "Test6: Wrong size,"); + + bs.get(64, 120); + assertEquals(64, bs.size(), "Test7: Wrong size,"); + assertEquals(26, bs.length(), "Test7: Wrong length,"); + + bs.get(80); + assertEquals(64, bs.size(), "Test8: Wrong size,"); + assertEquals(26, bs.length(), "Test8: Wrong length,"); + + bs.get(25); + assertEquals(64, bs.size(), "Test9: Wrong size,"); + assertEquals(26, bs.length(), "Test9: Wrong length,"); + + } + + /** + * Tests {@link FluentBitSet#hashCode()}. + */ + @Test + public void test_hashCode() { + // Test for method int java.util.BitSet.hashCode() + final FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + bs.clear(2); + bs.clear(6); + assertEquals(bs.bitSet().hashCode(), bs.hashCode(), "BitSet returns wrong hash value"); + bs.set(10); + bs.clear(3); + assertEquals(97, bs.hashCode(), "BitSet returns wrong hash value"); + } + + /** + * Tests {@link FluentBitSet#intersects(FluentBitSet)}. + */ + @Test + public void test_intersects() { + // Test for method boolean java.util.BitSet.intersects(BitSet) + final FluentBitSet bs = newInstance(500); + bs.set(5); + bs.set(63); + bs.set(64); + bs.set(71, 110); + bs.set(127, 130); + bs.set(192); + bs.set(450); + + final FluentBitSet bs2 = newInstance(8); + assertFalse(bs.intersects(bs2), "Test1: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs), "Test1: intersects() returned incorrect value"); + + bs2.set(4); + assertFalse(bs.intersects(bs2), "Test2: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs), "Test2: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(5); + assertTrue(bs.intersects(bs2), "Test3: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test3: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(63); + assertTrue(bs.intersects(bs2), "Test4: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test4: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(80); + assertTrue(bs.intersects(bs2), "Test5: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test5: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(127); + assertTrue(bs.intersects(bs2), "Test6: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test6: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(192); + assertTrue(bs.intersects(bs2), "Test7: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test7: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(450); + assertTrue(bs.intersects(bs2), "Test8: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs), "Test8: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(500); + assertFalse(bs.intersects(bs2), "Test9: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs), "Test9: intersects() returned incorrect value"); + } + + /** + * Tests {@link FluentBitSet#intersects(BitSet)}. + */ + @Test + public void test_intersects_BitSet() { + // Test for method boolean java.util.BitSet.intersects(BitSet) + final FluentBitSet bs = newInstance(500); + bs.set(5); + bs.set(63); + bs.set(64); + bs.set(71, 110); + bs.set(127, 130); + bs.set(192); + bs.set(450); + + final FluentBitSet bs2 = newInstance(8); + assertFalse(bs.intersects(bs2.bitSet()), "Test1: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs.bitSet()), "Test1: intersects() returned incorrect value"); + + bs2.set(4); + assertFalse(bs.intersects(bs2.bitSet()), "Test2: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs.bitSet()), "Test2: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(5); + assertTrue(bs.intersects(bs2.bitSet()), "Test3: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test3: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(63); + assertTrue(bs.intersects(bs2.bitSet()), "Test4: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test4: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(80); + assertTrue(bs.intersects(bs2.bitSet()), "Test5: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test5: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(127); + assertTrue(bs.intersects(bs2.bitSet()), "Test6: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test6: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(192); + assertTrue(bs.intersects(bs2.bitSet()), "Test7: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test7: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(450); + assertTrue(bs.intersects(bs2.bitSet()), "Test8: intersects() returned incorrect value"); + assertTrue(bs2.intersects(bs.bitSet()), "Test8: intersects() returned incorrect value"); + + bs2.clear(); + bs2.set(500); + assertFalse(bs.intersects(bs2.bitSet()), "Test9: intersects() returned incorrect value"); + assertFalse(bs2.intersects(bs.bitSet()), "Test9: intersects() returned incorrect value"); + } + + /** + * Tests {@link FluentBitSet#isEmpty()}. + */ + @Test + public void test_isEmpty() { + final FluentBitSet bs = newInstance(500); + assertTrue(bs.isEmpty(), "Test: isEmpty() returned wrong value"); + + // at bitset element 0 + bs.set(3); + assertFalse(bs.isEmpty(), "Test0: isEmpty() returned wrong value"); + + // at bitset element 1 + bs.clear(); + bs.set(12); + assertFalse(bs.isEmpty(), "Test1: isEmpty() returned wrong value"); + + // at bitset element 2 + bs.clear(); + bs.set(128); + assertFalse(bs.isEmpty(), "Test2: isEmpty() returned wrong value"); + + // boundary testing + bs.clear(); + bs.set(459); + assertFalse(bs.isEmpty(), "Test3: isEmpty() returned wrong value"); + + bs.clear(); + bs.set(511); + assertFalse(bs.isEmpty(), "Test4: isEmpty() returned wrong value"); + } + + /** + * Tests {@link FluentBitSet#length()}. + */ + @Test + public void test_length() { + final FluentBitSet bs = newInstance(); + assertEquals(0, bs.length(), "BitSet returned wrong length"); + bs.set(5); + assertEquals(6, bs.length(), "BitSet returned wrong length"); + bs.set(10); + assertEquals(11, bs.length(), "BitSet returned wrong length"); + bs.set(432); + assertEquals(433, bs.length(), "BitSet returned wrong length"); + bs.set(300); + assertEquals(433, bs.length(), "BitSet returned wrong length"); + } + + /** + * Tests {@link FluentBitSet#nextClearBit(int)}. + */ + @Test + public void test_nextClearBitI() { + // Test for method int java.util.BitSet.nextSetBit() + final FluentBitSet bs = newInstance(500); + bs.set(0, bs.size() - 1); // ensure all the bits from 0 to bs.size() + // -1 + bs.set(bs.size() - 1); // are set to true + bs.clear(5); + bs.clear(32); + bs.clear(63); + bs.clear(64); + bs.clear(71, 110); + bs.clear(127, 130); + bs.clear(193); + bs.clear(450); + try { + bs.nextClearBit(-1); + fail("Expected IndexOutOfBoundsException for negative index"); + } catch (final IndexOutOfBoundsException e) { + // correct behavior + } + assertEquals(5, bs.nextClearBit(0), "nextClearBit() returned the wrong value"); + assertEquals(5, bs.nextClearBit(5), "nextClearBit() returned the wrong value"); + assertEquals(32, bs.nextClearBit(6), "nextClearBit() returned the wrong value"); + assertEquals(32, bs.nextClearBit(32), "nextClearBit() returned the wrong value"); + assertEquals(63, bs.nextClearBit(33), "nextClearBit() returned the wrong value"); + + // boundary tests + assertEquals(63, bs.nextClearBit(63), "nextClearBit() returned the wrong value"); + assertEquals(64, bs.nextClearBit(64), "nextClearBit() returned the wrong value"); + + // at bitset element 1 + assertEquals(71, bs.nextClearBit(65), "nextClearBit() returned the wrong value"); + assertEquals(71, bs.nextClearBit(71), "nextClearBit() returned the wrong value"); + assertEquals(72, bs.nextClearBit(72), "nextClearBit() returned the wrong value"); + assertEquals(127, bs.nextClearBit(110), "nextClearBit() returned the wrong value"); + + // boundary tests + assertEquals(127, bs.nextClearBit(127), "nextClearBit() returned the wrong value"); + assertEquals(128, bs.nextClearBit(128), "nextClearBit() returned the wrong value"); + + // at bitset element 2 + assertEquals(193, bs.nextClearBit(130), "nextClearBit() returned the wrong value"); + assertEquals(193, bs.nextClearBit(191), "nextClearBit() returned the wrong value"); + + assertEquals(193, bs.nextClearBit(192), "nextClearBit() returned the wrong value"); + assertEquals(193, bs.nextClearBit(193), "nextClearBit() returned the wrong value"); + assertEquals(450, bs.nextClearBit(194), "nextClearBit() returned the wrong value"); + assertEquals(450, bs.nextClearBit(255), "nextClearBit() returned the wrong value"); + assertEquals(450, bs.nextClearBit(256), "nextClearBit() returned the wrong value"); + assertEquals(450, bs.nextClearBit(450), "nextClearBit() returned the wrong value"); + + // bitset has 1 still the end of bs.size() -1, but calling nextClearBit + // with any index value + // after the last true bit should return bs.size(), + assertEquals(512, bs.nextClearBit(451), "nextClearBit() returned the wrong value"); + assertEquals(512, bs.nextClearBit(511), "nextClearBit() returned the wrong value"); + assertEquals(512, bs.nextClearBit(512), "nextClearBit() returned the wrong value"); + + // if the index is larger than bs.size(), nextClearBit should return + // index; + assertEquals(513, bs.nextClearBit(513), "nextClearBit() returned the wrong value"); + assertEquals(800, bs.nextClearBit(800), "nextClearBit() returned the wrong value"); + } + + /** + * Tests {@link FluentBitSet#nextSetBit(int)}. + */ + @Test + public void test_nextSetBitI() { + // Test for method int java.util.BitSet.nextSetBit() + final FluentBitSet bs = newInstance(500); + bs.set(5); + bs.set(32); + bs.set(63); + bs.set(64); + bs.set(71, 110); + bs.set(127, 130); + bs.set(193); + bs.set(450); + try { + bs.nextSetBit(-1); + fail("Expected IndexOutOfBoundsException for negative index"); + } catch (final IndexOutOfBoundsException e) { + // correct behavior + } + assertEquals(5, bs.nextSetBit(0), "nextSetBit() returned the wrong value"); + assertEquals(5, bs.nextSetBit(5), "nextSetBit() returned the wrong value"); + assertEquals(32, bs.nextSetBit(6), "nextSetBit() returned the wrong value"); + assertEquals(32, bs.nextSetBit(32), "nextSetBit() returned the wrong value"); + assertEquals(63, bs.nextSetBit(33), "nextSetBit() returned the wrong value"); + + // boundary tests + assertEquals(63, bs.nextSetBit(63), "nextSetBit() returned the wrong value"); + assertEquals(64, bs.nextSetBit(64), "nextSetBit() returned the wrong value"); + + // at bitset element 1 + assertEquals(71, bs.nextSetBit(65), "nextSetBit() returned the wrong value"); + assertEquals(71, bs.nextSetBit(71), "nextSetBit() returned the wrong value"); + assertEquals(72, bs.nextSetBit(72), "nextSetBit() returned the wrong value"); + assertEquals(127, bs.nextSetBit(110), "nextSetBit() returned the wrong value"); + + // boundary tests + assertEquals(127, bs.nextSetBit(127), "nextSetBit() returned the wrong value"); + assertEquals(128, bs.nextSetBit(128), "nextSetBit() returned the wrong value"); + + // at bitset element 2 + assertEquals(193, bs.nextSetBit(130), "nextSetBit() returned the wrong value"); + + assertEquals(193, bs.nextSetBit(191), "nextSetBit() returned the wrong value"); + assertEquals(193, bs.nextSetBit(192), "nextSetBit() returned the wrong value"); + assertEquals(193, bs.nextSetBit(193), "nextSetBit() returned the wrong value"); + assertEquals(450, bs.nextSetBit(194), "nextSetBit() returned the wrong value"); + assertEquals(450, bs.nextSetBit(255), "nextSetBit() returned the wrong value"); + assertEquals(450, bs.nextSetBit(256), "nextSetBit() returned the wrong value"); + assertEquals(450, bs.nextSetBit(450), "nextSetBit() returned the wrong value"); + + assertEquals(-1, bs.nextSetBit(451), "nextSetBit() returned the wrong value"); + assertEquals(-1, bs.nextSetBit(511), "nextSetBit() returned the wrong value"); + assertEquals(-1, bs.nextSetBit(512), "nextSetBit() returned the wrong value"); + assertEquals(-1, bs.nextSetBit(800), "nextSetBit() returned the wrong value"); + } + + /** + * Tests {@link FluentBitSet#or(FluentBitSet)}. + */ + @Test + public void test_or() { + // Test for method void java.util.BitSet.or(BitSet) + FluentBitSet bs = newInstance(128); + bs.or(eightFbs); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR failed to set bits"); + } + + bs = newInstance(0); + bs.or(eightFbs); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR(0) failed to set bits"); + } + + eightFbs.clear(5); + bs = newInstance(128); + bs.or(eightFbs); + assertFalse(bs.get(5), "OR set a bit which should be off"); + } + + /** + * Tests {@link FluentBitSet#or(BitSet)}. + */ + @Test + public void test_or_BitSet() { + // Test for method void java.util.BitSet.or(BitSet) + FluentBitSet bs = newInstance(128); + bs.or(eightFbs.bitSet()); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR failed to set bits"); + } + + bs = newInstance(0); + bs.or(eightFbs.bitSet()); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR(0) failed to set bits"); + } + + eightFbs.clear(5); + bs = newInstance(128); + bs.or(eightFbs.bitSet()); + assertFalse(bs.get(5), "OR set a bit which should be off"); + } + + /** + * Tests {@link FluentBitSet#or(FluentBitSet)}. + */ + @Test + public void test_or_FluentBitSetArray() { + // Test for method void java.util.BitSet.or(BitSet) + FluentBitSet bs = newInstance(128); + bs.or(new FluentBitSet[] {eightFbs}); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR failed to set bits"); + } + + bs = newInstance(0); + bs.or(new FluentBitSet[] {eightFbs}); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "OR(0) failed to set bits"); + } + + eightFbs.clear(5); + bs = newInstance(128); + bs.or(new FluentBitSet[] {eightFbs}); + assertFalse(bs.get(5), "OR set a bit which should be off"); + } + + /** + * Tests {@link FluentBitSet#previousClearBit(int)}. + */ + @Test + public void test_previousClearBit() { + final FluentBitSet bs = newInstance(); + assertEquals(1, bs.previousClearBit(1), "previousClearBit"); + } + + /** + * Tests {@link FluentBitSet#previousSetBit(int)}. + */ + @Test + public void test_previousSetBit() { + final FluentBitSet bs = newInstance(); + assertEquals(-1, bs.previousSetBit(1), "previousSetBit"); + } + + /** + * Tests {@link FluentBitSet#set(int, int)}. + */ + @Test + public void test_setII() { + final FluentBitSet bitset = newInstance(30); + bitset.set(29, 29); + + // Test for method void java.util.BitSet.set(int, int) + // pos1 and pos2 are in the same bitset element + FluentBitSet bs = newInstance(16); + bs.set(5); + bs.set(15); + bs.set(7, 11); + for (int i = 0; i < 7; i++) { + if (i == 5) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + for (int i = 7; i < 11; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + for (int i = 11; i < bs.size(); i++) { + if (i == 15) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + // pos1 and pos2 is in the same bitset element, boundry testing + bs = newInstance(16); + bs.set(7, 64); + assertEquals(64, bs.size(), "Failed to grow BitSet"); + for (int i = 0; i < 7; i++) { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + for (int i = 7; i < 64; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(64), "Shouldn't have set bit 64"); + + // more boundary testing + bs = newInstance(32); + bs.set(0, 64); + for (int i = 0; i < 64; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(64), "Shouldn't have set bit 64"); + + bs = newInstance(32); + bs.set(0, 65); + for (int i = 0; i < 65; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(65), "Shouldn't have set bit 65"); + + // pos1 and pos2 are in two sequential bitset elements + bs = newInstance(128); + bs.set(7); + bs.set(110); + bs.set(9, 74); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + for (int i = 9; i < 74; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + for (int i = 74; i < bs.size(); i++) { + if (i == 110) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + // pos1 and pos2 are in two non-sequential bitset elements + bs = newInstance(256); + bs.set(7); + bs.set(255); + bs.set(9, 219); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertTrue(bs.get(i), "Shouldn't have set flipped " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + for (int i = 9; i < 219; i++) { + assertTrue(bs.get(i), "failed to set bit " + i); + } + + for (int i = 219; i < 255; i++) { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + + assertTrue(bs.get(255), "Shouldn't have flipped bit 255"); + + // test illegal args + bs = newInstance(10); + try { + bs.set(-1, 3); + fail("Test1: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + try { + bs.set(2, -1); + fail("Test2: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + bs.set(2, 2); + assertFalse(bs.get(2), "Bit got set incorrectly "); + + try { + bs.set(4, 2); + fail("Test4: Attempt to flip with illegal args failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + } + + /** + * Tests {@link FluentBitSet#set(int, int, boolean)}. + */ + @Test + public void test_setIIZ() { + // Test for method void java.util.BitSet.set(int, int, boolean) + eightFbs.set(3, 6, false); + assertTrue(!eightFbs.get(3) && !eightFbs.get(4) && !eightFbs.get(5), "Should have set bits 3, 4, and 5 to false"); + + eightFbs.set(3, 6, true); + assertTrue(eightFbs.get(3) && eightFbs.get(4) && eightFbs.get(5), "Should have set bits 3, 4, and 5 to true"); + + } + + /** + * Tests {@link FluentBitSet#setInclusive(int, int)}. + */ + @Test + public void test_setInclusive() { + final FluentBitSet bitset = newInstance(30); + bitset.set(29, 29); + + // Test for method void java.util.BitSet.set(int, int) + // pos1 and pos2 are in the same bitset element + FluentBitSet bs = newInstance(16); + bs.set(5); + bs.set(15); + bs.setInclusive(7, 11); + for (int i = 0; i < 7; i++) { + if (i == 5) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + for (int i = 7; i < 12; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + for (int i = 12; i < bs.size(); i++) { + if (i == 15) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + // pos1 and pos2 is in the same bitset element, boundry testing + bs = newInstance(16); + bs.setInclusive(7, 64); + assertEquals(128, bs.size(), "Failed to grow BitSet"); + for (int i = 0; i < 7; i++) { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + for (int i = 7; i < 65; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(65), "Shouldn't have set bit 64"); + + // more boundary testing + bs = newInstance(32); + bs.setInclusive(0, 64); + for (int i = 0; i < 65; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(65), "Shouldn't have set bit 64"); + + bs = newInstance(32); + bs.setInclusive(0, 65); + for (int i = 0; i < 66; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + assertFalse(bs.get(66), "Shouldn't have set bit 65"); + + // pos1 and pos2 are in two sequential bitset elements + bs = newInstance(128); + bs.set(7); + bs.set(110); + bs.setInclusive(9, 74); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + for (int i = 9; i < 75; i++) { + assertTrue(bs.get(i), "Failed to set bit " + i); + } + for (int i = 75; i < bs.size(); i++) { + if (i == 110) { + assertTrue(bs.get(i), "Shouldn't have flipped bit " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + // pos1 and pos2 are in two non-sequential bitset elements + bs = newInstance(256); + bs.set(7); + bs.set(255); + bs.setInclusive(9, 219); + for (int i = 0; i < 9; i++) { + if (i == 7) { + assertTrue(bs.get(i), "Shouldn't have set flipped " + i); + } else { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + } + + for (int i = 9; i < 220; i++) { + assertTrue(bs.get(i), "failed to set bit " + i); + } + + for (int i = 220; i < 255; i++) { + assertFalse(bs.get(i), "Shouldn't have set bit " + i); + } + + assertTrue(bs.get(255), "Shouldn't have flipped bit 255"); + + // test illegal args + bs = newInstance(10); + try { + bs.setInclusive(-1, 3); + fail("Test1: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + try { + bs.setInclusive(2, -1); + fail("Test2: Attempt to flip with negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + bs.setInclusive(2, 2); + assertFalse(bs.get(3), "Bit got set incorrectly "); + + try { + bs.setInclusive(4, 2); + fail("Test4: Attempt to flip with illegal args failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + } + + /** + * Tests {@link FluentBitSet#set(int)}. + */ + @Test + public void test_setInt() { + // Test for method void java.util.BitSet.set(int) + + FluentBitSet bs = newInstance(); + bs.set(8); + assertTrue(bs.get(8), "Failed to set bit"); + + try { + bs.set(-1); + fail("Attempt to set at negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + // Try setting a bit on a 64 boundary + bs.set(128); + assertEquals(192, bs.size(), "Failed to grow BitSet"); + assertTrue(bs.get(128), "Failed to set bit"); + + bs = newInstance(64); + for (int i = bs.size(); --i >= 0;) { + bs.set(i); + assertTrue(bs.get(i), "Incorrectly set"); + assertEquals(i + 1, bs.length(), "Incorrect length"); + for (int j = bs.size(); --j > i;) { + assertFalse(bs.get(j), "Incorrectly set bit " + j); + } + for (int j = i; --j >= 0;) { + assertFalse(bs.get(j), "Incorrectly set bit " + j); + } + bs.clear(i); + } + + bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length"); + bs.set(0); + assertEquals(1, bs.length(), "Test2: Wrong length"); + } + + /** + * Tests {@link FluentBitSet#set(int...)}. + */ + @Test + public void test_setIntArray() { + // Test for method void java.util.BitSet.set(int) + + FluentBitSet bs = newInstance(); + bs.set(new int[] {8}); + assertTrue(bs.get(8), "Failed to set bit"); + + try { + bs.set(new int[] {-1}); + fail("Attempt to set at negative index failed to generate exception"); + } catch (final IndexOutOfBoundsException e) { + // Correct behavior + } + + // Try setting a bit on a 64 boundary + bs.set(new int[] {128}); + assertEquals(192, bs.size(), "Failed to grow BitSet"); + assertTrue(bs.get(128), "Failed to set bit"); + + bs = newInstance(64); + for (int i = bs.size(); --i >= 0;) { + bs.set(new int[] {i}); + assertTrue(bs.get(i), "Incorrectly set"); + assertEquals(i + 1, bs.length(), "Incorrect length"); + for (int j = bs.size(); --j > i;) { + assertFalse(bs.get(j), "Incorrectly set bit " + j); + } + for (int j = i; --j >= 0;) { + assertFalse(bs.get(j), "Incorrectly set bit " + j); + } + bs.clear(i); + } + + bs = newInstance(0); + assertEquals(0, bs.length(), "Test1: Wrong length"); + bs.set(new int[] {0}); + assertEquals(1, bs.length(), "Test2: Wrong length"); + } + + /** + * Tests {@link FluentBitSet#set(int, boolean)}. + */ + @Test + public void test_setIZ() { + // Test for method void java.util.BitSet.set(int, boolean) + eightFbs.set(5, false); + assertFalse(eightFbs.get(5), "Should have set bit 5 to true"); + + eightFbs.set(5, true); + assertTrue(eightFbs.get(5), "Should have set bit 5 to false"); + } + + /** + * Tests {@link FluentBitSet#setInclusive(int, int)}. + */ + @Test + public void test_setRangeInclusive() { + // Test for method int java.util.BitSet.size() + assertEquals(64, eightFbs.size(), "Returned incorrect size"); + eightFbs.set(129); + assertTrue(eightFbs.size() >= 129, "Returned incorrect size"); + + } + + /** + * Tests {@link FluentBitSet#size()}. + */ + @Test + public void test_size() { + // Test for method int java.util.BitSet.size() + assertEquals(64, eightFbs.size(), "Returned incorrect size"); + eightFbs.set(129); + assertTrue(eightFbs.size() >= 129, "Returned incorrect size"); + + } + + /** + * Tests {@link FluentBitSet#previousSetBit(int)}. + */ + @Test + public void test_stream() { + final FluentBitSet bs = newInstance(); + assertEquals(0, bs.stream().count(), "stream"); + } + + /** + * Tests {@link FluentBitSet#previousSetBit(int)}. + */ + @Test + public void test_toByteArray() { + final FluentBitSet bs = newInstance(); + assertArrayEquals(ArrayUtils.EMPTY_BYTE_ARRAY, bs.toByteArray(), "stream"); + } + + /** + * Tests {@link FluentBitSet#previousSetBit(int)}. + */ + @Test + public void test_toLongArray() { + final FluentBitSet bs = newInstance(); + assertArrayEquals(ArrayUtils.EMPTY_LONG_ARRAY, bs.toLongArray(), "stream"); + } + + /** + * Tests {@link FluentBitSet#toString()}. + */ + @Test + public void test_toString() { + // Test for method java.lang.String java.util.BitSet.toString() + assertEquals("{0, 1, 2, 3, 4, 5, 6, 7}", eightFbs.toString(), "Returned incorrect string representation"); + eightFbs.clear(2); + assertEquals("{0, 1, 3, 4, 5, 6, 7}", eightFbs.toString(), "Returned incorrect string representation"); + } + + /** + * Tests {@link FluentBitSet#xor(FluentBitSet)}. + */ + @Test + public void test_xor() { + // Test for method void java.util.BitSet.xor(BitSet) + + FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + bs.xor(eightFbs); + for (int i = 0; i < 8; i++) { + assertFalse(bs.get(i), "XOR failed to clear bits"); + } + + bs.xor(eightFbs); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "XOR failed to set bits"); + } + + bs = newInstance(0); + bs.xor(eightFbs); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "XOR(0) failed to set bits"); + } + + bs = newInstance(); + bs.set(63); + assertEquals("{63}", bs.toString(), "Test highest bit"); + } + + /** + * Tests {@link FluentBitSet#xor(BitSet)}. + */ + @Test + public void test_xor_BitSet() { + // Test for method void java.util.BitSet.xor(BitSet) + + FluentBitSet bs = (FluentBitSet) eightFbs.clone(); + bs.xor(eightFbs.bitSet()); + for (int i = 0; i < 8; i++) { + assertFalse(bs.get(i), "XOR failed to clear bits"); + } + + bs.xor(eightFbs.bitSet()); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "XOR failed to set bits"); + } + + bs = newInstance(0); + bs.xor(eightFbs.bitSet()); + for (int i = 0; i < 8; i++) { + assertTrue(bs.get(i), "XOR(0) failed to set bits"); + } + + bs = newInstance(); + bs.set(63); + assertEquals("{63}", bs.toString(), "Test highest bit"); + } + +}