/**
   * Appends the given deltas to the deltas already stored. Updates the latest snapshot and latest
   * version as well. This method will make a copy of the snapshot.
   *
   * @param updatedSnapshot the snapshot after deltas have been applied
   * @param newDeltas the deltas that have been applied since the last call to appendDeltas.
   */
  public void appendDeltas(ReadableWaveletData updatedSnapshot, DeltaSequence newDeltas) {
    HashedVersion newEndVersion = newDeltas.getEndVersion();
    Preconditions.checkArgument(
        !newDeltas.isEmpty(), "There were no new deltas passed to appendDeltas");
    Preconditions.checkArgument(
        updatedSnapshot.getVersion() == newEndVersion.getVersion(),
        String.format(
            "Version of snapshot %s doesn't match the HashedVersion %s",
            updatedSnapshot.getVersion(), newEndVersion));
    Preconditions.checkArgument(
        areContiguousToCurrentVersion(newDeltas),
        String.format(
            "Deltas are not contiguous to the current version(%s) %s",
            getVersionAfterDeltas(), deltas));
    WaveletName updatedWaveletName = WaveletDataUtil.waveletNameOf(updatedSnapshot);
    Preconditions.checkArgument(
        updatedWaveletName.equals(waveletName),
        String.format(
            "Updated wavelet doesn't have the same name as with which this class has been "
                + "instantiated. %s != %s",
            updatedWaveletName, waveletName));

    // TODO(ljvderijk): This should actually be applying the deltas, however
    // they do not contain a timestamp at this time.
    snapshotAfterDeltas = WaveletDataUtil.copyWavelet(updatedSnapshot);
    deltas = DeltaSequence.join(deltas, newDeltas);
  }
/** @author [email protected] (Soren Lassen) */
public class WaveletDataUtilTest extends TestCase {
  public static final ParticipantId CREATOR = new ParticipantId("*****@*****.**");
  public static final ParticipantId JOE = new ParticipantId("*****@*****.**");
  public static final WaveletName WAVELET_NAME =
      WaveletName.of(WaveId.of("example.com", "w+wave"), WaveletId.of("example.com", "wavelet"));

  private WaveletOperationContext opContext(long timestamp, HashedVersion version) {
    return new WaveletOperationContext(CREATOR, timestamp, 1L, version);
  }

  private WaveletOperation addParticipant(ParticipantId user, long time, HashedVersion version) {
    return new AddParticipant(opContext(time, version), user);
  }

  private WaveletOperation addBlip(String id, long time, HashedVersion version) {
    return new WaveletBlipOperation(
        id, new BlipContentOperation(opContext(time, version), new DocOpBuilder().build()));
  }

  private TransformedWaveletDelta delta(WaveletOperation... ops) {
    WaveletOperation last = ops[ops.length - 1];
    WaveletOperationContext ctx = last.getContext();
    return new TransformedWaveletDelta(
        ctx.getCreator(), ctx.getHashedVersion(), ctx.getTimestamp(), Arrays.asList(ops));
  }

  private ObservableWaveletData build(TransformedWaveletDelta... deltas) throws OperationException {
    return WaveletDataUtil.buildWaveletFromDeltas(WAVELET_NAME, Arrays.asList(deltas).iterator());
  }

  public void testBuildWaveletFromOneDelta() throws Exception {
    WaveletData wavelet = build(delta(addParticipant(CREATOR, 1093L, HashedVersion.unsigned(1))));
    assertEquals(WAVELET_NAME, WaveletDataUtil.waveletNameOf(wavelet));
    assertEquals(CREATOR, wavelet.getCreator());
    assertEquals(1093L, wavelet.getCreationTime());
    assertEquals(1093L, wavelet.getLastModifiedTime());
    assertEquals(HashedVersion.unsigned(1), wavelet.getHashedVersion());
    assertEquals(ImmutableSet.of(), wavelet.getDocumentIds());
    assertEquals(ImmutableSet.of(CREATOR), wavelet.getParticipants());
  }

