Maybe offers more clarity around whether it creates an exception on Absent and has a toOptional, and tests
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/785342a4 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/785342a4 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/785342a4 Branch: refs/heads/master Commit: 785342a4aebe0af47fac1e44205f85c28d320f79 Parents: 1f973df Author: Alex Heneveld <[email protected]> Authored: Fri Jun 24 22:48:10 2016 +0100 Committer: Alex Heneveld <[email protected]> Committed: Fri Jun 24 22:52:15 2016 +0100 ---------------------------------------------------------------------- .../org/apache/brooklyn/util/guava/Maybe.java | 47 ++++- .../apache/brooklyn/util/guava/MaybeTest.java | 178 +++++++++++++++++++ 2 files changed, 217 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/785342a4/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java b/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java index f64d712..73c9761 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java @@ -45,29 +45,54 @@ public abstract class Maybe<T> implements Serializable, Supplier<T> { 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 <T> Maybe<T> absent() { - return new Absent<T>(); + return new Maybe.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. */ + /** Convenience for {@link #absentWithTrace(String)}. */ public static <T> Maybe<T> 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 <T> Maybe<T> 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 <T> Maybe<T> absentNoTrace(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. */ + /** 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 <code>absent(new MyException(...))</code> + * (but there is the Exception creation overhead there). */ 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. */ + /** 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 <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 */ + /** Creates an absent whose {@link #get()} throws a {@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)); } @@ -108,6 +133,12 @@ public abstract class Maybe<T> implements Serializable, Supplier<T> { public static <T> Maybe<T> of(@Nullable T value) { return ofAllowingNull(value); } + + /** Converts the given {@link Maybe} to {@link Optional}, failing if this {@link Maybe} contains null. */ + public Optional<T> 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. http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/785342a4/utils/common/src/test/java/org/apache/brooklyn/util/guava/MaybeTest.java ---------------------------------------------------------------------- diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/guava/MaybeTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/guava/MaybeTest.java new file mode 100644 index 0000000..5870303 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/guava/MaybeTest.java @@ -0,0 +1,178 @@ +/* + * 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 org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.exceptions.UserFacingException; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.base.Optional; + +public class MaybeTest { + + @Test + public void testMaybeGet() { + Maybe<String> m = Maybe.of("yes"); + Assert.assertTrue(m.isPresent()); + Assert.assertEquals(m.get(), "yes"); + } + + @Test + public void testMaybeToOptional() { + Maybe<String> m = Maybe.of("yes"); + Optional<String> o = m.toOptional(); + Assert.assertTrue(o.isPresent()); + Assert.assertEquals(o.get(), "yes"); + } + + @Test + public void testMaybeGetNull() { + Maybe<String> m = Maybe.ofAllowingNull(null); + Assert.assertTrue(m.isPresent()); + Assert.assertEquals(m.get(), null); + } + + @Test + public void testMaybeDefaultAllowsNull() { + // but note you have to cast it if you try to do it explicitly + // (of course normally it's a variable...) + Maybe<Object> m = Maybe.of((Object)null); + Assert.assertTrue(m.isPresent()); + Assert.assertEquals(m.get(), null); + } + + @Test + public void testMaybeDisallowingNull() { + Maybe<Object> m = Maybe.ofDisallowingNull(null); + try { + m.get(); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "null"); + } + } + + @Test + public void testMaybeToOptionalFailsOnNull() { + Maybe<String> m = Maybe.ofAllowingNull(null); + try { + m.toOptional(); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureOfType(e, NullPointerException.class); + } + } + + @Test + public void testMaybeAbsent() { + Maybe<String> m = Maybe.absent("nope"); + Assert.assertFalse(m.isPresent()); + assertGetFailsContaining(m, "nope"); + } + + protected Exception assertGetFailsContaining(Maybe<?> m, String phrase) { + try { + getInExplicitMethod(m); + throw Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, phrase); + return e; + } + } + + @Test + public void testMaybeAbsentToOptional() { + Maybe<String> m = Maybe.absent("nope"); + Optional<String> o = m.toOptional(); + Assert.assertFalse(o.isPresent()); + try { + o.get(); + throw Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "absent"); + } + } + + // --- now check traces ---- + + /** an explicit method we can search for in the trace */ + protected <T> T getInExplicitMethod(Maybe<T> m) { + return m.get(); + } + + protected boolean containsClassAndMethod(StackTraceElement[] stackTrace, String className, String methodName) { + boolean methodFound = (methodName==null); + boolean classFound = (className==null); + for (StackTraceElement element: stackTrace) { + if (className!=null && className.equals(element.getClassName())) classFound = true; + if (methodName!=null && methodName.equals(element.getMethodName())) methodFound = true; + } + return methodFound && classFound; + } + protected boolean containsGetExplicitMethod(Throwable e) { + return containsClassAndMethod(e.getStackTrace(), MaybeTest.class.getName(), "getInExplicitMethod"); + } + protected boolean containsMaybeMethod(Throwable e, String methodName) { + return containsClassAndMethod(e.getStackTrace(), Maybe.class.getName(), methodName); + } + + @Test + public void testMaybeAbsentMessageIncludesSource() { + Maybe<Object> m = Maybe.absent("nope"); + Assert.assertFalse(m.isPresent()); + Exception e = assertGetFailsContaining(m, "nope"); + + e.printStackTrace(); + Assert.assertTrue(containsGetExplicitMethod(e), "Outer trace should be in explicit method"); + Assert.assertFalse(containsMaybeMethod(e, "absent"), "Outer trace should not be from 'absent'"); + + Assert.assertFalse(containsGetExplicitMethod(e.getCause()), "Inner trace should not be in explicit method"); + Assert.assertTrue(containsMaybeMethod(e.getCause(), "absent"), "Inner trace should be from 'absent'"); + } + + @Test + public void testMaybeAbsentMessageNoTraceDoesNotIncludeSource() { + Maybe<Object> m = Maybe.absentNoTrace("nope"); + Assert.assertFalse(m.isPresent()); + Exception e = assertGetFailsContaining(m, "nope"); + Assert.assertTrue(containsGetExplicitMethod(e), "Outer trace should be in explicit method"); + Assert.assertFalse(containsMaybeMethod(e, "absentNoTrace"), "Outer trace should not be from 'absentNoTrace'"); + Assert.assertNull(e.getCause()); + } + + protected Exception newRE(String message) { return new UserFacingException(message); } + + @Test + public void testMaybeAbsentThrowableIncludesGivenSourceOnly() { + Maybe<Object> m = Maybe.absent(newRE("nope")); + Assert.assertFalse(m.isPresent()); + Exception e = assertGetFailsContaining(m, "nope"); + Assert.assertTrue(e instanceof IllegalStateException, "Outer should be ISE, not "+e); + Assert.assertTrue(containsGetExplicitMethod(e), "Outer trace should be in explicit method"); + Assert.assertFalse(containsMaybeMethod(e, "absent"), "Outer trace should not be from 'absent'"); + Assert.assertFalse(containsMaybeMethod(e, "newRE"), "Outer trace should not be 'newRE'"); + + Assert.assertTrue(e.getCause() instanceof UserFacingException, "Inner should be UFE, not "+e.getCause()); + Assert.assertFalse(containsGetExplicitMethod(e.getCause()), "Inner trace should not be in explicit method"); + Assert.assertFalse(containsMaybeMethod(e.getCause(), "absent"), "Inner trace should not be from 'absent'"); + Assert.assertTrue(containsClassAndMethod(e.getCause().getStackTrace(), MaybeTest.class.getName(), "newRE"), "Inner trace SHOULD be from 'newRE'"); + } + +}
