/**
* Copyright 2009 Google Inc.
*
* Licensed 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.conversation;
import org.waveprotocol.wave.model.adt.BasicValue;
import org.waveprotocol.wave.model.adt.ObservableElementList;
import org.waveprotocol.wave.model.adt.docbased.DocumentBasedBasicValue;
import org.waveprotocol.wave.model.adt.docbased.DocumentBasedElementList;
import org.waveprotocol.wave.model.adt.docbased.Factory;
import org.waveprotocol.wave.model.adt.docbased.Initializer;
import org.waveprotocol.wave.model.document.util.DocumentEventRouter;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.Serializer;
/**
* Manifest blip that uses an element in a document to store its data.
*
* @author zdwang@google.com (David Wang)
*/
final class DocumentBasedManifestBlip implements ObservableManifestBlip {
/**
* Initialisation data for a thread.
* Package-private for testing.
*/
static final class ThreadInitialiser {
final String id;
final boolean isInline;
public ThreadInitialiser(String id, boolean isInline) {
Preconditions.checkNotNull(id, "Null thread id");
this.id = id;
this.isInline = isInline;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ThreadInitialiser)) {
return false;
}
ThreadInitialiser other = (ThreadInitialiser) obj;
return id.equals(other.id) && (isInline == other.isInline);
}
@Override
public int hashCode() {
return id.hashCode() * 37 + Boolean.valueOf(isInline).hashCode();
}
}
private static final String THREAD_TAG = "thread";
private static final String BLIP_ID_ATTR = "id";
/** The id of the blip. */
private final BasicValue<String> id;
/** Replies to this blip, both inline and not. */
private final ObservableElementList<ObservableManifestThread, ThreadInitialiser> replies;
private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();
/**
* Creates a document-based blip.
*
* @param router the document access that the element belongs
* @param container the <blip> element
*/
static <E> DocumentBasedManifestBlip create(DocumentEventRouter<? super E, E, ?> router,
E container) {
return new DocumentBasedManifestBlip(
DocumentBasedElementList.create(router, container, THREAD_TAG,
DocumentBasedManifestThread.<E> factory()),
DocumentBasedBasicValue.create(router, container, Serializer.STRING, BLIP_ID_ATTR));
}
/**
* Creates a factory for initializing manifest blip entries in a
* document-based element list.
*/
static <E> Factory<E, ObservableManifestBlip, String> factory() {
return new Factory<E, ObservableManifestBlip, String>() {
@Override
public ObservableManifestBlip adapt(DocumentEventRouter<? super E, E, ?> router,
E element) {
return DocumentBasedManifestBlip.create(router, element);
}
@Override
public Initializer createInitializer(String blipId) {
return DocumentBasedBasicValue.createInitialiser(Serializer.STRING, BLIP_ID_ATTR, blipId);
}
};
}
/**
* Creates a DocumentBasedManifestBlip. Package-private for testing.
*
* @param replies the replies list
* @param id the id attribute
*/
DocumentBasedManifestBlip(
ObservableElementList<ObservableManifestThread, ThreadInitialiser> replies,
BasicValue<String> id) {
ObservableElementList.Listener<ObservableManifestThread> repliesListener =
new ObservableElementList.Listener<ObservableManifestThread>() {
public void onValueAdded(ObservableManifestThread entry) {
triggerOnManifestThreadAdded(entry);
}
public void onValueRemoved(ObservableManifestThread entry) {
entry.detachListeners();
triggerOnManifestThreadRemoved(entry);
}
};
this.replies = replies;
this.replies.addListener(repliesListener);
this.id = id;
}
@Override
public String getId() {
return id.get();
}
@Override
public ObservableManifestThread appendReply(String id, boolean inline) {
return replies.add(new ThreadInitialiser(id, inline));
}
@Override
public ObservableManifestThread insertReply(int index, String id, boolean inline) {
return replies.add(index, new ThreadInitialiser(id, inline));
}
@Override
public ObservableManifestThread getReply(int index) {
return replies.get(index);
}
@Override
public Iterable<ObservableManifestThread> getReplies() {
return replies.getValues();
}
@Override
public int indexOf(ManifestThread reply) {
return (reply instanceof ObservableManifestThread) ?
replies.indexOf((ObservableManifestThread) reply) : -1;
}
@Override
public boolean removeReply(ManifestThread reply) {
return (reply instanceof ObservableManifestThread) ?
replies.remove((ObservableManifestThread) reply) : false;
}
@Override
public int numReplies() {
return replies.size();
}
@Override
public void detachListeners() {
listeners.clear();
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
@Override
public String toString() {
return getClass().getName() + "(id = " + id.get() + ")";
}
private void triggerOnManifestThreadAdded(ObservableManifestThread entry) {
for (Listener l : listeners) {
l.onReplyAdded(entry);
}
}
private void triggerOnManifestThreadRemoved(ObservableManifestThread entry) {
for (Listener l : listeners) {
l.onReplyRemoved(entry);
}
}
}