From bae04bf1966d021bd57d5cfc827e8f2b1cd247ce Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 19 Sep 2014 07:46:42 +0200 Subject: [PATCH] Add sone change detector that finds new and removed elements. --- .../sone/core/SoneChangeDetector.java | 114 +++++++++++++++++++++ .../sone/core/SoneChangeDetectorTest.java | 108 +++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java create mode 100644 src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java diff --git a/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java b/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java new file mode 100644 index 0000000..efcdc6e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java @@ -0,0 +1,114 @@ +package net.pterodactylus.sone.core; + +import static com.google.common.base.Optional.absent; +import static com.google.common.base.Optional.fromNullable; +import static com.google.common.collect.FluentIterable.from; + +import java.util.Collection; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; + +/** + * Compares the contents of two {@link Sone}s and fires events for new and + * removed elements. + * + * @author David ‘Bombe’ Roden + */ +public class SoneChangeDetector { + + private final Sone oldSone; + private Optional newPostProcessor = absent(); + private Optional removedPostProcessor = absent(); + private Optional newPostReplyProcessor = absent(); + private Optional removedPostReplyProcessor = absent(); + + public SoneChangeDetector(Sone oldSone) { + this.oldSone = oldSone; + } + + public void onNewPosts(PostProcessor newPostProcessor) { + this.newPostProcessor = fromNullable(newPostProcessor); + } + + public void onRemovedPosts(PostProcessor removedPostProcessor) { + this.removedPostProcessor = fromNullable(removedPostProcessor); + } + + public void onNewPostReplies(PostReplyProcessor newPostReplyProcessor) { + this.newPostReplyProcessor = fromNullable(newPostReplyProcessor); + } + + public void onRemovedPostReplies( + PostReplyProcessor removedPostReplyProcessor) { + this.removedPostReplyProcessor = fromNullable(removedPostReplyProcessor); + } + + public void detectChanges(Sone newSone) { + processPosts(from(newSone.getPosts()).filter( + notContainedIn(oldSone.getPosts())), newPostProcessor); + processPosts(from(oldSone.getPosts()).filter( + notContainedIn(newSone.getPosts())), removedPostProcessor); + processPostReplies(from(newSone.getReplies()).filter( + notContainedIn(oldSone.getReplies())), newPostReplyProcessor); + processPostReplies(from(oldSone.getReplies()).filter( + notContainedIn(newSone.getReplies())), removedPostReplyProcessor); + } + + private void processPostReplies(FluentIterable postReplies, + Optional postReplyProcessor) { + for (PostReply postReply : postReplies) { + notifyPostReplyProcessor(postReplyProcessor, postReply); + } + } + + private void notifyPostReplyProcessor( + Optional postReplyProcessor, + PostReply postReply) { + if (postReplyProcessor.isPresent()) { + postReplyProcessor.get() + .processPostReply(postReply); + } + } + + private void processPosts(FluentIterable posts, + Optional newPostProcessor) { + for (Post post : posts) { + notifyPostProcessor(newPostProcessor, post); + } + } + + private void notifyPostProcessor(Optional postProcessor, + Post newPost) { + if (postProcessor.isPresent()) { + postProcessor.get().processPost(newPost); + } + } + + private Predicate notContainedIn(final Collection posts) { + return new Predicate() { + @Override + public boolean apply(T element) { + return !posts.contains(element); + } + }; + } + + public interface PostProcessor { + + void processPost(Post post); + + } + + public interface PostReplyProcessor { + + void processPostReply(PostReply postReply); + + } + +} diff --git a/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java b/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java new file mode 100644 index 0000000..f070eff --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java @@ -0,0 +1,108 @@ +package net.pterodactylus.sone.core; + +import static java.util.Arrays.asList; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashSet; + +import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor; +import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor; +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link SoneChangeDetector}. + * + * @author David ‘Bombe’ Roden + */ +public class SoneChangeDetectorTest { + + private final Sone oldSone = mock(Sone.class); + private final Sone newSone = mock(Sone.class); + private final SoneChangeDetector soneChangeDetector = + new SoneChangeDetector(oldSone); + private final Post oldPost = mock(Post.class); + private final Post removedPost = mock(Post.class); + private final Post newPost = mock(Post.class); + private final PostProcessor newPostProcessor = mock(PostProcessor.class); + private final PostProcessor removedPostProcessor = + mock(PostProcessor.class); + private final PostReply oldPostReply = mock(PostReply.class); + private final PostReply removedPostReply = mock(PostReply.class); + private final PostReply newPostReply = mock(PostReply.class); + private final PostReplyProcessor newPostReplyProcessor = + mock(PostReplyProcessor.class); + private final PostReplyProcessor removedPostReplyProcessor = + mock(PostReplyProcessor.class); + + @Before + public void setupPosts() { + when(oldSone.getPosts()).thenReturn(asList(oldPost, removedPost)); + when(newSone.getPosts()).thenReturn(asList(oldPost, newPost)); + } + + @Before + public void setupPostProcessors() { + soneChangeDetector.onNewPosts(newPostProcessor); + soneChangeDetector.onRemovedPosts(removedPostProcessor); + } + + @Before + public void setupPostReplies() { + when(oldSone.getReplies()).thenReturn( + new HashSet( + asList(oldPostReply, removedPostReply))); + when(newSone.getReplies()).thenReturn( + new HashSet(asList(oldPostReply, newPostReply))); + } + + @Before + public void setupPostReplyProcessors() { + soneChangeDetector.onNewPostReplies(newPostReplyProcessor); + soneChangeDetector.onRemovedPostReplies(removedPostReplyProcessor); + } + + @Test + public void changeDetectorDetectsChanges() { + soneChangeDetector.detectChanges(newSone); + + verify(newPostProcessor).processPost(newPost); + verify(newPostProcessor, never()).processPost(oldPost); + verify(newPostProcessor, never()).processPost(removedPost); + verify(removedPostProcessor).processPost(removedPost); + verify(removedPostProcessor, never()).processPost(oldPost); + verify(removedPostProcessor, never()).processPost(newPost); + + verify(newPostReplyProcessor).processPostReply(newPostReply); + verify(newPostReplyProcessor, never()).processPostReply(oldPostReply); + verify(newPostReplyProcessor, never()).processPostReply( + removedPostReply); + verify(removedPostReplyProcessor).processPostReply(removedPostReply); + verify(removedPostReplyProcessor, never()).processPostReply( + oldPostReply); + verify(removedPostReplyProcessor, never()).processPostReply( + newPostReply); + } + + @Test + public void changeDetectorDoesNotNotifyAnyProcessorIfProcessorsUnset() { + soneChangeDetector.onNewPosts(null); + soneChangeDetector.onRemovedPosts(null); + soneChangeDetector.onNewPostReplies(null); + soneChangeDetector.onRemovedPostReplies(null); + soneChangeDetector.detectChanges(newSone); + verify(newPostProcessor, never()).processPost(any(Post.class)); + verify(removedPostProcessor, never()).processPost(any(Post.class)); + verify(newPostReplyProcessor, never()).processPostReply(any(PostReply.class)); + verify(removedPostReplyProcessor, never()).processPostReply(any(PostReply.class)); + } + +} -- 2.7.4