package com.supaham.commons.relatives; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import java.util.function.Function; import javax.annotation.Nonnull; import javax.annotation.Nullable; import pluginbase.config.annotation.SerializeWith; /** * Represents a class for relativity with {@link Number} types. This feature and class should not be used in a * predictable environment. To clarify, this class is only meant to be used when some maths is to be done by the work * of some external data, such as a configuration file. If your program functionality is strictly kept inside the * program please resort to classes such as {@link Math} or direct arithmetic operators for your operations as it may * be more convenient both for the programmer and the JVM. * * <p /> * Although {@link RelativeNumber}'s {@code from} methods take all six primitive types, it is important to note that it * eventually stores it as a double for the largest number possible. To be able to distinguish whether the initial * number was whole or not, use {@link #isWholeNumber()}. * * <p /> * The {@link #isRelative()} feature is provided for ease of use in general cases. If the user wishes to have true * relativity they will have to check the boolean themselves and handle it from there. For more information see {@link * #isRelative()}. * * @see ArithmeticOperator * @see RelativeNumberSerializer */ @SerializeWith(RelativeNumberSerializer.class) public final class RelativeNumber implements Function<Number, Double> { /** * Represents a {@code ZERO} {@code relative} number with the {@link ArithmeticOperator#ADDITION} operator and {@link * #isWholeNumber()} as true. */ public static final RelativeNumber ZERO = from(ArithmeticOperator.ADDITION, 0); private final ArithmeticOperator operator; private final double number; private final boolean wholeNumber; private final boolean relative; /** * Deserialization in the form of {@link #toString()}. The following table shows the valid and invalid serialized * forms: * <table> * <thead> * <tr> * <th>Valid</th> * <th>Invalid</th> * </tr> * </thead> * * <tbody> * <tr> * <td>1</td> * <td>1asd</td> * </tr> * <tr> * <td>+1</td> * <td>+1asd</td> * </tr> * <tr> * <td>-1</td> * <td>-1asd</td> * </tr> * <tr> * <td></td> * <td>*1asd</td> * </tr> * * <tr> * <td>~1</td> * <td>~1asd</td> * </tr> * * <tr> * <td>~+1</td> * <td>~1+</td> * </tr> * * <tr> * <td>~-1</td> * <td>~1-</td> * </tr> * * <tr> * <td>~*1</td> * <td>~1*</td> * </tr> * * <tr> * <td>~/1</td> * <td>~1/</td> * </tr> * * <tr> * <td>~%1</td> * <td>~1%</td> * </tr> * * <tr> * <td>~^1</td> * <td>~1^</td> * </tr> * </tbody> * </table> * * @param string string to deserialize * * @return deserialized string in the form of {@link RelativeNumber} */ public static RelativeNumber fromString(@Nonnull String string) { Preconditions.checkNotNull(string, "string cannot be null."); boolean relative = false; double number; boolean wholeNumber; ArithmeticOperator operator; string = string.trim(); // Make sure the string isn't just "" or "~". if (string.isEmpty() || string.equals("~")) { return ZERO; } if (string.startsWith("~")) { relative = true; string = string.substring(1).trim(); } if (Character.isDigit(string.charAt(0))) { operator = ArithmeticOperator.ADDITION; } else { operator = ArithmeticOperator.fromChar(string.charAt(0)); string = string.substring(1).trim(); } try { number = Integer.parseInt(string); wholeNumber = true; } catch (NumberFormatException e) { try { number = Double.parseDouble(string); wholeNumber = false; } catch (NumberFormatException e2) { throw new NumberFormatException(string + " must consists of only numbers and at most one decimal point."); } } Preconditions.checkNotNull(operator, "operator cannot be null."); return new RelativeNumber(operator, number, wholeNumber, relative); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, byte b) { return new RelativeNumber(operator, b, true); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, short s) { return new RelativeNumber(operator, s, true); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, float f) { return new RelativeNumber(operator, f, false); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, int i) { return new RelativeNumber(operator, i, true); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, long l) { return new RelativeNumber(operator, l, true); } public static RelativeNumber from(@Nonnull ArithmeticOperator operator, double d) { return new RelativeNumber(operator, d, false); } private RelativeNumber(ArithmeticOperator operator, Number number, boolean wholeNumber) { this(operator, number, wholeNumber, true); } private RelativeNumber(ArithmeticOperator operator, Number number, boolean wholeNumber, boolean relative) { this.operator = operator; this.number = number.doubleValue(); this.wholeNumber = wholeNumber; this.relative = relative; } /** * Applies a {@link Number} to this {@link RelativeNumber} for mathematical operation based on {@link * #getOperator()}. If the given {@code number} is null, {@link #getNumber()} is returned. * * @param number number to apply to the operator, nullable * * @return result of the operation */ @Override public Double apply(@Nullable Number number) { return number == null || !this.relative ? this.number : this.operator.applyToDouble(this.number, number.doubleValue()); } @Override public boolean equals(Object object) { if (this == object) { return true; } if (object == null || getClass() != object.getClass()) { return false; } RelativeNumber that = (RelativeNumber) object; return Double.compare(that.number, number) == 0 && relative == that.relative && operator == that.operator; } @Override public int hashCode() { return Objects.hashCode(operator, number, relative); } /** * Serializes this object as "~[op]number". Where ~ is provided if {@link #isRelative()}, [op] is only provided if * the {@link #getOperator()} is not {@link ArithmeticOperator#ADDITION}, number is provided at all times. * * @return serialized relative number */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (this.relative) { sb.append("~"); } // We automatically interpret number only as ADDITION in deserialization, so omit it from serialization. if (this.operator != ArithmeticOperator.ADDITION) { sb.append(this.operator.getChar()); } if (wholeNumber) { sb.append((int) this.number); } else { sb.append(this.number); } return sb.toString(); } @Nonnull public ArithmeticOperator getOperator() { return operator; } public double getNumber() { return number; } /** * Returns whether this {@link RelativeNumber} is truly of relative nature. The only case where this returns false is * if this instance was deserialized as a whole number via {@link #fromString(String)}, e.g. "123". */ public boolean isRelative() { return relative; } /** * Returns whether {@link RelativeNumber#getNumber()} was initially created with a whole number. * * @return whether this relative number uses a whole number */ public boolean isWholeNumber() { return wholeNumber; } }