/**
* 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 read-states, implemented by embedding
* them in elements of a document.
*
*/
class WaveletReadStateCollection<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, WaveletReadState> waveletSupplements =
new HashMap<WaveletId, WaveletReadState>();
/** Listener to inject into each read-state. */
private final Listener listener;
private WaveletReadStateCollection(DocumentEventRouter<? super E, E, ?> router, E container,
Listener listener) {
this.router = router;
this.container = container;
this.listener = listener;
}
public static <E> WaveletReadStateCollection<E> create(
DocumentEventRouter<? super E, E, ?> router, E e, Listener listener) {
WaveletReadStateCollection<E> col = new WaveletReadStateCollection<E>(router, e, listener);
router.addChildListener(e, col);
col.load();
return col;
}
private ObservableMutableDocument<? super E, E, ?> getDocument() {
return router.getDocument();
}
private void load() {
E child = DocHelper.getFirstChildElement(getDocument(), getDocument().getDocumentElement());
while (child != null) {
onElementAdded(child);
child = DocHelper.getNextSiblingElement(getDocument(), child);
}
}
private WaveletId valueOf(E element) {
String waveletIdStr = getDocument().getAttribute(element, WaveletBasedSupplement.ID_ATTR);
return WaveletBasedConversation.widFor(waveletIdStr);
}
@Override
public void onElementAdded(E element) {
assert container == getDocument().getParentElement(element);
if (!WaveletBasedSupplement.WAVELET_TAG.equals(getDocument().getTagName(element))) {
return;
}
WaveletId waveletId = valueOf(element);
if (waveletId != null) {
WaveletReadState existing = waveletSupplements.get(waveletId);
if (existing == null) {
WaveletReadState read =
DocumentBasedWaveletReadState.create(router, element, waveletId, listener);
waveletSupplements.put(waveletId, read);
// Fire events reflecting the initial read state.
//
// NOTE(user): it is important that these events get fired after the new read-state
// object is added to the map above, in order that the interface presented by this
// collection object is consistent with the events being broadcast to the listener.
//
int waveletVersion = read.getWaveletLastReadVersion();
if (waveletVersion != WaveletBasedSupplement.NO_VERSION) {
listener.onLastReadWaveletVersionChanged(waveletId, PrimitiveSupplement.NO_VERSION,
waveletVersion);
}
int participantsVersion = read.getParticipantsLastReadVersion();
if (participantsVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadParticipantsVersionChanged(waveletId, PrimitiveSupplement.NO_VERSION,
participantsVersion);
}
int tagsVersion = read.getTagsLastReadVersion();
if (tagsVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadTagsVersionChanged(waveletId, PrimitiveSupplement.NO_VERSION,
tagsVersion);
}
for (String blipId : read.getReadBlips()) {
int blipVersion = read.getBlipLastReadVersion(blipId);
if (blipVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadBlipVersionChanged(waveletId, blipId, PrimitiveSupplement.NO_VERSION,
blipVersion);
}
}
} else {
//
// We can't mutate during callbacks yet.
// Let's just ignore the latter :(. Clean up on timer?
//
// Merge the two together, and delete latter.
//
// for (String blip : read.getReadBlips()) {
// existing.setBlipLastReadVersion(blip,
// read.getBlipLastReadVersion(blip));
// }
// int participantsReadVersion =
// read.getParticipantsLastReadVersion();
// int waveletReadVersion = read.getWaveletLastReadVersion();
// if (participantsReadVersion != NO_VERSION) {
// existing.setParticipantsLastReadVersion(participantsReadVersion);
// }
// if (waveletReadVersion != NO_VERSION) {
// existing.setWaveletLastReadVersion(waveletReadVersion);
// }
}
} else {
// XML error: someone added a WAVELET element without an id. Ignore.
// TODO(user): log this at error level, once loggers are injected into
// these classes.
}
}
public void onElementRemoved(E element) {
if (WaveletBasedSupplement.WAVELET_TAG.equals(getDocument().getTagName(element))) {
WaveletId waveletId = valueOf(element);
if (waveletId != null) {
WaveletReadState state = waveletSupplements.remove(waveletId);
if (state == null) {
// Not good - there was a read-state element and we weren't tracking
// it...
// TODO(user): report an error here.
return;
}
// Fire events reflecting everything becoming unread.
for (String blipId : state.getReadBlips()) {
int blipVersion = state.getBlipLastReadVersion(blipId);
if (blipVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadBlipVersionChanged(waveletId, blipId, blipVersion,
PrimitiveSupplement.NO_VERSION);
}
}
int tagsVersion = state.getTagsLastReadVersion();
if (tagsVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadTagsVersionChanged(waveletId, tagsVersion,
PrimitiveSupplement.NO_VERSION);
}
int participantsVersion = state.getParticipantsLastReadVersion();
if (participantsVersion != PrimitiveSupplement.NO_VERSION) {
listener.onLastReadParticipantsVersionChanged(waveletId, participantsVersion,
PrimitiveSupplement.NO_VERSION);
}
int waveletVersion = state.getWaveletLastReadVersion();
if (waveletVersion != WaveletBasedSupplement.NO_VERSION) {
listener.onLastReadWaveletVersionChanged(waveletId, PrimitiveSupplement.NO_VERSION,
waveletVersion);
}
}
}
}
private void createEntry(WaveletId waveletId) {
String waveletIdStr = WaveletBasedConversation.idFor(waveletId);
E container =
getDocument().createChildElement(getDocument().getDocumentElement(),
WaveletBasedSupplement.WAVELET_TAG,
new AttributesImpl(WaveletBasedSupplement.ID_ATTR, waveletIdStr));
}
WaveletReadState getSupplement(WaveletId waveletId) {
Preconditions.checkNotNull(waveletId, "wavelet id must not be null");
WaveletReadState 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 setLastReadBlipVersion(WaveletId waveletId, String blipId, int version) {
getSupplement(waveletId).setBlipLastReadVersion(blipId, version);
}
void setLastReadParticipantsVersion(WaveletId waveletId, int version) {
getSupplement(waveletId).setParticipantsLastReadVersion(version);
}
void setLastReadTagsVersion(WaveletId waveletId, int version) {
getSupplement(waveletId).setTagsLastReadVersion(version);
}
void setLastReadWaveletVersion(WaveletId waveletId, int version) {
getSupplement(waveletId).setWaveletLastReadVersion(version);
}
void clear() {
Collection<WaveletId> toRemove = new ArrayList<WaveletId>(waveletSupplements.keySet());
for (WaveletId waveletId : toRemove) {
waveletSupplements.get(waveletId).remove();
}
}
void clearBlipReadState(WaveletId waveletId, String blipId) {
waveletSupplements.get(waveletId).clearBlipReadState(blipId);
}
int getLastReadParticipantsVersion(WaveletId waveletId) {
WaveletReadState wavelet = waveletSupplements.get(waveletId);
return wavelet != null ? wavelet.getParticipantsLastReadVersion()
: WaveletBasedSupplement.NO_VERSION;
}
int getLastReadTagsVersion(WaveletId waveletId) {
WaveletReadState wavelet = waveletSupplements.get(waveletId);
return wavelet != null ? wavelet.getTagsLastReadVersion()
: WaveletBasedSupplement.NO_VERSION;
}
int getLastReadBlipVersion(WaveletId waveletId, String blipId) {
WaveletReadState wavelet = waveletSupplements.get(waveletId);
return wavelet != null ? wavelet.getBlipLastReadVersion(blipId)
: WaveletBasedSupplement.NO_VERSION;
}
int getLastReadWaveletVersion(WaveletId waveletId) {
WaveletReadState wavelet = waveletSupplements.get(waveletId);
return wavelet != null ? wavelet.getWaveletLastReadVersion()
: WaveletBasedSupplement.NO_VERSION;
}
Iterable<WaveletId> getReadWavelets() {
return waveletSupplements.keySet();
}
Iterable<String> getReadBlips(WaveletId waveletId) {
WaveletReadState wavelet = waveletSupplements.get(waveletId);
return wavelet != null ? wavelet.getReadBlips() : Collections.<String> emptySet();
}
}