/**
* 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.examples.fedone.waveclient.common;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.waveprotocol.wave.examples.fedone.common.CommonConstants;
import org.waveprotocol.wave.examples.fedone.common.DocumentConstants;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.BufferedDocOp;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.DocOpCursor;
import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder;
import org.waveprotocol.wave.model.document.operation.impl.InitializationCursorAdapter;
import org.waveprotocol.wave.model.id.IdConstants;
import org.waveprotocol.wave.model.id.WaveId;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletDocumentOperation;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.WaveletData;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Static utility methods for common use throughout the client.
*
*
*/
public class ClientUtils {
/**
* Disallow construction.
*/
private ClientUtils() {
}
/**
* Renders and concatenates all of the specified documents into a single
* String in the order in which they appear in documentStates.
*
* @param documentStates the documents to render
* @return A String containing the characters from all documents, in order
*/
public static String render(Iterable<BufferedDocOp> documentStates) {
final StringBuilder resultBuilder = new StringBuilder();
for (BufferedDocOp documentState : documentStates) {
documentState.apply(new InitializationCursorAdapter(
new DocOpCursor() {
@Override
public void characters(String s) {
resultBuilder.append(s);
}
@Override public void annotationBoundary(AnnotationBoundaryMap map) {}
@Override public void elementStart(String type, Attributes attrs) {}
@Override public void elementEnd() {}
@Override public void retain(int itemCount) {}
@Override public void deleteCharacters(String chars) {}
@Override public void deleteElementStart(String type, Attributes attrs) {}
@Override public void deleteElementEnd() {}
@Override public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) {}
@Override public void updateAttributes(AttributesUpdate attrUpdate) {}
}
));
}
return resultBuilder.toString();
}
/**
* Render all of the documents in a wave as a single String, in the order
* in which they are listed by wavelet.getDocumentIds().
*
* TODO: move this to the console package (...console.ConsoleUtils)
*
* @param wave wave to render
* @return rendered wave
*/
public static String renderDocuments(ClientWaveView wave) {
final StringBuilder doc = new StringBuilder();
for (WaveletData wavelet : wave.getWavelets()) {
doc.append(render(wavelet.getDocuments().values()));
}
return doc.toString();
}
/**
* Create a document operation for the insertion of text, inserting at a given index.
*
* @param text text to insert
* @param index index to insert at
* @return document operation which inserts text at a given index
*/
public static BufferedDocOp createTextInsertion(String text,
int index, int previousTotalLength) {
DocOpBuilder builder = new DocOpBuilder();
if (index > 0) {
builder.retain(index);
}
if (!text.isEmpty()) {
builder.characters(text);
}
if (previousTotalLength > index) {
builder.retain(previousTotalLength - index);
}
return builder.build();
}
/**
* Create a mutation that creates a new blip containing the given text, places it in a new
* blip, then adds a reference to the blip in the document manifest.
*
* @param manifestDocument document containing the manifest
* @param author of the delta
* @param newBlipId
* @param text to place in the new blip
*/
public static WaveletDelta createAppendBlipDelta(BufferedDocOp manifestDocument,
ParticipantId author, String newBlipId, String text) {
if (text.length() == 0) {
throw new IllegalArgumentException("Cannot append a empty String");
} else {
// Create new blip to insert at end of document.
BufferedDocOp newBlipOp = new DocOpBuilder()
.elementStart(DocumentConstants.BODY, Attributes.EMPTY_MAP)
.elementStart(DocumentConstants.LINE, Attributes.EMPTY_MAP)
.elementEnd() // </line>
.characters(text)
.elementEnd() // </body>
.build();
// An empty doc op to indicate a new blip is being created.
BufferedDocOp emptyNewBlipOp = new DocOpBuilder().build();
// Send the operation.
ImmutableList<WaveletDocumentOperation> operations = ImmutableList.of(
new WaveletDocumentOperation(newBlipId, emptyNewBlipOp),
new WaveletDocumentOperation(newBlipId, newBlipOp),
appendToManifest(manifestDocument, newBlipId));
return new WaveletDelta(author, operations);
}
}
/**
* Add record of a blip to the end of the manifest.
*/
public static WaveletDocumentOperation appendToManifest(BufferedDocOp manifestDocument,
String blipId) {
BufferedDocOp manifestUpdateOp = new DocOpBuilder()
.retain(findDocumentSize(manifestDocument) - 1)
.elementStart(DocumentConstants.BLIP,
new AttributesImpl(ImmutableMap.of(DocumentConstants.BLIP_ID, blipId)))
.elementEnd() // </blip>
.retain(1) // retain </conversation>
.build();
return new WaveletDocumentOperation(DocumentConstants.MANIFEST_DOCUMENT_ID, manifestUpdateOp);
}
/**
* Find the size of a document in number of characters and tags.
*
* @param doc document mutation to find the size
* @return size of the document in number of characters and tags
*/
public static int findDocumentSize(DocOp doc) {
final AtomicInteger size = new AtomicInteger(0);
doc.apply(new InitializationCursorAdapter(new DocOpCursor() {
@Override public void characters(String s) {
size.getAndAdd(s.length());
}
@Override public void elementStart(String key, Attributes attrs) {
size.incrementAndGet();
}
@Override public void elementEnd() {
size.incrementAndGet();
}
@Override public void annotationBoundary(AnnotationBoundaryMap map) {}
@Override public void retain(int itemCount) {}
@Override public void deleteCharacters(String chars) {}
@Override public void deleteElementStart(String type, Attributes attrs) {}
@Override public void deleteElementEnd() {}
@Override public void replaceAttributes(Attributes oldAttrs, Attributes newAttrs) {}
@Override public void updateAttributes(AttributesUpdate attrUpdate) {}
}));
return size.get();
}
/**
* @return a empty document
*/
public static BufferedDocOp createEmptyDocument() {
return new DocOpBuilder().build();
}
/**
* Retrieve a list of index entries from an index wave.
*
* @param indexWave the wave to retrieve the index from
* @return list of index entries
*/
public static List<IndexEntry> getIndexEntries(ClientWaveView indexWave) {
if (!indexWave.getWaveId().equals(CommonConstants.INDEX_WAVE_ID)) {
throw new IllegalArgumentException(indexWave + " is not the index wave");
}
List<IndexEntry> indexEntries = Lists.newArrayList();
for (WaveletData wavelet : indexWave.getWavelets()) {
// The wave id is encoded as the wavelet id
WaveId waveId = WaveId.deserialise(wavelet.getWaveletName().waveletId.serialise());
String digest = ClientUtils.render(wavelet.getDocuments().values());
indexEntries.add(new IndexEntry(waveId, digest));
}
return indexEntries;
}
/**
* Get the conversation root wavelet of a wave.
*
* @param wave to get conversation root of
* @return conversation root wavelet of the wave
*/
public static WaveletData getConversationRoot(ClientWaveView wave) {
return wave.getWavelet(getConversationRootId(wave));
}
/**
* @return the conversation root wavelet id of a wave.
*/
public static WaveletId getConversationRootId(ClientWaveView wave) {
return getConversationRootId(wave.getWaveId());
}
/**
* @return the conversation root wavelet id corresponding to a wave id.
*/
public static WaveletId getConversationRootId(WaveId waveId) {
return new WaveletId(waveId.getDomain(), IdConstants.CONVERSATION_ROOT_WAVELET);
}
}