  public void testBuildWaveletFromThreeDeltas() throws Exception {
    WaveletData wavelet =
        build(
            delta(addParticipant(CREATOR, 1093L, HashedVersion.unsigned(1))),
            delta(addParticipant(JOE, 1492L, HashedVersion.unsigned(2))),
            delta(addBlip("blipid", 2010L, HashedVersion.unsigned(3))));
    assertEquals(WAVELET_NAME, WaveletDataUtil.waveletNameOf(wavelet));
    assertEquals(CREATOR, wavelet.getCreator());
    assertEquals(1093L, wavelet.getCreationTime());
    assertEquals(2010L, wavelet.getLastModifiedTime());
    assertEquals(HashedVersion.unsigned(3), wavelet.getHashedVersion());
    assertEquals(ImmutableSet.of("blipid"), wavelet.getDocumentIds());
    assertEquals(ImmutableSet.of(CREATOR, JOE), wavelet.getParticipants());
  }
}
 private <T extends WaveletContainer> T getWavelet(
     WaveletId waveletId, ConcurrentMap<WaveletId, T> waveletsMap) throws WaveletStateException {
   ImmutableSet<WaveletId> storedWavelets;
   try {
     storedWavelets =
         FutureUtil.getResultOrPropagateException(lookedupWavelets, PersistenceException.class);
   } catch (PersistenceException e) {
     throw new WaveletStateException(
         "Failed to lookup wavelet " + WaveletName.of(waveId, waveletId), e);
   } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
     throw new WaveletStateException(
         "Interrupted looking up wavelet " + WaveletName.of(waveId, waveletId), e);
   }
   // Since waveletsMap is a computing map, we must call containsKey(waveletId)
   // to tell if waveletId is mapped, we cannot test if get(waveletId) returns null.
   if (!storedWavelets.contains(waveletId) && !waveletsMap.containsKey(waveletId)) {
     return null;
   } else {
     T wavelet = waveletsMap.get(waveletId);
     Preconditions.checkNotNull(wavelet, "computingMap returned null");
     return wavelet;
   }
 }
 @Override
 public T apply(WaveletId waveletId) {
   return factory.create(notifiee, WaveletName.of(waveId, waveletId), waveDomain);
 }
 private WaveletName getFakeWaveletName(String domain) {
   return WaveletName.of(WaveId.of(domain, "wave"), WaveletId.of(domain, "wavelet"));
 }
/**
 * Tests for {@link WaveletState} implementations.
 *
 * @author [email protected] (Alex North)
 */
public abstract class WaveletStateTestBase extends TestCase {

  private static final WaveletName NAME =
      WaveletName.of(WaveId.of("example.com", "waveid"), WaveletId.of("example.com", "waveletid"));
  private static final ParticipantId AUTHOR = ParticipantId.ofUnsafe("*****@*****.**");
  private static final DeltaTestUtil UTIL = new DeltaTestUtil(AUTHOR);
  private static final long TS = 1234567890L;
  private static final long TS2 = TS + 1;
  private static final long TS3 = TS2 + 1;

  private static final IdURIEncoderDecoder URI_CODEC = new IdURIEncoderDecoder(new JavaUrlCodec());
  private static final HashedVersionFactory HASH_FACTORY = new HashedVersionFactoryImpl(URI_CODEC);
  private static final HashedVersion V0 = HASH_FACTORY.createVersionZero(NAME);

  /** Creates a new, empty wavelet state. */
  protected abstract WaveletState createEmptyState(WaveletName name) throws Exception;

  /**
   * Waits for all pending persistence operations to be completed. All persistence listener
   * callbacks must be completed before this method returns.
   */
  protected abstract void awaitPersistence() throws Exception;

  private WaveletDeltaRecord d1;
  private WaveletDeltaRecord d2;
  private WaveletDeltaRecord d3;

  private WaveletState target;

  @Override
  public void setUp() throws Exception {
    d1 = makeDelta(V0, TS, 2);
    d2 = makeDelta(d1.getResultingVersion(), TS2, 2);
    d3 = makeDelta(d2.getResultingVersion(), TS3, 1);

    target = createEmptyState(NAME);
  }

  public void testReportsWaveletName() {
    assertEquals(NAME, target.getWaveletName());
  }

