From ffeaf092f0381cf21d378115f8b97188d6eecdc6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Thu, 12 Jun 2014 07:09:08 +0200 Subject: [PATCH] Add Sone modification detector. --- .../sone/core/SoneModificationDetector.java | 77 +++++++++++++++++ .../sone/core/SoneModificationDetectorTest.java | 98 ++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java create mode 100644 src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java diff --git a/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java b/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java new file mode 100644 index 0000000..0a26e9e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java @@ -0,0 +1,77 @@ +package net.pterodactylus.sone.core; + +import static com.google.common.base.Optional.absent; +import static com.google.common.base.Optional.of; +import static com.google.common.base.Ticker.systemTicker; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.concurrent.atomic.AtomicInteger; + +import net.pterodactylus.sone.data.Sone; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; +import com.google.common.base.Ticker; + +/** + * Class that detects {@link Sone} modifications (as per their {@link + * Sone#getFingerprint() fingerprints} and determines when a modified Sone may + * be inserted. + * + * @author David ‘Bombe’ Roden + */ +class SoneModificationDetector { + + private final Ticker ticker; + private final Core core; + private final Sone sone; + private final AtomicInteger insertionDelay; + private Optional lastModificationTime; + private String originalFingerprint; + private String lastFingerprint; + + SoneModificationDetector(Core core, Sone sone, AtomicInteger insertionDelay) { + this(systemTicker(), core, sone, insertionDelay); + } + + @VisibleForTesting + SoneModificationDetector(Ticker ticker, Core core, Sone sone, AtomicInteger insertionDelay) { + this.ticker = ticker; + this.core = core; + this.sone = sone; + this.insertionDelay = insertionDelay; + originalFingerprint = sone.getFingerprint(); + lastFingerprint = originalFingerprint; + } + + public boolean isEligibleForInsert() { + if (core.isLocked(sone)) { + lastModificationTime = absent(); + lastFingerprint = ""; + return false; + } + String fingerprint = sone.getFingerprint(); + if (originalFingerprint.equals(fingerprint)) { + lastModificationTime = absent(); + lastFingerprint = fingerprint; + return false; + } + if (!lastFingerprint.equals(fingerprint)) { + lastModificationTime = of(ticker.read()); + lastFingerprint = fingerprint; + return false; + } + return insertionDelayHasPassed(); + } + + public void setFingerprint(String fingerprint) { + originalFingerprint = fingerprint; + lastFingerprint = originalFingerprint; + lastModificationTime = absent(); + } + + private boolean insertionDelayHasPassed() { + return lastModificationTime.isPresent() && (NANOSECONDS.toSeconds(ticker.read() - lastModificationTime.get()) >= insertionDelay.get()); + } + +} diff --git a/src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java b/src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java new file mode 100644 index 0000000..e85beb0 --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/core/SoneModificationDetectorTest.java @@ -0,0 +1,98 @@ +package net.pterodactylus.sone.core; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.concurrent.atomic.AtomicInteger; + +import net.pterodactylus.sone.data.Sone; + +import com.google.common.base.Ticker; +import org.junit.Test; + +/** + * Unit test for {@link SoneModificationDetector}. + * + * @author David ‘Bombe’ Roden + */ +public class SoneModificationDetectorTest { + + @Test + public void modifiedSoneIsEligibleAfter60Seconds() { + Ticker ticker = mock(Ticker.class); + Sone sone = mock(Sone.class); + when(sone.getFingerprint()).thenReturn("original"); + Core core = mock(Core.class); + SoneModificationDetector soneModificationDetector = new SoneModificationDetector(ticker, core, sone, new AtomicInteger(60)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(sone.getFingerprint()).thenReturn("modified"); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(ticker.read()).thenReturn(SECONDS.toNanos(100)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(true)); + } + + @Test + public void modifiedSoneIsNotEligibleAfter30Seconds() { + Ticker ticker = mock(Ticker.class); + Sone sone = mock(Sone.class); + when(sone.getFingerprint()).thenReturn("original"); + Core core = mock(Core.class); + SoneModificationDetector soneModificationDetector = new SoneModificationDetector(ticker, core, sone, new AtomicInteger(60)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(sone.getFingerprint()).thenReturn("modified"); + when(ticker.read()).thenReturn(SECONDS.toNanos(30)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + } + + @Test + public void lockedAndModifiedSoneIsNotEligibleAfter60Seconds() { + Ticker ticker = mock(Ticker.class); + Sone sone = mock(Sone.class); + when(sone.getFingerprint()).thenReturn("original"); + Core core = mock(Core.class); + when(core.isLocked(sone)).thenReturn(true); + SoneModificationDetector soneModificationDetector = new SoneModificationDetector(ticker, core, sone, new AtomicInteger(60)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(sone.getFingerprint()).thenReturn("modified"); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(ticker.read()).thenReturn(SECONDS.toNanos(100)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + } + + @Test + public void settingFingerprintWillResetTheEligibility() { + Ticker ticker = mock(Ticker.class); + Sone sone = mock(Sone.class); + when(sone.getFingerprint()).thenReturn("original"); + Core core = mock(Core.class); + SoneModificationDetector soneModificationDetector = new SoneModificationDetector(ticker, core, sone, new AtomicInteger(60)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(sone.getFingerprint()).thenReturn("modified"); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(ticker.read()).thenReturn(SECONDS.toNanos(100)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(true)); + soneModificationDetector.setFingerprint("modified"); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + } + + @Test + public void changingInsertionDelayWillInfluenceEligibility() { + Ticker ticker = mock(Ticker.class); + Sone sone = mock(Sone.class); + when(sone.getFingerprint()).thenReturn("original"); + Core core = mock(Core.class); + AtomicInteger insertionDelay = new AtomicInteger(60); + SoneModificationDetector soneModificationDetector = new SoneModificationDetector(ticker, core, sone, insertionDelay); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(sone.getFingerprint()).thenReturn("modified"); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + when(ticker.read()).thenReturn(SECONDS.toNanos(100)); + assertThat(soneModificationDetector.isEligibleForInsert(), is(true)); + insertionDelay.set(120); + assertThat(soneModificationDetector.isEligibleForInsert(), is(false)); + } + +} -- 2.7.4