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

org.apache.brooklyn.util.guava.Maybe Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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 org.apache.brooklyn.util.javalang.MemoryUsageTracker.SoftUsageTracker;

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 implements Serializable, Supplier {

    private static final long serialVersionUID = -6372099069863179019L;

    /** Returns an absent indicator. No message is available and access does not include any reference to this creation.
     * Therefore it is fast and simple, but hard to work with if someone might {@link #get()} it and want a useful exception.
     * See also {@link #absentNoTrace(String)} to include a message with very low overhead,
     * or {@link #absentWithTrace(String)} or {@link #absent(Throwable)} for more control over the exception thrown. 
     */
    public static  Maybe absent() {
        return new Maybe.Absent();
    }

    /** Convenience for {@link #absentWithTrace(String)}. */
    public static  Maybe absent(final String message) {
        return absent(new IllegalStateException(message));
    }

    /** Creates an absent whose {@link #get()} throws an {@link IllegalStateException} with the indicated message.
     * Both stack traces (the cause and the callers) are provided, which can be quite handy,
     * but comparatively expensive as the cause stack trace has to generated when this method is invoked,
     * even if it is never accessed. See also {@link #absentNoTrace(String)} and {@link #absent(Throwable)}. */
    public static  Maybe absentWithTrace(final String message) {
        return absent(new IllegalStateException(message));
    }

    /** Creates an absent whose get throws an {@link IllegalStateException} with the indicated message,
     * but not a stack trace for the calling location. As stack traces can be comparatively expensive
     * this is useful for efficiency, but it can make debugging harder as the origin of the absence is not kept,
     * in contrast to {@link #absentWithTrace(String)} and {@link #absent(Throwable)}. */
    public static  Maybe absentNoTrace(final String message) {
        return absent(new IllegalStateExceptionSupplier(message));
    }

    /** As {@link #absentWithTrace(String)} but using the provided exception instead of this location
     * as the cause, and a string based on this cause as the message on the {@link IllegalStateException}
     * thrown if a user does a {@link #get()}. 
     * Useful if an {@link Exception} has already been generated (and no overhead)
     * or if you want to supply a specific cause as in absent(new MyException(...))
     * (but there is the Exception creation overhead there). */
    public static  Maybe absent(final Throwable cause) {
        return absent(new IllegalStateExceptionSupplier(cause));
    }
    
    /** As {@link #absent(Throwable)} but using the given message as the message on the {@link IllegalStateException}
     * thrown if a user does a {@link #get()}. */
    public static  Maybe absent(final String message, final Throwable cause) {
        return absent(new IllegalStateExceptionSupplier(message, cause));
    }
    
    /** Creates an absent whose {@link #get()} throws a {@link RuntimeException} 
     * generated on demand from the given supplier */
    public static  Maybe absent(final Supplier exceptionSupplier) {
        return new Absent(Preconditions.checkNotNull(exceptionSupplier));
    }

    /** as {@link #absentNull(String)} but with a generic message */
    public static  Maybe absentNull() {
        return absentNull("disallowed null value");
    }
    
    /** like {@link #absent(String)} but {@link #isNull()} will return true on the result. */
    public static  Maybe absentNull(String message) {
        return new AbsentNull(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  Maybe ofAllowingNull(@Nullable T value) {
        return new Present(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  Maybe ofDisallowingNull(@Nullable T value) {
        if (value==null) return absentNull();
        return new Present(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  Maybe of(@Nullable T value) {
        return ofAllowingNull(value);
    }
    
    /**
     * Casts the given value to the desired type. This is valid because {@link Maybe} is immutable,
     * so things like {@code Maybe} is a super-type of {@code Maybe}.
     */
    @SuppressWarnings("unchecked")
    public static  Maybe cast(Maybe value) {
        return (Maybe) value;
    }
    
    /** Converts the given {@link Maybe} to {@link Optional}, failing if this {@link Maybe} contains null. */
    public Optional toOptional() {
        if (isPresent()) return Optional.of(get());
        return Optional.absent();
    }

    /** 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  Maybe fromNullable(@Nullable T value) {
        return ofDisallowingNull(value);
    }
    
    /** Creates a new Maybe object out of the {@link Optional} argument */
    public static  Maybe fromOptional(Optional value) {
        return Maybe.fromNullable(value.orNull());
    }
    
    /** 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  Maybe 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  Maybe softThen(T value, Maybe ifEmpty) {
        if (value==null) return of((T)null);
        return new SoftlyPresent(value).usingAfterExpiry(ifEmpty);
    }

    public static  Maybe of(final Optional value) {
        if (value.isPresent()) return new AbstractPresent() {
            private static final long serialVersionUID = -5735268814211401356L;
            @Override
            public T get() {
                return value.get();
            }
        };
        return absent();
    }
    
    @SuppressWarnings("unused")
    private static  Maybe ofOldKeptForDeserializationOfAnonymousInnerClass(final Supplier value) {
        return new AbstractPresent() {
            private static final long serialVersionUID = -5735268814211401356L;
            @Override
            public T get() {
                return value.get();
            }
        };
    }
    
    public static  Maybe of(final Supplier value) {
        return new MaybeSupplier(value);
    }
    
    public static class MaybeSupplier extends AbstractPresent {
        private static final long serialVersionUID = -823731500051341455L;
        private final Supplier supplier;
        public MaybeSupplier(Supplier value) {
            this.supplier = value;
        }
        @Override
        public T get() {
            return supplier.get();
        }
        public Supplier getSupplier() {
            return supplier;
        }
    }
    
    /** returns a Maybe containing the next element in the iterator, or absent if none */ 
    public static  Maybe next(Iterator iterator) {
        return iterator.hasNext() ? Maybe.of(iterator.next()) : Maybe.absent();
    }

    public abstract boolean isPresent();
    @Override
    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 or(Maybe nextValue) {
        if (isPresent()) return this;
        return nextValue;
    }

    public T or(Supplier nextValue) {
        if (isPresent()) return get();
        return nextValue.get();
    }

    public T orNull() {
        if (isPresent()) return get();
        return null;
    }

    /** As {@link #get()} but if the Maybe wraps an exception 
     * (and where {@link #get()} throws a {@link RuntimeException} indicating the caller,
     * caused by the exception in original processing)
     * this throws the original unwrapped exception
     * (masking the caller's location)
     * 

* As this masks the caller's exception, use with care, typically in a location * near the original execution, otherwise it can get confusing. * For instance someCallReturningMaybe().orThrowUnwrapped() is a nice idiom, * but someMaybeVarReturnedEarlier.orThrowUnwrapped() is usually a bad idea. *

* The benefit of this is simpler stack traces and preservation of original exception type. */ public T orThrowUnwrapped() { return get(); } public Set asSet() { if (isPresent()) return ImmutableSet.of(get()); return Collections.emptySet(); } public Maybe transform(final Function f) { if (isPresent()) return new AbstractPresent() { private static final long serialVersionUID = 325089324325L; @Override public V get() { return f.apply(Maybe.this.get()); } }; 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 Iterable presentInstances(final Iterable> maybes) { checkNotNull(maybes); return new Iterable() { @Override public Iterator iterator() { return new AbstractIterator() { private final Iterator> iterator = checkNotNull(maybes.iterator()); @Override protected T computeNext() { while (iterator.hasNext()) { Maybe maybe = iterator.next(); if (maybe.isPresent()) { return maybe.get(); } } return endOfData(); } }; } }; } public static class Absent extends Maybe { private static final long serialVersionUID = -757170462010887057L; private final Supplier exception; public Absent() { this(IllegalStateExceptionSupplier.EMPTY_EXCEPTION); } public Absent(Supplier exception) { this.exception = exception; } @Override public boolean isPresent() { return false; } @Override public boolean isNull() { return false; } @Override public T get() { throw getException(); } @Override public T orThrowUnwrapped() { throw getException(); } public RuntimeException getException() { return exception.get(); } public Supplier getExceptionSupplier() { return exception; } public static Maybe changeExceptionSupplier(Maybe original, final Class type) { return changeExceptionSupplier(original, new Function,Supplier>() { @SuppressWarnings("unchecked") @Override public Supplier apply(AnyExceptionSupplier input) { if (type.isInstance(input)) return (Supplier) input; return new AnyExceptionSupplier(type, input.getMessageSupplier(), input.getCause()); } }); } public static Maybe changeExceptionSupplier(Maybe original, Function,Supplier> transform) { if (original==null || original.isPresent()) return original; final Supplier supplier = ((Maybe.Absent)original).getExceptionSupplier(); if (!(supplier instanceof AnyExceptionSupplier)) return original; return Maybe.absent(transform.apply((AnyExceptionSupplier)supplier)); } /** Like {@link #cast(Maybe)} but allows any casting because that is valid for absents. * Enforces that the argument really is absent. */ @SuppressWarnings("unchecked") public static Maybe castAbsent(Maybe absent) { if (absent!=null && absent.isPresent()) { throw new IllegalArgumentException("Expected an absent, but instead got: "+absent); } return (Maybe)absent; } } public static class AbsentNull extends Absent { 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 extends Maybe { private static final long serialVersionUID = -2266743425340870492L; protected AbstractPresent() { } @Override public boolean isNull() { return get()==null; } @Override public boolean isPresent() { return true; } } public static class Present extends AbstractPresent { private static final long serialVersionUID = 436799990500336015L; private final T value; protected Present(T value) { this.value = value; } @Override public T get() { return value; } } public static class SoftlyPresent extends Maybe { private static final SoftUsageTracker TRACKER = new SoftUsageTracker(); private static final long serialVersionUID = 436799990500336015L; private final SoftReference value; private Maybe defaultValue; protected SoftlyPresent(@Nonnull T value) { this.value = new SoftReference(value); TRACKER.track(this, this.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 solidify() { return Maybe.fromNullable(value.get()); } SoftlyPresent usingAfterExpiry(Maybe defaultValue) { this.defaultValue = defaultValue; return this; } /** Returns the global usage tracker to determine how many references are soft */ public static SoftUsageTracker getUsageTracker() { return TRACKER; } } @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()); } /** Two {@link Maybe} instances are equal if both present wrapping the same value, * or if both are absent for any reason. *

* Specifically, in cases of absences, the reasons for absence are not compared. * This could be revisited if there is compelling reason to do so, but in the main * the cause of an absence is interesting for giving information to the user. * Note this is different to the behaviour of {@link Optional} which says absences * are only equal if they are the same instance. */ @Override public boolean equals(Object obj) { if (!(obj instanceof Maybe)) return false; Maybe other = (Maybe)obj; if (!isPresent()) { if (other.isPresent()) return false; // could compare exceptions; see javadoc return true; } if (!other.isPresent()) { return false; } return Objects.equal(get(), other.get()); } /** Finds the {@link Absent#getException()} if {@link #isAbsent()}, or null */ public static RuntimeException getException(Maybe t) { return t instanceof Maybe.Absent ? ((Maybe.Absent)t).getException() : null; } }