/** * 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.box.consoleclient; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import org.waveprotocol.box.common.DocumentConstants; import org.waveprotocol.box.common.Snippets; 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.DocInitialization; 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.BlipContentOperation; import org.waveprotocol.wave.model.operation.wave.BlipOperation; import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.model.wave.data.BlipData; import org.waveprotocol.wave.model.wave.data.WaveletData; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Static utility methods for common use throughout the client. * * */ public class ClientUtils { /** * Disallow construction. */ private ClientUtils() { } /** * Collect the text of all of the documents in a wave into a single String. * * TODO: move this to the console package (...console.ConsoleUtils) * * @param wave to collect the text from. * @return the collected text from the wave. */ public static String collateText(ClientWaveView wave) { final StringBuilder doc = new StringBuilder(); for (WaveletData wavelet : wave.getWavelets()) { Set<String> documentIds = wavelet.getDocumentIds(); for (String documentId : documentIds) { doc.append( Snippets.collateTextForDocuments(Arrays.asList(wavelet.getDocument(documentId)))); } } 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 DocOp 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 newBlipId * @param text to place in the new blip */ public static WaveletBlipOperation[] createAppendBlipOps(BlipData manifestDocument, String newBlipId, String text, WaveletOperationContext context) { if (text.length() == 0) { throw new IllegalArgumentException("Cannot append a empty String"); } else { // An empty doc op to indicate a new blip is being created. BlipContentOperation emptyBlipOp = new BlipContentOperation(context, new DocOpBuilder().build()); // Create new blip to insert at end of document. BlipContentOperation newBlipOp = new BlipContentOperation(context, new DocOpBuilder() .elementStart(DocumentConstants.BODY, Attributes.EMPTY_MAP) .elementStart(DocumentConstants.LINE, Attributes.EMPTY_MAP) .elementEnd() // </line> .characters(text) .elementEnd() // </body> .build()); return new WaveletBlipOperation[] { new WaveletBlipOperation(newBlipId, emptyBlipOp), new WaveletBlipOperation(newBlipId, newBlipOp), appendToManifest(manifestDocument, newBlipId, context)}; } } /** * Add record of a blip to the end of the manifest. */ public static WaveletBlipOperation appendToManifest( BlipData manifestDocument, String blipId, WaveletOperationContext context) { DocInitialization manifestDocOp = manifestDocument.getContent().asOperation(); BlipOperation blipOp = new BlipContentOperation(context, new DocOpBuilder() .retain(findDocumentSize(manifestDocOp) - 1) .elementStart(DocumentConstants.BLIP, new AttributesImpl(ImmutableMap.of(DocumentConstants.BLIP_ID, blipId))) .elementEnd() // </blip> .retain(1) // retain </conversation> .build()); return new WaveletBlipOperation(DocumentConstants.MANIFEST_DOCUMENT_ID, blipOp); } /** * @return an empty document */ public static DocOp createEmptyDocument() { return new DocOpBuilder().build(); } /** * Creates an empty manifest document. * * @return the manifest document. */ public static DocOp createManifest() { return new DocOpBuilder() .elementStart(DocumentConstants.CONVERSATION, Attributes.EMPTY_MAP).elementEnd().build(); } /** * 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(InitializationCursorAdapter.adapt(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(); } /** * Returns all documents in the wave, aggregated from all the wavelets. * * @param wave to get the documents from. * @return map of all documents in the wave, aggregated from all the wavelets, * and keyed by their IDs. */ public static Map<String, BlipData> getAllDocuments(ClientWaveView wave) { final Map<String, BlipData> documents = Maps.newHashMap(); for (WaveletData wavelet : wave.getWavelets()) { for (String documentId : wavelet.getDocumentIds()) { documents.put(documentId, wavelet.getDocument(documentId)); } } return documents; } /** * Returns all participants in the wave, aggregated from all the wavelets. * * @param wave to get the participants from. * @return all participants in the wave, aggregated from all the wavelets. */ public static Set<ParticipantId> getAllParticipants(ClientWaveView wave) { final Set<ParticipantId> participants = new HashSet<ParticipantId>(); for (WaveletData wavelet : wave.getWavelets()) { participants.addAll(wavelet.getParticipants()); } return participants; } /** * 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 WaveletId.of(waveId.getDomain(), IdConstants.CONVERSATION_ROOT_WAVELET); } }