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

cdc.util.enums.AbstractEnumMask Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show newest version
package cdc.util.enums;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import cdc.util.function.Iterables;
import cdc.util.function.Predicates;
import cdc.util.lang.Checks;
import cdc.util.lang.CollectionsUtil;

/**
 * Mask (set) of values belonging to an enum type.
 * 

* Objects of this class are immutable. *

* A typical implementation should look like this: *

{@code
 * public final class FooMask extends AbstractEnumMask {
 *     public static final Support SUPPORT = support(FooMask.class, FooMask::new, FooType, Nullable.FALSE);
 *
 *     private FooMask(Support support,
 *                     Set values) {
 *         super(support, values);
 *     }
 * }
 * }
* For a standard enum, one can declare: *
{@code
 * public final class FooMask extends AbstractEnumMask {
 *     public static final Support SUPPORT = support(FooMask.class, FooMask::new, Foo.class, Nullable.FALSE);
 *
 *     private FooMask(Support support,
 *                     Set values) {
 *         super(support, values);
 *     }
 * }
 * }
* * @author Damien Carbonne * @param The mask type. * @param The enum type. */ public class AbstractEnumMask, V> { /** * The associated support class. */ protected final Support owner; /** * The mask values. */ protected final Set values; protected AbstractEnumMask(Support support, Set values) { Checks.isNotNull(support, "support"); this.owner = support; this.values = Collections.unmodifiableSet(values); checkValues(); } /** * Support methods. * * @author Damien Carbonne * * @param The mask type. * @param The enum type. */ public static interface Support { /** * @return The mask class. */ public Class getMaskClass(); /** * @return The associated enum type. */ public EnumType getEnumType(); /** * @return {@code true} if {@code null} is a valid mask value. */ public boolean isNullable(); /** * @return The empty mask. */ public M empty(); /** * @return The fill mask (at the time of call). */ public M full(); /** * @return The empty mask. */ public M create(); /** * Creates a mask with one value. * * @param value The value. * @return The mask that contains {@code value}. */ public M create(V value); /** * Creates a mask from an array. * * @param values The values (possibly including {@code null}). * @return The created mask. */ @SuppressWarnings("unchecked") public M create(V... values); /** * Creates a mask from an iterable. * * @param values The values (possibly including {@code null}). * @return The created mask. */ public M create(Iterable values); /** * Creates a mask from a predicate. * * @param predicate A predicate of values to include. * @return The created mask. */ public M create(Predicate predicate); /** * Creates a full or empty mask. *

* WARNING: Full is meaningful at the time of calling this constructor. * It may become false if values change after this call. * * @param enabled If {@code true}, creates a full mask. An empty mask otherwise. * @return The created mask. */ public M create(boolean enabled); } @FunctionalInterface protected static interface Creator { public M create(Support support, Set values); } /** * Creates a Support implementation. * * @param The mask type. * @param The enum type. * @param maskClass The mask class. * @param creator The mask factory. * @param enumType The enum type. * @param nullable {@code Nullable.TRUE} if  {@code null} is a valid value. * @return A Support implementation. */ protected static , V> Support support(Class maskClass, Creator creator, EnumType enumType, Nullable nullable) { Checks.isNotNull(maskClass, "maskClass"); Checks.isNotNull(creator, "creator"); Checks.isNotNull(enumType, "enumType"); Checks.isNotNull(nullable, "nullable"); return new SupportImpl<>(maskClass, creator, enumType, nullable); } /** * Creates a Support implementation for a standard enum. * * @param The mask type. * @param The enum type. * @param maskClass The mask class. * @param creator The mask factory. * @param enumClass The enum class. * @param nullable {@code Nullable.TRUE} if  {@code null} is a valid value. * @return A Support implementation. */ protected static , V extends Enum> Support support(Class maskClass, Creator creator, Class enumClass, Nullable nullable) { Checks.isNotNull(maskClass, "maskClass"); Checks.isNotNull(creator, "creator"); Checks.isNotNull(nullable, "nullable"); return support(maskClass, creator, StandardEnumTypes.getEnumType(enumClass), nullable); } private void checkValue(V value) { Checks.isTrue(value != null || isNullable(), "null is not supported"); } private void checkOther(M other) { Checks.isNotNull(other, "other"); Checks.isTrue(owner.isNullable() == other.owner.isNullable(), "Cannot mix masks with different nullable"); } private void checkValues() { Checks.isTrue(isNullable() || !values.contains((V) null), "null is not supported"); } /** * @return The enum type. */ public EnumType getEnumType() { return owner.getEnumType(); } /** * @return {@code true} if this mask is a nullable mask. */ public boolean isNullable() { return owner.isNullable(); } /** * @return {@code true} if this mask is empty. */ public boolean isEmpty() { return values.isEmpty(); } /** * * @return {@code true} if this mask is full. * The result may change if underlying enum type is dynamic. */ public boolean isFull() { if (values.size() != owner.getEnumType().getValues().size() + (isNullable() ? 1 : 0)) { return false; } else { return this.equals(full()); } } /** * @return The values. */ public Set getValues() { return values; } /** * Returns {@code true} if a value is contained. * * @param value The value. * @return {@code true} if {@code value} is contained in this mask. */ public boolean isSet(V value) { checkValue(value); return values.contains(value); } /** * Sets or clears a value. * * @param value The value. * @param enabled If {@code true} sets the value, otherwise clears it. * @return A new mask with {@code value} set or cleared. */ public M set(V value, boolean enabled) { checkValue(value); return enabled ? set(value) : clear(value); } /** * Returns a new mask augmented with a value. *

* If {@code value} is already set, returns this mask. * * @param value The value. * @return A new mask augmented with {@code value}. */ public M set(V value) { checkValue(value); if (isSet(value)) { return owner.getMaskClass().cast(this); } else { final Set tmp = new HashSet<>(); tmp.addAll(getValues()); tmp.add(value); return owner.create(tmp); } } /** * Returns a new mask reduced with a value. *

* If {@code value} is already cleared, returns this mask. * * @param value The value. * @return A new mask reduced with {@code value}. */ public M clear(V value) { checkValue(value); if (isSet(value)) { final Set tmp = new HashSet<>(); tmp.addAll(getValues()); tmp.remove(value); return owner.create(tmp); } else { return owner.getMaskClass().cast(this); } } /** * Returns an empty or full mask. *

* The full mask may change if underlying enum type is dynamic. * * @param enabled If {@code true}, creates a full mask. An empty mask otherwise. * @return An empty or full mask. */ public M setAll(boolean enabled) { return owner.create(enabled); } /** * @return An empty mask. */ public M empty() { return setAll(false); } /** * @return A full mask at the time of call. * Result may change if underlying enum type is dynamic. */ public M full() { return setAll(true); } /** * Returns the intersection of this mask with another one. * * @param other The other mask. * @return The intersection of this mask with {@code other}. */ public M and(M other) { checkOther(other); final Set tmp = new HashSet<>(); tmp.addAll(getValues()); tmp.retainAll(other.getValues()); return owner.create(tmp); } @SuppressWarnings("unchecked") public M and(V... values) { final M other = owner.create(values); return and(other); } /** * Returns the union of this mask with another one. * * @param other The other mask. * @return The union of this mask with {@code other}. */ public M or(M other) { checkOther(other); final Set tmp = new HashSet<>(); tmp.addAll(getValues()); tmp.addAll(other.getValues()); return owner.create(tmp); } @SuppressWarnings("unchecked") public M or(V... values) { final M other = owner.create(values); return or(other); } /** * Returns the complement of this mask (at the time of calling this method). * * @return The complement of this mask (at the time of calling this method). */ public M not() { final Set tmp = new HashSet<>(); tmp.addAll(owner.getEnumType().getValues()); if (owner.isNullable()) { tmp.add(null); } tmp.removeAll(getValues()); return owner.create(tmp); } /** * Returns {@code true} if all values of this mask are contained in another mask. * * @param other The other mask. * @return {@code true} if all values of this mask are contained in {@code other}. */ public final boolean contains(M other) { checkOther(other); return and(other).equals(other); } @SafeVarargs public final boolean contains(V... values) { final M other = owner.create(values); return contains(other); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(owner.getMaskClass().isInstance(other))) { return false; } final M o = owner.getMaskClass().cast(other); return owner.getEnumType().equals(o.owner.getEnumType()) && owner.isNullable() == o.owner.isNullable() && this.values.equals(o.values); } @Override public int hashCode() { return owner.getEnumType().hashCode() + (owner.isNullable() ? 1 : 0) + values.hashCode(); } /** * Returns a string representation of this mask. * * @param valueToString The function used to converter values (including {@code null}) to string. * @param separator The separator to use between values. * @return A string representation of this mask. */ public String toString(Function valueToString, String separator) { final StringBuilder builder = new StringBuilder(); boolean first = true; for (final V value : getEnumType().getValues()) { if (isSet(value)) { if (first) { first = false; } else { builder.append(separator); } builder.append(valueToString.apply(value)); } } if (isNullable() && isSet(null)) { if (!first) { builder.append(separator); } builder.append(valueToString.apply(null)); } return builder.toString(); } @Override public String toString() { return toString(owner.getEnumType()::getQName, "|"); } private static final class SupportImpl, V> implements Support { public final Class cls; private final Creator creator; private final EnumType enumType; private final Nullable nullable; private final M empty; public SupportImpl(Class cls, Creator creator, EnumType enumType, Nullable nullable) { this.cls = cls; this.creator = creator; this.enumType = enumType; this.nullable = nullable; this.empty = creator.create(this, Collections.emptySet()); } @Override public Class getMaskClass() { return cls; } @Override public EnumType getEnumType() { return enumType; } @Override public boolean isNullable() { return nullable == Nullable.TRUE; } @Override public M empty() { return empty; } @Override public M full() { return create(Predicates.alwaysTrue()); } @Override public M create() { return empty; } @Override public M create(V value) { return creator.create(this, CollectionsUtil.toUnmodifiableSet(value)); } @SuppressWarnings("unchecked") @Override public M create(V... values) { return creator.create(this, CollectionsUtil.toUnmodifiableSet(values)); } @Override public M create(Iterable values) { return creator.create(this, Iterables.toUnmodifiableSet(values)); } @Override public M create(Predicate predicate) { final Set values; if (nullable.isTrue()) { values = Iterables.toUnmodifiableSet(Iterables.join(enumType.getValues(), (V) null), predicate); } else { values = Iterables.toUnmodifiableSet(enumType.getValues(), predicate); } return creator.create(this, values); } @Override public M create(boolean enabled) { return create(enabled ? Predicates.alwaysTrue() : Predicates.alwaysFalse()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy