Package org.waveprotocol.wave.model.conversation

Source Code of org.waveprotocol.wave.model.conversation.DocumentBasedManifest

/**
* 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.conversation;

import com.google.common.annotations.VisibleForTesting;

import org.waveprotocol.wave.model.adt.ObservableStructuredValue;
import org.waveprotocol.wave.model.adt.docbased.DocumentBasedStructuredValue;
import org.waveprotocol.wave.model.adt.docbased.Factory;
import org.waveprotocol.wave.model.adt.docbased.Initializer;
import org.waveprotocol.wave.model.document.Doc;
import org.waveprotocol.wave.model.document.Document;
import org.waveprotocol.wave.model.document.ObservableMutableDocument;
import org.waveprotocol.wave.model.document.Doc.E;
import org.waveprotocol.wave.model.document.util.DefaultDocumentEventRouter;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.document.util.DocumentEventRouter;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.Serializer;

import java.util.Map;

/**
* Manifest backed by a mutable document.
*
* Example Manifest XML:
*
* <conversation sort="m" ...private reply anchor stuff>
<blip id="b1"></blip>            <!-- the "root" blip -->
<blip id="b2">
*    <thread id="b2t1">             <!-- a reply to b2 -->
*      <blip id="b2t1b1"></blip>
*    </thread>
*    <thread id="b2t2" inline="1">  <!-- an inline reply to b2 -->
*      ...
*    </thread>
*   </blip...>
* </conversation>
*
* @author anorth@google.com (Alex North)
*/
public final class DocumentBasedManifest implements ObservableManifest {

  public enum AnchorKey {
    WAVELET { @Override public String toString() { return "anchorWavelet"; } },
    BLIP { @Override public String toString() { return "anchorBlip"; } },
  }

  /** {@link Factory} for manifests. */
  static final Factory<Doc.E, DocumentBasedManifest, Void> FACTORY =
      new Factory<Doc.E, DocumentBasedManifest, Void>() {
        @Override
        public DocumentBasedManifest adapt(DocumentEventRouter<? super Doc.E, Doc.E, ?> router,
            E element) {
          return DocumentBasedManifest.createOnExisting(router, element);
        }

        @Override
        public Initializer createInitializer(Void initialState) {
          // NOTE(anorth): We could set the private reply anchor info here.
          return new Initializer() {
            public void initialize(Map<String, String> target) {
            }
          };
        }
      };

  /**
   * Checks whether a manifest could be created on a document.
   */
  public static boolean documentHasManifest(Document doc) {
    // True if the document has a top-level <MANIFEST_TOP_TAG> element.
    // The schema implies this is the only possible top element.
    Doc.E top = DocHelper.getFirstChildElement(doc, doc.getDocumentElement());
    return (top != null) && doc.getTagName(top).equals(MANIFEST_TOP_TAG);
  }

  /**
   * Deletes the toplevel conversation manifest in the given document.
   */
  public static void delete(Document manifestDoc) {
    Doc.E top = DocHelper.expectAndGetFirstTopLevelElement(manifestDoc, MANIFEST_TOP_TAG);
    manifestDoc.deleteNode(top);
  }

  /**
   * Initialises a manifest document such that
   * {@link #documentHasManifest(Document)} is true.
   *
   * @param doc document in which to initialise conversation structure
   * @precondition {@code !documentHasManifest(doc)}
   */
  static void initialiseDocumentManifest(Document doc) {
    // The schema implies that any other top-level elements must be
    // manifest roots, thus if the precondition holds then the document
    // must be empty.
    DocHelper.createFirstTopLevelElement(doc, MANIFEST_TOP_TAG);
  }

  /**
   * Creates a document-based manifest backed by a document with existing
   * conversation structure.
   *
   * @param router event router for the document on which to base the manifest
   * @precondition {@code documentHasManifest(doc)}
   */
  static <E> DocumentBasedManifest createOnExisting(
      DocumentEventRouter<? super E, E, ?> router, E top) {
    Preconditions.checkArgument(router.getDocument().getTagName(top).equals(MANIFEST_TOP_TAG),
        "Invalid manifest top tag %s", router.getDocument().getTagName(top));
    return new DocumentBasedManifest(
        DocumentBasedManifestThread.create(router, top),
        DocumentBasedStructuredValue.create(router, top, Serializer.STRING, AnchorKey.class));
  }

  /**
   * Creates a document-based manifest backed by an empty document.
   *
   * @param doc the document on which to base the manifest
   * @precondition {@code !documentHasManifest(doc)}
   */
  @VisibleForTesting
  public static <E> DocumentBasedManifest createNew(
      ObservableMutableDocument<? super E, E, ?> doc) {
    // If the precondition holds then the document must be empty.
    E top = DocHelper.createFirstTopLevelElement(doc, MANIFEST_TOP_TAG);
    DocumentEventRouter<? super E, E, ?> router = DefaultDocumentEventRouter.create(doc);
    return new DocumentBasedManifest(
        DocumentBasedManifestThread.create(router, top),
        DocumentBasedStructuredValue.create(router, top, Serializer.STRING, AnchorKey.class));
  }

  /** The root element of the conversation manifest. */
  public static final String MANIFEST_TOP_TAG = "conversation";

  /** The manifest anchor value. */
  private final ObservableStructuredValue<AnchorKey, String> anchor;

  /** The root thread. */
  private final ObservableManifestThread rootThread;

  private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();

  /**
   * Creates a document-based manifest. Package-private for testing.
   *
   * @param rootThread the root thread
   * @param anchor value representing the conversation anchor
   */
  DocumentBasedManifest(ObservableManifestThread rootThread,
      ObservableStructuredValue<AnchorKey, String> anchor) {
    ObservableStructuredValue.Listener<AnchorKey, String> anchorListener =
        new ObservableStructuredValue.Listener<AnchorKey, String>() {
          @Override
          public void onValuesChanged(Map<AnchorKey, ? extends String> oldValues,
              Map<AnchorKey, ? extends String> newValues) {
            triggerOnAnchorChanged(oldValues, newValues);
          }

          @Override
          public void onDeleted() {
            // TODO(anorth): remove onDeleted from this interface.
          }
        };

    this.rootThread = rootThread;
    this.anchor = anchor;
    this.anchor.addListener(anchorListener);
  }

  @Override
  public AnchorData getAnchor() {
    return new AnchorData(anchor.get(AnchorKey.WAVELET), anchor.get(AnchorKey.BLIP));
  }

  @Override
  public void setAnchor(AnchorData newAnchor) {
    anchor.set(CollectionUtils.immutableMap(AnchorKey.WAVELET, newAnchor.getConversationId(),
        AnchorKey.BLIP, newAnchor.getBlipId()));
  }

  @Override
  public ObservableManifestThread getRootThread() {
    return rootThread;
  }

  @Override
  public void addListener(Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Listener listener) {
    listeners.remove(listener);
  }

  private void triggerOnAnchorChanged(Map<AnchorKey, ? extends String> oldValues,
      Map<AnchorKey, ? extends String> newValues) {
    // Get old values. If a component didn't change use its current value.
    String oldAnchorWavelet = (oldValues.containsKey(AnchorKey.WAVELET)) ?
        oldValues.get(AnchorKey.WAVELET) : anchor.get(AnchorKey.WAVELET);
    String oldAnchorBlipId = (oldValues.containsKey(AnchorKey.BLIP)) ?
        oldValues.get(AnchorKey.BLIP) : anchor.get(AnchorKey.BLIP);

    // Get new/current values from the underlying state. If a component didn't
    // change it will not be present in newValues.
    String newAnchorWavelet = anchor.get(AnchorKey.WAVELET);
    String newAnchorBlipId = anchor.get(AnchorKey.BLIP);

    AnchorData oldAnchor = new AnchorData(oldAnchorWavelet, oldAnchorBlipId);
    AnchorData newAnchor = new AnchorData(newAnchorWavelet, newAnchorBlipId);

    if (!oldAnchor.equals(newAnchor)) {
      for (Listener l : listeners) {
        l.onAnchorChanged(oldAnchor, newAnchor);
      }
    }
  }
}
TOP

Related Classes of org.waveprotocol.wave.model.conversation.DocumentBasedManifest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.