private void observe(WaveletBasedConversationThread thread) { threads.put(thread.getId(), thread); thread.addListener(new ThreadListenerAggregator(thread)); for (WaveletBasedConversationBlip blip : thread.getBlips()) { observe(blip); } }
/** * Invalidates this thread. It may no longer be accessed. */ void invalidate() { checkIsUsable(); manifestThread.removeListener(this); isUsable = false; }
@Override public boolean isRoot() { return parentThread == getConversation().getRootThread() && this == parentThread.getFirstBlip(); }
@Override public WaveletBasedConversationBlip appendBlip() { checkIsUsable(); return appendBlipWithContent(null); }
@Override public void onDeleted() { thread.removeListener(this); triggerOnThreadDeleted(thread); threads.remove(thread.getId()); } }
/** * Creates a conversation thread backed by a manifest thread and inserts it in * {@code replies}. */ private WaveletBasedConversationThread adaptThread(ObservableManifestThread manifestThread) { WaveletBasedConversationThread thread = WaveletBasedConversationThread.create(manifestThread, this, helper); String id = thread.getId(); replies.put(id, thread); return thread; }
public void testAppendBlipsInReplyThreadsUpdatesManifest() { WaveletBasedConversationBlip blip = target.getRootThread().appendBlip(); WaveletBasedConversationThread reply = blip.addReplyThread(); WaveletBasedConversationBlip firstReplyBlip = reply.appendBlip(); WaveletBasedConversationBlip secondReplyBlip = reply.appendBlip(); assertManifestXml("<blip id=\"" + blip.getId() + "\">" + "<thread id=\"" + reply.getId() + "\">" + "<blip id=\"" + firstReplyBlip.getId() + "\"></blip>" + "<blip id=\"" + secondReplyBlip.getId() + "\"></blip>" + "</thread>" + "</blip>"); assertMirrorConversationEquivalent(); }
public void testConcurrentDeletionOfFinalThreadsLeavesEmptyBlip() { WaveletBasedConversationBlip first = target.getRootThread().appendBlip(); WaveletBasedConversationThread t1 = first.addReplyThread(); WaveletBasedConversationBlip t1b = t1.appendBlip(); WaveletBasedConversationThread t2 = first.addReplyThread(); WaveletBasedConversationBlip t2b = t2.appendBlip(); // Locally delete t1, remotely delete t2. t1b.delete(); first.addListener(blipListener); t2.addListener(threadListener); target.addListener(convListener); remoteRemoveBlip(t2b); remoteRemoveThread(t2); // Expect thread t2 deletion events and it to be invalid. verify(threadListener).onDeleted(); verify(convListener).onBlipDeleted(t2b); verify(convListener).onThreadDeleted(t2); assertBlipInvalid(t2b); assertThreadInvalid(t2); assertBlipValid(first); assertNotNull(target.getRootThread().getFirstBlip()); // The manifest now has an empty blip. assertEquals(0, first.getManifestBlip().numReplies()); // Still there after next write. WaveletBasedConversationBlip second = target.getRootThread().appendBlip(); assertBlipValid(first); verify(convListener).onBlipAdded(second); verifyNoMoreInteractions(blipListener, threadListener, convListener); }
WaveletBasedConversationBlip rootWaveletRootBlip = rootConv.getRootThread().appendBlip(); target.setAnchor(rootConv.createAnchor(rootConv.getRootThread().getFirstBlip())); WaveletBasedConversationBlip firstBlip = target.getRootThread().appendBlip(); WaveletBasedConversationBlip secondBlip = target.getRootThread().appendBlip(); WaveletBasedConversationBlip firstReplyFirstBlip = firstReply.appendBlip(); final WaveletBasedConversationThread secondReply = firstBlip.addReplyThread( BlipTestUtils.getBodyPosition(firstBlip) + 1); WaveletBasedConversationBlip secondReplyFirstBlip = secondReply.appendBlip(); + "<thread id=\"" + firstReply.getId() + "\">" + "<blip id=\"" + firstReplyFirstBlip.getId() + "\"></blip>" + "</thread>" + "<thread id=\"" + secondReply.getId() + "\" inline=\"true\">" + "<blip id=\"" + secondReplyFirstBlip.getId() + "\"></blip>" + "</thread>"
/** * Tests that empty threads are not ignored. */ public void testCreateWithEmptyManifestThreadNotIgnored() { ConversationBlip blip = target.getRootThread().appendBlip(); ConversationThread thread = blip.addReplyThread(); WaveletBasedConversation another = mirrorConversation(target); assertNotNull(another.getRootThread().getFirstBlip()); assertTrue(another.getRootThread().getFirstBlip().getReplyThreads().iterator().hasNext()); }
/** * Deletes a thread from this blip, deleting that thread's blips. */ void deleteThread(WaveletBasedConversationThread threadToDelete) { threadToDelete.deleteBlips(); manifestBlip.removeReply(threadToDelete.getManifestThread()); clearInlineReplyAnchor(threadToDelete.getId()); }
/** * Tests copying of a wavelet with a root thread and a reply thread. */ public void testReplyThreadCopy() { WaveletBasedConversationBlip blip = source.getRootThread().appendBlip(); WaveletBasedConversationThread conversationThread = blip.addReplyThread(); Document doc = conversationThread.appendBlip().getContent(); LineContainers.appendToLastLine(doc, XmlStringBuilder.createText(SAMPLE_TEXT)); ConversationCopier.copyWaveletContents(sourceWavelet, destWavelet); compareWavelets(sourceWavelet, destWavelet); }
/** * Removes a thread from the manifest, as though a remote client had done so. */ private void remoteRemoveThread(WaveletBasedConversationThread thread) { ManifestBlip parentBlip = thread.getParentBlip().getManifestBlip(); ManifestThread manifestThread = thread.getManifestThread(); parentBlip.removeReply(manifestThread); }
/** * Test that iterating a thread whose manifest contains blips not backed by the * wavelet skips those blips. */ public void testMissingBlipIteration() { WaveletBasedConversationThread thread = target.getRootThread(); WaveletBasedConversationBlip first = thread.appendBlip(); manifestDoc.with(new Action() { @Override public <N, E extends N, T extends N> void exec(MutableDocument<N, E, T> doc) { N rootThreadNode = doc.getFirstChild(doc.getDocumentElement()); E rootThread = doc.asElement(rootThreadNode); doc.createChildElement(rootThread, "blip", Collections.singletonMap( "id", idGenerator.newBlipId())); } }); WaveletBasedConversationBlip third = thread.appendBlip(); assertEquals(3, CollectionUtils.newArrayList(thread.getManifestThread().getBlips()).size()); assertEquals(Arrays.asList(first, third), getBlipList(thread)); }
public void testConcrrentDeletionOfFinalBlipsLeavesEmptyThread() { WaveletBasedConversationBlip first = target.getRootThread().appendBlip(); WaveletBasedConversationThread replyThread = first.addReplyThread(); WaveletBasedConversationBlip b1 = replyThread.appendBlip(); WaveletBasedConversationBlip b2 = replyThread.appendBlip(); // Locally delete b1, remotely delete b2. b1.delete(); replyThread.addListener(threadListener); b2.addListener(blipListener); target.addListener(convListener); remoteRemoveBlip(b2); // Expect blip deletion events and it to be invalid. verify(blipListener).onDeleted(); verify(convListener).onBlipDeleted(b2); assertBlipInvalid(b2); assertThreadValid(replyThread); assertEquals(Arrays.asList(replyThread), CollectionUtils.newArrayList(first.getReplyThreads())); // The manifest now has a thread with no blips. assertEquals(1, first.getManifestBlip().numReplies()); assertEquals(0, first.getManifestBlip().getReply(0).numBlips()); // Still there after the next mutation. ObservableConversationBlip second = target.getRootThread().appendBlip(); assertThreadValid(replyThread); assertEquals(Arrays.asList(replyThread), CollectionUtils.newArrayList(first.getReplyThreads())); verify(convListener).onBlipAdded(second); verifyNoMoreInteractions(blipListener, threadListener, convListener); }
/** * Removes a thread from the internal list and triggers its deletion event. */ private void forgetThread(WaveletBasedConversationThread threadToRemove) { String id = threadToRemove.getId(); assert replies.containsKey(id); replies.remove(id); threadToRemove.triggerOnDeleted(); }
/** * Deletes all threads from this blip. * * @see WaveletBasedConversationBlip#deleteThread(WaveletBasedConversationThread) */ void deleteThreads() { // deleteThread() equivalent is inline here so we can do only one // document traversal to remove inline reply anchors. List<WaveletBasedConversationThread> threads = CollectionUtils.newArrayList(getReplyThreads()); for (WaveletBasedConversationThread threadToDelete : threads) { threadToDelete.deleteBlips(); manifestBlip.removeReply(threadToDelete.getManifestThread()); } clearAllInlineReplyAnchors(); }
/** * Removes a blip from the manifest, as though a remote client had done so. */ private void remoteRemoveBlip(WaveletBasedConversationBlip blip) { ManifestThread parentThread = blip.getThread().getManifestThread(); ManifestBlip manifestBlip = blip.getManifestBlip(); parentThread.removeBlip(manifestBlip); }
@Override public Iterable<LocatedReplyThread> locateReplyThreads() { // NOTE(anorth): We must recalculate the anchor locations on each // call as the document does not provide stable elements. However, we // calculate the list of anchor locations on demand. Map<String, Integer> replyLocations = null; List<LocatedReplyThread> inlineReplyThreads = CollectionUtils.newArrayList(); for (WaveletBasedConversationThread reply : getReplyThreads()) { if (replyLocations == null) { replyLocations = findAnchors(); } Integer location = replyLocations.get(reply.getId()); inlineReplyThreads.add(new LocatedReplyThread(reply, (location != null) ? location : Blips.INVALID_INLINE_LOCATION)); } Collections.sort(inlineReplyThreads); return Collections.unmodifiableList(inlineReplyThreads); }
@Override public void onBlipAdded(ObservableManifestBlip manifestBlip) { Blip blip = helper.getBlip(manifestBlip.getId()); if (blip != null) { // Note that this means the blip will be ignored if it doesn't exist in // the wavelet when the manifest entry is added. WaveletBasedConversationBlip convBlip = adaptBlip(manifestBlip, blip); triggerOnBlipAdded(convBlip); } }