  public void testEmptyStateIsEmpty() {
    assertNull(target.getSnapshot());
    assertEquals(V0, target.getCurrentVersion());
    assertEquals(V0, target.getHashedVersion(0));

    assertNull(target.getTransformedDelta(V0));
    assertNull(target.getAppliedDelta(V0));
  }

  public void testSnapshotMetadataReflectsDeltas() throws Exception {
    HashedVersion v2 = d1.getResultingVersion();
    appendDeltas(d1);

    assertEquals(v2, target.getCurrentVersion());
    ReadableWaveletData snapshot = target.getSnapshot();
    assertEquals(AUTHOR, snapshot.getCreator());
    assertEquals(v2, snapshot.getHashedVersion());
    assertEquals(TS, snapshot.getCreationTime());
    assertEquals(TS, snapshot.getLastModifiedTime());
    assertEquals(2, snapshot.getVersion());

    HashedVersion v4 = d2.getResultingVersion();
    appendDeltas(d2);

    assertEquals(v4, target.getCurrentVersion());
    snapshot = target.getSnapshot();
    assertEquals(v4, snapshot.getHashedVersion());
    assertEquals(4, snapshot.getVersion());
    // Last-modified-time doesn't change due to unworthiness.
  }

  public void testHashedVersionAccessibleOnDeltaBoundaries() throws Exception {
    appendDeltas(d1, d2, d3);
    assertEquals(V0, target.getHashedVersion(0));
    assertEquals(d1.getResultingVersion(), target.getHashedVersion(2));
    assertEquals(d2.getResultingVersion(), target.getHashedVersion(4));
    assertEquals(d3.getResultingVersion(), target.getHashedVersion(5));
    assertNull(target.getHashedVersion(1));
    assertNull(target.getHashedVersion(3));
    assertNull(target.getHashedVersion(6));
  }

  public void testDeltasAccessibleByBeginVersion() throws Exception {
    appendDeltas(d1, d2, d3);
    assertEquals(d1.transformed, target.getTransformedDelta(V0));
    assertEquals(d1.applied, target.getAppliedDelta(V0));

    assertEquals(d2.transformed, target.getTransformedDelta(d1.getResultingVersion()));
    assertEquals(d2.applied, target.getAppliedDelta(d1.getResultingVersion()));

    assertEquals(d3.transformed, target.getTransformedDelta(d2.getResultingVersion()));
    assertEquals(d3.applied, target.getAppliedDelta(d2.getResultingVersion()));

    // Wrong hashes return null.
    assertNull(target.getTransformedDelta(HashedVersion.unsigned(0)));
    assertNull(target.getAppliedDelta(HashedVersion.unsigned(0)));
  }

