All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.organicdesign.fp.oneOf.OneOf2OrNone Maven / Gradle / Ivy

package org.organicdesign.fp.oneOf;

import org.organicdesign.fp.collections.ImList;
import org.organicdesign.fp.function.Fn0;
import org.organicdesign.fp.function.Fn1;
import org.organicdesign.fp.type.RuntimeTypes;

import java.util.Objects;

import static org.organicdesign.fp.type.RuntimeTypes.union2Str;

/**
 Holds one of 2 values or {@link None}. See {@link OneOf2} for a full description.  If you're passing the same type,
 you probably want a tuple instead.
 */
public abstract class OneOf2OrNone {

    private final Object item;
    private final int sel;
    private final ImList types;

    /**
     Protected constructor for subclassing.

     @param o the item (may be null)
     @param aClass class 0 (to have at runtime for descriptive error messages and toString()).
     @param bClass class 1 (to have at runtime for descriptive error messages and toString()).
     @param index 0 means this represents an A, 1 represents a B, 2 represents a None
     */
    protected OneOf2OrNone(Object o, Class aClass, Class bClass, int index) {
        types = RuntimeTypes.registerClasses(aClass, bClass, None.class);
        sel = index;
        item = o;
        if (index < 0) {
            throw new IllegalArgumentException("Selected item index must be 0-2");
        } else if (index > 2) {
            throw new IllegalArgumentException("Selected item index must be 0-2");
        } else if ( (index == 2) && (o != null) ) {
            throw new IllegalArgumentException("You specified the index " + index +
                                               " meaning 'None' but passed a value: " + o);
        }
        if ( (o != null) && (!types.get(index).isInstance(o)) ) {
            throw new ClassCastException("You specified index " + index + ", indicating a(n) " +
                                         types.get(index).getCanonicalName() + "," +
                                         " but passed a " + o.getClass().getCanonicalName());
        }
    }

    /**
     Languages that have union types built in have a match statement that works like this method.
     Exactly one of these functions will be executed - determined by which type of item this object holds.
     @param fa the function to be executed if this OneOf stores the first type.
     @param fb the function to be executed if this OneOf stores the second type.
     @param fz the function to be executed if this OneOf stores a None.
     @return the return value of whichever function is executed.
     */
    // We only store one item and it's type is erased, so we have to cast it at runtime.
    // If sel is managed correctly, it ensures that the cast is accurate.
    @SuppressWarnings("unchecked")
    public  R match(Fn1 fa,
                       Fn1 fb,
                       Fn0 fz) {
        if (sel == 0) {
            return fa.apply((A) item);
        } else if (sel == 1) {
            return fb.apply((B) item);
        } else {
            return fz.apply();
        }
    }

    public int hashCode() {
        // Simplest way to make the two items different.
        return Objects.hashCode(item) + sel;
    }

    @SuppressWarnings("unchecked")
    @Override public boolean equals(Object other) {
        if (this == other) { return true; }
        if (!(other instanceof OneOf2OrNone)) { return false; }

        OneOf2OrNone that = (OneOf2OrNone) other;
        return (sel == that.sel) &&
               Objects.equals(item, that.item);
    }

    @Override public String toString() { return union2Str(sel == 2 ? None.NONE : item, types); }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy