com.almworks.jira.structure.api.attribute.AttributeValue Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.attribute;
import com.almworks.jira.structure.api.error.StructureRuntimeException;
import com.atlassian.annotations.Internal;
import com.atlassian.annotations.PublicApi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.Immutable;
import java.util.function.Consumer;
/**
* Represents a value, or lack thereof. A defined value must be non-null. Similar to {@code Optional},
* but with additional values to represent special cases and an additional field to carry loader data.
*
* {@code AttributeValue} instances are immutable.
*
* Loader data
*
* Loader data is a field that attribute loaders can use to store additional information, which is not a part of
* the resulting value, but which can be used by some other loaders or by the same loader when calculating a multi-row value.
*
* For example, a loader that calculates the sum of all story points under the row without the row itself could store
* the own story points value in the loader data, because it will be needed when calculating the attribute value for the parent.
*
* Special values
*
* Special values are provided via the static methods and represent a few special situations. See {@link #undefined()}, {@link #absent()},
* {@link #inaccessible()} and {@link #error()}. Special values may also carry loader data.
*
* @param type of the value object
* @since 17.0
*/
@PublicApi
@Immutable
public abstract class AttributeValue {
private AttributeValue() {}
/**
* Creates an instance for the given value.
*
* @param value a value
* @param value type
* @return attribute value instance
*/
@NotNull
public static AttributeValue of(@NotNull T value) {
//noinspection ConstantConditions
assert value != null;
return ofNullable(value);
}
/**
* Creates an instance for the given value. If the value is {@code null}, returns an undefined value.
*
* @param value a value or null
* @param value type
* @return attribute value instance
*/
@NotNull
public static AttributeValue ofNullable(@Nullable T value) {
return value == null ? undefined() : new Defined<>(value);
}
/**
* Returns an undefined value.
*
* @param value type
* @return undefined value
*/
@SuppressWarnings("unchecked")
@NotNull
public static AttributeValue undefined() {
return (AttributeValue) Undefined.BARE_UNDEFINED;
}
/**
* Returns an "error" undefined value. It means there was an error when calculating the value. Specific loaders may put additional information
* about the error as loader data.
*
* @param value type
* @return error value
*/
@SuppressWarnings("unchecked")
@NotNull
public static AttributeValue error() {
return (AttributeValue) Undefined.BARE_ERROR;
}
/**
* Returns an "absent" undefined value, which is used as a placeholder for a value that is being calculated. This method should not be used
* outside the Attributes system.
*
* @param value type
* @return absent value
*/
@SuppressWarnings("unchecked")
@Internal
@NotNull
public static AttributeValue absent() {
return (AttributeValue) Undefined.BARE_ABSENT;
}
/**
* Returns an "inaccessible" undefined value, which means the row is not accessible to the user and the real value is not available.
* This method should not be used outside the Attributes system.
*
* @param value type
* @return inaccessible value
*/
@SuppressWarnings("unchecked")
@Internal
@NotNull
public static AttributeValue inaccessible() {
return (AttributeValue) Undefined.BARE_INACCESSIBLE;
}
/**
* Creates a new instance of {@code AttributeValue} that has the same value, but a new loader data.
*
* @param loaderData loader data
* @return a new attribute value
*/
@NotNull
public abstract AttributeValue withData(@Nullable Object loaderData);
/**
* Returns the value or {@code null} if this value is undefined.
*
* @return value
*/
@Nullable
public abstract T getValue();
/**
* Returns a defined value, or throws a runtime exception if the value is not defined.
*
* @return value
* @throws StructureRuntimeException if the value is undefined
*/
@NotNull
public final T getDefinedValue() {
T value = getValue();
if (value == null) {
throw new StructureRuntimeException("unexpected undefined value");
}
return value;
}
/**
* Supplies the value to the consumer, if the value is defined.
*
* @param consumer consumer of the value, will be supplied with a non-null reference
* @return this instance
*/
@NotNull
public abstract AttributeValue ifPresent(@NotNull Consumer super T> consumer);
/**
* Checks if the value is defined.
*
* @return {@code true} if the value is defined
*/
public abstract boolean isDefined();
/**
* Checks if this is a special "error" value.
*
* @return {@code true} if this is an error
*/
public abstract boolean isError();
/**
* Checks if this is a special "absent" value.
*
* @return {@code true} if this is an absent value
*/
public abstract boolean isAbsent();
/**
* Checks if this is a special "inaccessible" value.
*
* @return {@code true} if this is an inaccessible value
*/
public abstract boolean isInaccessible();
/**
* Returns the loader data if it matches the provided type.
*
* @param valueClass expected loader data class
* @param expected loader data type
* @return loader data or {@code null} if it is missing or of other type
*/
@Nullable
public D getLoaderData(Class valueClass) {
return null;
}
/**
* Transforms the value to a different type.
*
* @param spec attribute spec to take the type from
* @param the new type
* @return this instance
*/
@NotNull
@Internal
public AttributeValue cast(AttributeSpec spec) {
T value = getValue();
if (value != null) {
if (!spec.getFormat().getValueClass().isInstance(value)) {
assert false : this + " " + spec;
return null;
}
}
//noinspection unchecked
return (AttributeValue) this;
}
@Immutable
private static class Defined extends AttributeValue {
@NotNull
private final T myValue;
private Defined(@NotNull T value) {
myValue = value;
}
@NotNull
@Override
public AttributeValue withData(@Nullable Object loaderData) {
return loaderData == null ? this : new DefinedWithLoaderData<>(myValue, loaderData);
}
@Override
public boolean isDefined() {
return true;
}
public boolean isError() {
return false;
}
public boolean isAbsent() {
return false;
}
public boolean isInaccessible() {
return false;
}
@NotNull
@Override
public T getValue() {
return myValue;
}
@NotNull
@Override
public AttributeValue ifPresent(@NotNull Consumer super T> consumer) {
consumer.accept(myValue);
return this;
}
@Override
public String toString() {
return String.valueOf(myValue);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Defined> that = (Defined>) o;
return myValue.equals(that.myValue);
}
@Override
public int hashCode() {
return myValue.hashCode();
}
}
@Immutable
private static class DefinedWithLoaderData extends Defined {
@NotNull
private final Object myLoaderData;
public DefinedWithLoaderData(@NotNull T value, @NotNull Object loaderData) {
super(value);
myLoaderData = loaderData;
}
@Nullable
@Override
public D getLoaderData(Class valueClass) {
return valueClass.isInstance(myLoaderData) ? valueClass.cast(myLoaderData) : null;
}
@NotNull
@Override
public AttributeValue withData(@Nullable Object loaderData) {
T value = getValue();
return loaderData == null ? new Defined<>(value) : new DefinedWithLoaderData<>(value, loaderData);
}
@Override
public String toString() {
return super.toString() + " (+)";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
DefinedWithLoaderData> that = (DefinedWithLoaderData>) o;
return myLoaderData.equals(that.myLoaderData);
}
@Override
public int hashCode() {
return super.hashCode() * 31 + myLoaderData.hashCode();
}
}
@Immutable
private static class Undefined extends AttributeValue {
private static final AttributeValue BARE_UNDEFINED = new Undefined(UndefinedKind.UNDEFINED);
private static final AttributeValue BARE_ERROR = new Undefined(UndefinedKind.ERROR);
private static final AttributeValue BARE_ABSENT = new Undefined(UndefinedKind.ABSENT);
private static final AttributeValue BARE_INACCESSIBLE = new Undefined(UndefinedKind.INACCESSIBLE);
@NotNull
private final UndefinedKind myKind;
public Undefined(@NotNull UndefinedKind kind) {
myKind = kind;
}
@NotNull
@Override
public AttributeValue withData(@Nullable Object loaderData) {
return loaderData == null ? this : new UndefinedWithLoaderData<>(myKind, loaderData);
}
@Nullable
@Override
public T getValue() {
return null;
}
@NotNull
@Override
public AttributeValue ifPresent(@NotNull Consumer super T> consumer) {
return this;
}
@Override
public boolean isDefined() {
return false;
}
@Override
public boolean isError() {
return myKind == UndefinedKind.ERROR;
}
@Override
public boolean isAbsent() {
return myKind == UndefinedKind.ABSENT;
}
@Override
public boolean isInaccessible() {
return myKind == UndefinedKind.INACCESSIBLE;
}
@NotNull
public UndefinedKind getKind() {
return myKind;
}
@Override
public String toString() {
switch (myKind) {
case ERROR: return "";
case ABSENT: return "<->";
case INACCESSIBLE: return "<#>";
default: return ">";
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Undefined> that = (Undefined>) o;
return myKind == that.myKind;
}
@Override
public int hashCode() {
return myKind.hashCode();
}
}
@Immutable
private static class UndefinedWithLoaderData extends Undefined {
@NotNull
private final Object myLoaderData;
public UndefinedWithLoaderData(@NotNull UndefinedKind kind, @NotNull Object loaderData) {
super(kind);
myLoaderData = loaderData;
}
@Nullable
@Override
public D getLoaderData(Class valueClass) {
return valueClass.isInstance(myLoaderData) ? valueClass.cast(myLoaderData) : null;
}
@NotNull
@Override
public AttributeValue withData(@Nullable Object loaderData) {
UndefinedKind kind = getKind();
return loaderData == null ? new Undefined<>(kind) : new UndefinedWithLoaderData<>(kind, loaderData);
}
@Override
public String toString() {
return super.toString() + " (+)";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
UndefinedWithLoaderData> that = (UndefinedWithLoaderData>) o;
return myLoaderData.equals(that.myLoaderData);
}
@Override
public int hashCode() {
return super.hashCode() * 31 + myLoaderData.hashCode();
}
}
private enum UndefinedKind {
UNDEFINED,
ERROR,
ABSENT,
INACCESSIBLE
}
}