/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.util.guava;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableSet;
/** Like Guava Optional but permitting null and permitting errors to be thrown. */
public abstract class Maybe<T> implements Serializable, Supplier<T> {
private static final long serialVersionUID = -6372099069863179019L;
public static <T> Maybe<T> absent() {
return new Absent<T>();
}
/** Creates an absent whose get throws an {@link IllegalStateException} with the indicated message.
* Both stack traces (the cause and the callers) are provided, which can be quite handy. */
public static <T> Maybe<T> absent(final String message) {
return absent(new IllegalStateExceptionSupplier(message));
}
/** Creates an absent whose get throws an {@link IllegalStateException} with the indicated cause.
* Both stack traces (the cause and the callers) are provided, which can be quite handy. */
public static <T> Maybe<T> absent(final Throwable cause) {
return absent(new IllegalStateExceptionSupplier(cause));
}
/** Creates an absent whose get throws an {@link IllegalStateException} with the indicated message and underlying cause.
* Both stack traces (the cause and the callers) are provided, which can be quite handy. */
public static <T> Maybe<T> absent(final String message, final Throwable cause) {
return absent(new IllegalStateExceptionSupplier(message, cause));
}
/** Creates an absent whose get throws an {@link RuntimeException} generated on demand from the given supplier */
public static <T> Maybe<T> absent(final Supplier<? extends RuntimeException> exceptionSupplier) {
return new Absent<T>(Preconditions.checkNotNull(exceptionSupplier));
}
/** as {@link #absentNull(String)} but with a generic message */
public static <T> Maybe<T> absentNull() {
return absentNull("value is null");
}
/** like {@link #absent(String)} but {@link #isNull()} will return true on the result. */
public static <T> Maybe<T> absentNull(String message) {
return new AbsentNull<T>(message);
}
/** Creates a new Maybe object which is present.
* The argument may be null and the object still present,
* which may be confusing in some contexts
* (traditional {@link Optional} usages) but
* may be natural in others (where null is a valid value, distinguished from no value set).
* See also {@link #ofDisallowingNull(Object)}. */
public static <T> Maybe<T> ofAllowingNull(@Nullable T value) {
return new Present<T>(value);
}
/** Creates a new Maybe object which is present if and only if the argument is not null.
* If the argument is null, then an {@link #absentNull()} is returned,
* on which {@link #isNull()} will be true. */
public static <T> Maybe<T> ofDisallowingNull(@Nullable T value) {
if (value==null) return absentNull();
return new Present<T>(value);
}
/** Creates a new Maybe object.
* Currently this uses {@link #ofAllowingNull(Object)} semantics,
* but it is recommended to use that method for clarity
* if the argument might be null. */
// note: Optional throws if null is supplied; we might want to do the same here
public static <T> Maybe<T> of(@Nullable T value) {
return ofAllowingNull(value);
}
/** Creates a new Maybe object using {@link #ofDisallowingNull(Object)} semantics.
* It is recommended to use that method for clarity.
* This method is provided for consistency with {@link Optional#fromNullable(Object)}. */
public static <T> Maybe<T> fromNullable(@Nullable T value) {
return ofDisallowingNull(value);
}
/** creates an instance wrapping a {@link SoftReference}, so it might go absent later on.
* if null is supplied the result is a present null. */
public static <T> Maybe<T> soft(@Nonnull T value) {
return softThen(value, null);
}
/** creates an instance wrapping a {@link SoftReference}, using the second item given
* if the first argument is dereferenced.
* however if the first argument is null, this is a permanent present null,
* as {@link #of(Object)} with null. */
public static <T> Maybe<T> softThen(T value, Maybe<T> ifEmpty) {
if (value==null) return of((T)null);
return new SoftlyPresent<T>(value).usingAfterExpiry(ifEmpty);
}
public static <T> Maybe<T> of(final Optional<T> value) {
if (value.isPresent()) return new AbstractPresent<T>() {
private static final long serialVersionUID = -5735268814211401356L;
@Override
public T get() {
return value.get();
}
@Override
public boolean isNull() {
// should always be false as per Optional contract
return get()==null;
}
};
return absent();
}
public static <T> Maybe<T> of(final Supplier<T> value) {
return new AbstractPresent<T>() {
private static final long serialVersionUID = -5735268814211401356L;
@Override
public T get() {
return value.get();
}
@Override
public boolean isNull() {
return get()==null;
}
};
}
/** returns a Maybe containing the next element in the iterator, or absent if none */
public static <T> Maybe<T> next(Iterator<T> iterator) {
return iterator.hasNext() ? Maybe.of(iterator.next()) : Maybe.<T>absent();
}
public abstract boolean isPresent();
public abstract T get();
public boolean isAbsent() {
return !isPresent();
}
public boolean isAbsentOrNull() {
return isAbsent() || isNull();
}
public boolean isPresentAndNonNull() {
return isPresent() && !isNull();
}
/** Whether the value is null, if present, or
* if it was specified as absent because it was null,
* e.g. using {@link #fromNullable(Object)}.
*/
public abstract boolean isNull();
public T or(T nextValue) {
if (isPresent()) return get();
return nextValue;
}
public Maybe<T> or(Maybe<T> nextValue) {
if (isPresent()) return this;
return nextValue;
}
public T or(Supplier<T> nextValue) {
if (isPresent()) return get();
return nextValue.get();
}
public T orNull() {
if (isPresent()) return get();
return null;
}
public Set<T> asSet() {
if (isPresent()) return ImmutableSet.of(get());
return Collections.emptySet();
}
public <V> Maybe<V> transform(final Function<? super T, V> f) {
if (isPresent()) return new AbstractPresent<V>() {
private static final long serialVersionUID = 325089324325L;
public V get() {
return f.apply(Maybe.this.get());
}
@Override
public boolean isNull() {
return get()==null;
}
};
return absent();
}
/**
* Returns the value of each present instance from the supplied {@code maybes}, in order,
* skipping over occurrences of {@link Maybe#absent()}. Iterators are unmodifiable and are
* evaluated lazily.
*
* @see Optional#presentInstances(Iterable)
*/
@Beta
public static <T> Iterable<T> presentInstances(final Iterable<? extends Maybe<? extends T>> maybes) {
checkNotNull(maybes);
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new AbstractIterator<T>() {
private final Iterator<? extends Maybe<? extends T>> iterator = checkNotNull(maybes.iterator());
@Override
protected T computeNext() {
while (iterator.hasNext()) {
Maybe<? extends T> maybe = iterator.next();
if (maybe.isPresent()) { return maybe.get(); }
}
return endOfData();
}
};
}
};
}
public static class Absent<T> extends Maybe<T> {
private static final long serialVersionUID = -757170462010887057L;
private final Supplier<? extends RuntimeException> exception;
public Absent() {
this(IllegalStateExceptionSupplier.EMPTY_EXCEPTION);
}
public Absent(Supplier<? extends RuntimeException> exception) {
this.exception = exception;
}
@Override
public boolean isPresent() {
return false;
}
@Override
public boolean isNull() {
return false;
}
@Override
public T get() {
throw getException();
}
public RuntimeException getException() {
return exception.get();
}
}
public static class AbsentNull<T> extends Absent<T> {
private static final long serialVersionUID = 2422627709567857268L;
public AbsentNull(String message) {
super(new IllegalStateExceptionSupplier(message));
}
@Override
public boolean isNull() {
return true;
}
}
public static abstract class AbstractPresent<T> extends Maybe<T> {
private static final long serialVersionUID = -2266743425340870492L;
protected AbstractPresent() {
}
@Override
public boolean isPresent() {
return true;
}
}
public static class Present<T> extends AbstractPresent<T> {
private static final long serialVersionUID = 436799990500336015L;
private final T value;
protected Present(T value) {
this.value = value;
}
@Override
public T get() {
return value;
}
@Override
public boolean isNull() {
return value==null;
}
}
public static class SoftlyPresent<T> extends Maybe<T> {
private static final long serialVersionUID = 436799990500336015L;
private final SoftReference<T> value;
private Maybe<T> defaultValue;
protected SoftlyPresent(@Nonnull T value) {
this.value = new SoftReference<T>(value);
}
@Override
public T get() {
T result = value.get();
if (result!=null) return result;
if (defaultValue==null) throw new IllegalStateException("Softly present item has been GC'd");
return defaultValue.get();
}
@Override
public T orNull() {
T result = value.get();
if (result!=null) return result;
if (defaultValue==null) return null;
return defaultValue.orNull();
}
@Override
public boolean isPresent() {
return value.get()!=null || (defaultValue!=null && defaultValue.isPresent());
}
@Override
public boolean isNull() {
// null not allowed here
return false;
}
public Maybe<T> solidify() {
return Maybe.fromNullable(value.get());
}
SoftlyPresent<T> usingAfterExpiry(Maybe<T> defaultValue) {
this.defaultValue = defaultValue;
return this;
}
}
@Override
public String toString() {
return JavaClassNames.simpleClassName(this)+"["+(isPresent()?"value="+get():"")+"]";
}
@Override
public int hashCode() {
if (!isPresent()) return Objects.hashCode(31, isPresent());
return Objects.hashCode(31, get());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Maybe)) return false;
Maybe<?> other = (Maybe<?>)obj;
if (!isPresent())
return !other.isPresent();
return Objects.equal(get(), other.get());
}
}