  public void testDeltasAccesssibleByEndVersion() throws Exception {
    appendDeltas(d1, d2, d3);
    for (WaveletDeltaRecord d : Arrays.asList(d1, d2, d3)) {
      assertEquals(d.transformed, target.getTransformedDeltaByEndVersion(d.getResultingVersion()));
      assertEquals(d.applied, target.getAppliedDeltaByEndVersion(d.getResultingVersion()));
    }

    // Wrong hashes return null.
    assertNull(
        target.getTransformedDeltaByEndVersion(
            HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
    assertNull(
        target.getAppliedDeltaByEndVersion(
            HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
  }

  public void testDeltaHistoryRequiresCorrectHash() throws Exception {
    appendDeltas(d1);

    // Wrong start hash.
    assertNull(
        target.getTransformedDeltaHistory(HashedVersion.unsigned(0), d1.getResultingVersion()));
    assertNull(target.getAppliedDeltaHistory(HashedVersion.unsigned(0), d1.getResultingVersion()));

    // Wrong end hash.
    assertNull(
        target.getTransformedDeltaHistory(
            V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
    assertNull(
        target.getAppliedDeltaHistory(
            V0, HashedVersion.unsigned(d1.getResultingVersion().getVersion())));
  }

  public void testSingleDeltaHistoryAccessible() throws Exception {
    appendDeltas(d1);
    DeltaSequence transformedHistory =
        target.getTransformedDeltaHistory(V0, d1.getResultingVersion());
    assertNotNull(transformedHistory);
    assertEquals(1, transformedHistory.size());
    assertEquals(d1.transformed, transformedHistory.get(0));

    Collection<ByteStringMessage<ProtocolAppliedWaveletDelta>> appliedHistory =
        target.getAppliedDeltaHistory(V0, d1.getResultingVersion());
    assertNotNull(appliedHistory);
    assertEquals(1, appliedHistory.size());
    assertEquals(d1.applied, Iterables.getOnlyElement(appliedHistory));
  }

  public void testDeltaHistoryQueriesCorrectHistory() throws Exception {
    appendDeltas(d1, d2, d3);

    checkHistoryForDeltas(d1);
    checkHistoryForDeltas(d1, d2);
    checkHistoryForDeltas(d2, d3);
    checkHistoryForDeltas(d1, d2, d3);
  }

  /**
   * Checks that a request for the deltas spanning a contiguous sequence of delta facets produces
   * correct results.
   */
  private void checkHistoryForDeltas(WaveletDeltaRecord... deltas) {
    HashedVersion beginVersion = deltas[0].appliedAtVersion;
    HashedVersion endVersion = deltas[deltas.length - 1].transformed.getResultingVersion();

    {
      List<TransformedWaveletDelta> expected = Lists.newArrayListWithExpectedSize(deltas.length);
      for (WaveletDeltaRecord d : deltas) {
        expected.add(d.transformed);
      }
      assertEquals(expected, target.getTransformedDeltaHistory(beginVersion, endVersion));
    }
    {
      List<ByteStringMessage<ProtocolAppliedWaveletDelta>> expected =
          Lists.newArrayListWithExpectedSize(deltas.length);
      for (WaveletDeltaRecord d : deltas) {
        expected.add(d.applied);
      }
      assertTrue(
          Iterables.elementsEqual(
              expected, target.getAppliedDeltaHistory(beginVersion, endVersion)));
    }
  }

  public void checkSingleDeltaPersistFutureDone() throws Exception {
    appendDeltas(d1);
    Future<Void> future = target.persist(d1.getResultingVersion());
    awaitPersistence();
    assertTrue(future.isDone());
    assertEquals(null, future.get());
    assertEquals(d1.getResultingVersion(), target.getLastPersistedVersion());
  }

  public void checkManyDeltasPersistFutureDone() throws Exception {
    appendDeltas(d1, d2, d3);
    Future<Void> future = target.persist(d3.getResultingVersion());
    awaitPersistence();
    assertTrue(future.isDone());
    assertEquals(null, future.get());
    assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion());
  }

  public void testCanPersistOnlySomeDeltas() throws Exception {
    appendDeltas(d1, d2, d3);
    Future<Void> future = target.persist(d2.getResultingVersion());
    awaitPersistence();
    assertTrue(future.isDone());
    assertEquals(null, future.get());
    assertEquals(d2.getResultingVersion(), target.getLastPersistedVersion());

    future = target.persist(d3.getResultingVersion());
    awaitPersistence();
    assertTrue(future.isDone());
    assertEquals(null, future.get());
    assertEquals(d3.getResultingVersion(), target.getLastPersistedVersion());
  }

  /** Applies a delta to the target. */
  private void appendDeltas(WaveletDeltaRecord... deltas)
      throws InvalidProtocolBufferException, OperationException {
    for (WaveletDeltaRecord delta : deltas) {
      target.appendDelta(delta.appliedAtVersion, delta.transformed, delta.applied);
    }
  }

  /**
   * Creates a delta of no-ops and builds the corresponding applied and transformed delta objects.
   */
  private static WaveletDeltaRecord makeDelta(
      HashedVersion appliedAtVersion, long timestamp, int numOps)
      throws InvalidProtocolBufferException {
    // Use no-op delta so the ops can actually apply.
    WaveletDelta delta = UTIL.makeNoOpDelta(appliedAtVersion, timestamp, numOps);
    ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta =
        WaveServerTestUtil.buildAppliedDelta(delta, timestamp);
    TransformedWaveletDelta transformedDelta =
        AppliedDeltaUtil.buildTransformedDelta(appliedDelta, delta);
    return new WaveletDeltaRecord(appliedAtVersion, appliedDelta, transformedDelta);
  }
}