Position.java

package swingtree.layout;

import com.google.errorprone.annotations.Immutable;

import java.awt.Point;
import java.util.Objects;

/**
 *  An immutable value based class that represents a location in a two-dimensional
 *  coordinate system specified in float precision and specifically designed
 *  for Swing components.
 *  It can be used as an alternative to the AWT {@link Point} class,
 *  but in situations where immutability is desired (which should be most cases).
 *  <p>
 *  Use the {@link #of(float, float)} factory method to create a new instance
 *  or {@link #withX(int)} and {@link #withY(int)} to create a new instance
 *  with a modified value.
 */
@Immutable
public final class Position
{
    private final static Position ORIGIN = new Position( 0, 0 );

    /**
     *  A factory method that creates a new location with the specified x- and y-coordinates
     *  or returns the {@link #origin()} constant if both coordinates are zero.
     *
     * @param x The x-coordinate of the location to create.
     * @param y The y-coordinate of the location to create.
     * @return A new location with the specified x- and y-coordinates.
     *         If both coordinates are zero, the {@link #origin()} is returned.
     */
    public static Position of( float x, float y ) {
        if ( x == 0 && y == 0 )
            return ORIGIN;

        return new Position(x, y);
    }

    /**
     *  A factory method that creates a new location from the supplied AWT {@link Point}.
     *  If the point is null, a {@link NullPointerException} is thrown.
     *
     * @param p The point to create a location from.
     * @return A new location with the x- and y-coordinates of the specified point.
     *         If both coordinates are zero, the {@link #origin()} is returned.
     */
    public static Position of(Point p ) {
        Objects.requireNonNull(p);
        return of( p.x, p.y );
    }

    public static Position origin() {
        return ORIGIN;
    }

    final float _x;
    final float _y;


    private Position( float x, float y ) {
        _x = x;
        _y = y;
    }

    /**
     *  Exposes the x coordinate of this location in the form of a float,
     *  which describes the horizontal position in a two-dimensional coordinate system.
     *  So the larger the x value of the location, the further to the right it is.
     *
     * @return The x-coordinate of this location.
     */
    public float x() {
        return _x;
    }

    /**
     *  Exposes the y coordinate of this location in the form of a float,
     *  which describes the vertical position in a two-dimensional coordinate system.
     *  So the larger the y value of the location, the further down it is.
     *
     * @return The y-coordinate of this location.
     */
    public float y() {
        return _y;
    }

    /**
     *  Allows you to create an updated version of this location with the
     *  specified y-coordinate and the same x-coordinate
     *  as this location instance.
     *
     * @param y The y-coordinate of the location to create.
     * @return A new location with the same x-coordinate as this location
     *         and the specified y-coordinate.
     */
    public Position withY(int y ) {
        return of( _x, y );
    }

    /**
     *  Allows you to create an updated version of this location with the
     *  specified x-coordinate and the same y-coordinate
     *  as this location instance.
     *
     * @param x The x-coordinate of the location to create.
     * @return A new location with the same y-coordinate as this location
     *         and the specified x-coordinate.
     */
    public Position withX(int x ) {
        return of( x, _y );
    }

    /**
     *  Creates a new location where the specified {@code dx} and
     *  {@code dy} values are added to the x- and y-coordinates of this location.
     *
     * @param dx The amount to increase the x-coordinate by.
     * @param dy The amount to increase the y-coordinate by.
     * @return A new location with the x- and y-coordinates of this location
     *         increased by the specified values.
     */
    public Position plus(float dx, float dy ) {
        return of( _x + dx, _y + dy );
    }

    /**
     *  Creates a new location where the x- and y-coordinates of the specified
     *  {@link Position} are added to the x- and y-coordinates of this location.
     *
     * @param other The location to add to this location.
     * @return A new location with the x- and y-coordinates of this location
     *         increased by the x- and y-coordinates of the specified location.
     */
    public Position plus(Position other ) {
        return of( _x + other._x, _y + other._y );
    }

    /**
     *  Creates a new location where the specified {@code dx} and
     *  {@code dy} values are subtracted from the x- and y-coordinates of this location.
     *
     * @param dx The amount to decrease the x-coordinate by.
     * @param dy The amount to decrease the y-coordinate by.
     * @return A new location with the x- and y-coordinates of this location
     *         decreased by the specified values.
     */
    public Position minus(float dx, float dy ) {
        return of( _x - dx, _y - dy );
    }

    /**
     *  Creates a new location where the x- and y-coordinates of the specified
     *  {@link Position} are subtracted from the x- and y-coordinates of this location.
     *
     * @param other The location to subtract from this location.
     * @return A new location with the x- and y-coordinates of this location
     *         decreased by the x- and y-coordinates of the specified location.
     */
    public Position minus(Position other ) {
        return of( _x - other._x, _y - other._y );
    }

    /**
     *  A {@link Position} consists of two x and y coordinates in 2D space, which is
     *  why this convenience method allows you to transform this
     *  {@link Position} object to an AWT {@link Point}.
     *
     * @return A new AWT {@link Point} with the same x- and y-coordinates as this location.
     */
    public Point toPoint() {
        return new Point( (int) _x, (int) _y);
    }


    @Override
    public String toString() {
        return this.getClass().getSimpleName()+"[" +
                    "x=" + _x + ", "+
                    "y=" + _y +
                "]";
    }

    @Override
    public boolean equals( Object o ) {
        if ( o == this ) return true;
        if ( o == null ) return false;
        if ( o.getClass() != this.getClass() ) return false;
        Position other = (Position) o;
        return _x == other._x && _y == other._y;
    }

    @Override
    public int hashCode() {
        return Float.hashCode(_x) ^ Float.hashCode(_y);
    }

}