From 7ef77629fae730e86df6b85d8fb4186f062deb00 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Sat, 10 Dec 2016 23:27:25 -0500 Subject: [PATCH] #4914 Add support for Notification for rx.Single --- src/main/java/rx/SingleNotification.java | 182 ++++++++++++++ src/test/java/rx/SingleNotificationTest.java | 241 +++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 src/main/java/rx/SingleNotification.java create mode 100644 src/test/java/rx/SingleNotificationTest.java diff --git a/src/main/java/rx/SingleNotification.java b/src/main/java/rx/SingleNotification.java new file mode 100644 index 0000000000..115ec027b9 --- /dev/null +++ b/src/main/java/rx/SingleNotification.java @@ -0,0 +1,182 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed 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 rx; + +/** + * An object representing a notification sent to a {@link Single}. + * @param the actual value type held by the SingleNotification + */ +public final class SingleNotification { + + private final Kind kind; + private final Throwable throwable; + private final T value; + + /** + * Creates and returns a {@code SingleNotification} of variety {@code Kind.OnSuccess}, and assigns it a value. + * + * @param the actual value type held by the SingleNotification + * @param t + * the item to assign to the notification as its value + * @return an {@code OnSuccess} variety of {@code SingleNotification} + */ + public static SingleNotification createOnSuccess(T t) { + return new SingleNotification(Kind.OnSuccess, t, null); + } + + /** + * Creates and returns a {@code SingleNotification} of variety {@code Kind.OnError}, and assigns it an exception. + * + * @param the actual value type held by the SingleNotification + * @param e + * the exception to assign to the notification + * @return an {@code OnError} variety of {@code SingleNotification} + */ + public static SingleNotification createOnError(Throwable e) { + return new SingleNotification(Kind.OnError, null, e); + } + + private SingleNotification(Kind kind, T value, Throwable e) { + this.value = value; + this.throwable = e; + this.kind = kind; + } + + /** + * Retrieves the exception associated with this (onError) notification. + * + * @return the Throwable associated with this (onError) notification + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Retrieves the item associated with this (onSuccess) notification. + * + * @return the item associated with this (onSuccess) notification + */ + public T getValue() { + return value; + } + + /** + * Indicates whether this notification has an item associated with it. + * + * @return a boolean indicating whether or not this notification has an item associated with it + */ + public boolean hasValue() { + return isOnSuccess() && value != null; + } + + /** + * Indicates whether this notification has an exception associated with it. + * + * @return a boolean indicating whether this notification has an exception associated with it + */ + public boolean hasThrowable() { + return isOnError() && throwable != null; + } + + /** + * Retrieves the kind of this notification: {@code OnSuccess}, or {@code OnError} + * + * @return the kind of the notification: {@code OnSuccess}, or {@code OnError} + */ + public Kind getKind() { + return kind; + } + + /** + * Indicates whether this notification represents an {@code onError} event. + * + * @return a boolean indicating whether this notification represents an {@code onError} event + */ + public boolean isOnError() { + return getKind() == Kind.OnError; + } + + /** + * Indicates whether this notification represents an {@code onSuccess} event. + * + * @return a boolean indicating whether this notification represents an {@code onSuccess} event + */ + public boolean isOnSuccess() { + return getKind() == Kind.OnSuccess; + } + + /** + * Forwards this notification on to a specified {@link SingleSubscriber}. + * @param singleSubscriber the target singleSubscriber to call onXXX methods on based on the kind of this SingleNotification instance + */ + public void accept(SingleSubscriber singleSubscriber) { + if (kind == Kind.OnSuccess) { + singleSubscriber.onSuccess(getValue()); + } else if (kind == Kind.OnError) { + singleSubscriber.onError(getThrowable()); + } + } + + /** + * Specifies the kind of the notification: an element or an error notification. + */ + public enum Kind { + OnSuccess, OnError + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder(64).append('[').append(super.toString()) + .append(' ').append(getKind()); + if (hasValue()) { + str.append(' ').append(getValue()); + } + if (hasThrowable()) { + str.append(' ').append(getThrowable().getMessage()); + } + str.append(']'); + return str.toString(); + } + + @Override + public int hashCode() { + int hash = getKind().hashCode(); + if (hasValue()) { + hash = hash * 31 + getValue().hashCode(); + } + if (hasThrowable()) { + hash = hash * 31 + getThrowable().hashCode(); + } + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (this == obj) { + return true; + } + + if (obj.getClass() != getClass()) { + return false; + } + SingleNotification singleNotification = (SingleNotification) obj; + return singleNotification.getKind() == getKind() && (value == singleNotification.value || (value != null && value.equals(singleNotification.value))) && (throwable == singleNotification.throwable || (throwable != null && throwable.equals(singleNotification.throwable))); + } +} \ No newline at end of file diff --git a/src/test/java/rx/SingleNotificationTest.java b/src/test/java/rx/SingleNotificationTest.java new file mode 100644 index 0000000000..20e596eb04 --- /dev/null +++ b/src/test/java/rx/SingleNotificationTest.java @@ -0,0 +1,241 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed 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 rx; + +import org.junit.Test; +import rx.exceptions.TestException; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SingleNotificationTest { + + @Test + public void testOnSuccessIntegerNotificationDoesNotEqualNullNotification() { + final SingleNotification integerNotification = SingleNotification.createOnSuccess(1); + final SingleNotification nullNotification = SingleNotification.createOnSuccess(null); + assertFalse(integerNotification.equals(nullNotification)); + } + + @Test + public void testOnSuccessNullNotificationDoesNotEqualIntegerNotification() { + final SingleNotification integerNotification = SingleNotification.createOnSuccess(1); + final SingleNotification nullNotification = SingleNotification.createOnSuccess(null); + assertFalse(nullNotification.equals(integerNotification)); + } + + @Test + public void testOnSuccessIntegerNotificationsWhenEqual() { + final SingleNotification integerNotification = SingleNotification.createOnSuccess(1); + final SingleNotification integerNotification2 = SingleNotification.createOnSuccess(1); + assertTrue(integerNotification.equals(integerNotification2)); + } + + @Test + public void testOnSuccessIntegerNotificationsWhenNotEqual() { + final SingleNotification integerNotification = SingleNotification.createOnSuccess(1); + final SingleNotification integerNotification2 = SingleNotification.createOnSuccess(2); + assertFalse(integerNotification.equals(integerNotification2)); + } + + @Test + public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { + final SingleNotification integerNotification = SingleNotification.createOnError(new Exception()); + final SingleNotification nullNotification = SingleNotification.createOnError(null); + assertFalse(integerNotification.equals(nullNotification)); + } + + @Test + public void testOnErrorNullNotificationDoesNotEqualIntegerNotification() { + final SingleNotification integerNotification = SingleNotification.createOnError(new Exception()); + final SingleNotification nullNotification = SingleNotification.createOnError(null); + assertFalse(nullNotification.equals(integerNotification)); + } + + @Test + public void testOnErrorIntegerNotificationsWhenEqual() { + final Exception exception = new Exception(); + final SingleNotification integerNotification = SingleNotification.createOnError(exception); + final SingleNotification integerNotification2 = SingleNotification.createOnError(exception); + assertTrue(integerNotification.equals(integerNotification2)); + } + + @Test + public void testOnErrorIntegerNotificationWhenNotEqual() { + final SingleNotification integerNotification = SingleNotification.createOnError(new Exception()); + final SingleNotification integerNotification2 = SingleNotification.createOnError(new Exception()); + assertFalse(integerNotification.equals(integerNotification2)); + } + + @Test + public void createWithClass() { + SingleNotification n = SingleNotification.createOnSuccess(1); + assertTrue(n.isOnSuccess()); + assertFalse(n.hasThrowable()); + assertTrue(n.hasValue()); + } + + @Test + public void accept() { + @SuppressWarnings("unchecked") + SingleSubscriber singleSubscriber = mock(SingleSubscriber.class); + + SingleNotification.createOnSuccess(1).accept(singleSubscriber); + SingleNotification.createOnError(new TestException()).accept(singleSubscriber); + + verify(singleSubscriber).onSuccess(1); + verify(singleSubscriber).onError(any(TestException.class)); + } + + /** Strip the @NNNNNN from the string. */ + static String stripAt(String s) { + int index = s.indexOf('@'); + if (index >= 0) { + int j = s.indexOf(' ', index); + if (j >= 0) { + return s.substring(0, index) + s.substring(j); + } + return s.substring(0, index); + } + return s; + } + + @Test + public void toStringVariants() { + assertEquals("[rx.SingleNotification OnSuccess 1]", stripAt(SingleNotification.createOnSuccess(1).toString())); + assertEquals("[rx.SingleNotification OnError Forced failure]", stripAt(SingleNotification.createOnError(new TestException("Forced failure")).toString())); + } + + @Test + public void hashCodeWorks() { + SingleNotification n1 = SingleNotification.createOnSuccess(1); + SingleNotification n1a = SingleNotification.createOnSuccess(1); + SingleNotification n2 = SingleNotification.createOnSuccess(2); + SingleNotification e1 = SingleNotification.createOnError(new TestException()); + + Set> set = new HashSet>(); + + set.add(n1); + set.add(n2); + set.add(e1); + + assertTrue(set.contains(n1)); + assertTrue(set.contains(n1a)); + assertTrue(set.contains(n2)); + assertTrue(set.contains(e1)); + } + + @Test + public void equalWorks() { + SingleNotification z1 = SingleNotification.createOnSuccess(null); + SingleNotification z1a = SingleNotification.createOnSuccess(null); + + SingleNotification n1 = SingleNotification.createOnSuccess(1); + SingleNotification n1a = SingleNotification.createOnSuccess(new Integer(1)); + SingleNotification n2 = SingleNotification.createOnSuccess(2); + SingleNotification e1 = SingleNotification.createOnError(new TestException()); + SingleNotification e2 = SingleNotification.createOnError(new TestException()); + + assertEquals(n1, n1a); + assertNotEquals(n1, n2); + assertNotEquals(n2, n1); + + assertNotEquals(n1, e1); + assertNotEquals(e1, n1); + + assertEquals(e1, e1); + assertNotEquals(e1, e2); + + assertFalse(n1.equals(null)); + assertFalse(n1.equals(1)); + + assertEquals(z1a, z1); + assertEquals(z1, z1a); + } + + @Test + public void contentChecks() { + SingleNotification z1 = SingleNotification.createOnSuccess(null); + SingleNotification n1 = SingleNotification.createOnSuccess(1); + SingleNotification e1 = SingleNotification.createOnError(new TestException()); + SingleNotification e2 = SingleNotification.createOnError(null); + + assertFalse(z1.hasValue()); + assertFalse(z1.hasThrowable()); + assertTrue(z1.isOnSuccess()); + + assertTrue(n1.hasValue()); + assertFalse(n1.hasThrowable()); + assertTrue(n1.isOnSuccess()); + + assertFalse(e1.hasValue()); + assertTrue(e1.hasThrowable()); + assertFalse(e1.isOnSuccess()); + + assertFalse(e2.hasValue()); + assertFalse(e2.hasThrowable()); + assertFalse(e2.isOnSuccess()); + } + + @Test + public void exceptionEquality() { + EqualException ex1 = new EqualException("1"); + EqualException ex2 = new EqualException("1"); + EqualException ex3 = new EqualException("3"); + + SingleNotification e1 = SingleNotification.createOnError(ex1); + SingleNotification e2 = SingleNotification.createOnError(ex2); + SingleNotification e3 = SingleNotification.createOnError(ex3); + + assertEquals(e1, e1); + assertEquals(e1, e2); + assertEquals(e2, e1); + assertEquals(e2, e2); + + assertNotEquals(e1, e3); + assertNotEquals(e2, e3); + assertNotEquals(e3, e1); + assertNotEquals(e3, e2); + } + + static final class EqualException extends RuntimeException { + + private static final long serialVersionUID = 446310455393317050L; + + public EqualException(String message) { + super(message); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof EqualException) { + return getMessage().equals(((EqualException) obj).getMessage()); + } + return false; + } + + @Override + public int hashCode() { + return getMessage().hashCode(); + } + } +} \ No newline at end of file