/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.waveprotocol.wave.model.supplement; import org.waveprotocol.wave.model.conversation.WaveletBasedConversation; import org.waveprotocol.wave.model.document.ObservableMutableDocument; import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl; import org.waveprotocol.wave.model.document.util.DocHelper; import org.waveprotocol.wave.model.document.util.DocumentEventRouter; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.supplement.ObservablePrimitiveSupplement.Listener; import org.waveprotocol.wave.model.util.ElementListener; import org.waveprotocol.wave.model.util.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Represents a collection of per-wavelet thread-states, implemented by * embedding them in elements of a document. * * @see WaveletReadStateCollection * */ final class WaveletThreadStateCollection<E> implements ElementListener<E> { private final DocumentEventRouter<? super E, E, ?> router; private final E container; /** Read state, expressed as a per-wavelet structure. */ private final Map<WaveletId, WaveletThreadState> waveletSupplements = new HashMap<WaveletId, WaveletThreadState>(); /** Listener to inject into each read-state. */ private final Listener listener; private WaveletThreadStateCollection(DocumentEventRouter<? super E, E, ?> router, E container, Listener listener) { this.router = router; this.container = container; this.listener = listener; } public static <E> WaveletThreadStateCollection<E> create( DocumentEventRouter<? super E, E, ?> router, E e, Listener listener) { WaveletThreadStateCollection<E> col = new WaveletThreadStateCollection<E>(router, e, listener); router.addChildListener(e, col); col.load(); return col; } private ObservableMutableDocument<? super E, E, ?> getDocument() { return router.getDocument(); } private void load() { ObservableMutableDocument<? super E, E, ?> doc = getDocument(); E child = DocHelper.getFirstChildElement(doc, doc.getDocumentElement()); while (child != null) { onElementAdded(child); child = DocHelper.getNextSiblingElement(doc, child); } } private WaveletId valueOf(E element) { String waveletIdStr = getDocument().getAttribute(element, WaveletBasedSupplement.ID_ATTR); return WaveletBasedConversation.widFor(waveletIdStr); } @Override public void onElementAdded(E element) { ObservableMutableDocument<? super E, E, ?> doc = getDocument(); assert container.equals(doc.getParentElement(element)); if (!WaveletBasedSupplement.CONVERSATION_TAG.equals(doc.getTagName(element))) { return; } WaveletId waveletId = valueOf(element); if (waveletId != null) { WaveletThreadState existing = waveletSupplements.get(waveletId); if (existing == null) { WaveletThreadState read = DocumentBasedWaveletThreadState.create(router, element, waveletId, listener); waveletSupplements.put(waveletId, read); } else { // // We can't mutate during callbacks yet. // Let's just ignore the latter :(. Clean up on timer? // } } else { // XML error: someone added a WAVELET element without an id. Ignore. // TODO(user): we should log this } } public void onElementRemoved(E element) { if (WaveletBasedSupplement.CONVERSATION_TAG.equals(getDocument().getTagName(element))) { WaveletId waveletId = valueOf(element); if (waveletId != null) { WaveletThreadState state = waveletSupplements.remove(waveletId); if (state == null) { // Not good - there was a collapsed-state element and we weren't // tracking it... // TODO(user): this is the same problem as the read state // tracker } } } } private void createEntry(WaveletId waveletId) { String waveletIdStr = WaveletBasedConversation.idFor(waveletId); ObservableMutableDocument<? super E, E, ?> doc = getDocument(); E container = doc.createChildElement(doc.getDocumentElement(), WaveletBasedSupplement.CONVERSATION_TAG, new AttributesImpl(WaveletBasedSupplement.ID_ATTR, waveletIdStr)); } WaveletThreadState getThreadStateSupplement(WaveletId waveletId) { Preconditions.checkNotNull(waveletId, "wavelet id must not be null"); WaveletThreadState wavelet = waveletSupplements.get(waveletId); if (wavelet == null) { // Create a new container element for tracking state for the wavelet. // Callbacks should build it. createEntry(waveletId); wavelet = waveletSupplements.get(waveletId); assert wavelet != null; } return wavelet; } void clear() { Collection<WaveletId> toRemove = new ArrayList<WaveletId>(waveletSupplements.keySet()); for (WaveletId waveletId : toRemove) { waveletSupplements.get(waveletId).remove(); } } ThreadState getThreadState(WaveletId waveletId, String threadId) { WaveletThreadState wavelet = waveletSupplements.get(waveletId); return wavelet == null ? null : wavelet.getThreadState(threadId); } public Iterable<String> getStatefulThreads(WaveletId waveletId) { return waveletSupplements.containsKey(waveletId) ? getThreadStateSupplement(waveletId) .getThreads() : Collections.<String> emptySet(); } public Iterable<WaveletId> getStatefulWavelets() { return waveletSupplements.keySet(); } void setThreadState(WaveletId waveletId, String threadId, ThreadState value) { if (value == null) { WaveletThreadState wavelet = waveletSupplements.get(waveletId); if (wavelet != null) { wavelet.setThreadState(threadId, value); } } else { getThreadStateSupplement(waveletId).setThreadState(threadId, value); } } }