--- /dev/null
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class SoneModificationDetector {
+
+ private final Ticker ticker;
+ private final Core core;
+ private final Sone sone;
+ private final AtomicInteger insertionDelay;
+ private Optional<Long> 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());
+ }
+
+}
--- /dev/null
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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));
+ }
+
+}