Package org.waveprotocol.wave.model.adt.docbased

Source Code of org.waveprotocol.wave.model.adt.docbased.DocumentBasedBasicSet

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

import org.waveprotocol.wave.model.adt.AbstractObservableBasicSet;
import org.waveprotocol.wave.model.document.ObservableMutableDocument;
import org.waveprotocol.wave.model.document.operation.Attributes;
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.util.CollectionUtils;
import org.waveprotocol.wave.model.util.ElementListener;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.Serializer;

import java.util.Map;
import java.util.Set;

/**
* Implementation of a basic set, using elements in a region of a concurrent
* document.
*
* Does not support null values.
*
*/
public final class DocumentBasedBasicSet<E, T> extends AbstractObservableBasicSet<T> {

  /** Backing document service. */
  private final DocumentEventRouter<? super E, E, ?> router;

  /** Serializer for converting between attribute values and entry values. */
  private final Serializer<T> serializer;

  /** Element containing set entries. */
  private final E container;

  /** Name to use for entry elements. */
  private final String entryTagName;

  /** Name to use for the value attribute. */
  private final String valueAttrName;

  /** Maps from value held, to the element that holds it. */
  private final Map<T, E> valueElements = CollectionUtils.newHashMap();

  /** Child elements of the container we wish would go away. */
  private final Set<E> obsoleteElements = CollectionUtils.newHashSet();

  /**
   * Creates a basic set. The returned object must be init()ialized before it is
   * valid.
   *
   * @param router router for the document holding the set state
   * @param entryContainer element in which entry elements should be created
   * @param serializer converter between strings and values
   * @param entryTagName name to use for entry elements
   * @param valueAttrName name to use for value attributes
   */
  private DocumentBasedBasicSet(DocumentEventRouter<? super E, E, ?> router,
      E entryContainer, Serializer<T> serializer,
      String entryTagName, String valueAttrName) {
    this.router = router;
    this.container = entryContainer;
    this.serializer = serializer;
    this.entryTagName = entryTagName;
    this.valueAttrName = valueAttrName;
  }

  /**
   * Creates a document-based basic set.
   *
   * @see #DocumentBasedBasicSet(DocumentEventRouter, Object, Serializer,
   *      String, String)
   */
  public static <E, C extends Comparable<C>> DocumentBasedBasicSet<E, C> create(
      DocumentEventRouter<? super E, E, ?> router, E entryContainer,
      Serializer<C> serializer, String entryTagName, String valueAttrName) {
    return new DocumentBasedBasicSet<E, C>(
        router, entryContainer, serializer, entryTagName, valueAttrName).init();
  }

  private ObservableMutableDocument<? super E, E, ?> getDocument() {
    return router.getDocument();
  }

  /**
   * Initializes a new object. Call after construction is complete.
   */
  private DocumentBasedBasicSet<E, T> init() {
    // Plumb events through
    router.addChildListener(container, new ElementListener<E>() {
      @Override
      public void onElementAdded(E element) {
        handleElementAdded(element);
      }

      @Override
      public void onElementRemoved(E element) {
        handleElementRemoved(element);
      }
    });

    // Call handleElementAdded() to notify this class of existing data
    ObservableMutableDocument<? super E, E, ?> document = getDocument();
    E curr = DocHelper.getFirstChildElement(document, container);
    E next;

    while (curr != null) {
      next = DocHelper.getNextSiblingElement(document, curr);
      handleElementAdded(curr);
      curr = next;
    }
    return this;
  }

  //
  // Methods from BasicSet interface. State is read only from the valueElements
  // and obsoleteElements members, and state is changed only by manipulating the
  // underlying document state.
  //

  @Override
  public Iterable<T> getValues() {
    return CollectionUtils.newArrayList(valueElements.keySet());
  }

  @Override
  public boolean contains(T value) {
    return valueElements.containsKey(value);
  }

  @Override
  public void add(T value) {
    Preconditions.checkNotNull(value, "value must not be null");
    // Add an element to represent the value
    if (!valueElements.containsKey(value)) {
      Attributes attrs = new AttributesImpl(valueAttrName, serializer.toString(value));
      getDocument().createChildElement(container, entryTagName, attrs);
      deleteObsoleteElements();
    }
  }

  @Override
  public void remove(T value) {
    if (valueElements.containsKey(value)) {
      deleteObsoleteElements();
      getDocument().deleteNode(valueElements.get(value));
    }
  }

  @Override
  public void clear() {
    if (!valueElements.isEmpty()) {
      deleteObsoleteElements();
      // Remove every container element that corresponds to a value
      ObservableMutableDocument<? super E, E, ?> document = getDocument();
      while (!valueElements.isEmpty()) {
        document.deleteNode(valueElements.values().iterator().next());
      }
    }
  }

  //
  // Methods that respond to changes in the underlying document. These methods
  // update valueElements and obsoleteElements, and trigger any listeners to
  // this BasicSet.
  //

  /**
   * Handles a new element being added to the container. If it represents a new
   * value, that value is recorded. If it represents a value already in this
   * container, either the old element or the new element is marked obsolete.
   * The element marked obsolete is the one that appears later in the document.
   */
  private void handleElementAdded(E newElement) {
    ObservableMutableDocument<? super E, E, ?> document = getDocument();
    assert container.equals(document.getParentElement(newElement));
    if (!entryTagName.equals(document.getTagName(newElement))) {
      return;
    }

    T value = valueOf(newElement);

    E oldEntry = valueElements.get(value);
    if (oldEntry == null) {
      // Entry is for a new value - add it to the element map and fire an event
      // to collection listeners
      valueElements.put(value, newElement);
      fireOnValueAdded(value);
    } else if (document.getLocation(oldEntry) < document.getLocation(newElement)) {
      // newEntry is not needed, so mark it obsolete
      obsoleteElements.add(newElement);
    } else {
      // oldEntry is no needed, so mark it obsoleted and use the new one instead
      obsoleteElements.add(oldEntry);
      valueElements.put(value, newElement);
    }
  }

  /**
   * Handles an element being removed from the container.
   */
  private void handleElementRemoved(E deletedElement) {
    // To start with though, we do a quick check to see if deletedElement has
    // the same tag name as value elements.
    if (!entryTagName.equals(getDocument().getTagName(deletedElement))) {
      // Exit, because deletedElement definitely isn't part of this container.
      return;
    }

    if (obsoleteElements.remove(deletedElement)) {
      // Element was obsolete, so ignore the removal event
      return;
    }

    T value = valueOf(deletedElement);
    E existingElement = valueElements.get(value);
    if (existingElement != deletedElement) {
      // deleted element was not part of the backing store for this map, so
      // ignore it
    } else {
      valueElements.remove(value);
      fireOnValueRemoved(value);
    }
  }

  //
  // Helper methods.
  //

  /**
   * Deletes those elements that represent doubled-up values. This condition is
   * detected in the handleElementAdded() method, but the actual deletion is
   * only performed after some kind of write was made to the document via a call
   * to {@link #add(Object)}, {@link #remove(Object)} or {@link #clear()}.
   *
   * <p>
   * Delaying document mutation ensures that agents and clients that are only
   * listening to a document do not send mutation operations, preventing issues
   * of writing to read-only wavelets and minimizing the number of concurrent
   * writers sending ops to a wave server.
   *
   * <p>
   * When removing values from the container, obsolete elements ought to be
   * deleted <i>before</i> removing valid elements. This ensures that other
   * agents do not see an intermediate state where an obsolete element is
   * present, but a valid one is not.
   *
   * <p>
   * When adding values to the container, obsolete elements ought to be deleted
   * <i>after</i> adding valid elements. This is to maintain symmetry with
   * removal, thereby pleasing all right-thinking engineers.
   */
  private void deleteObsoleteElements() {
    ObservableMutableDocument<? super E, E, ?> document = getDocument();
    while (!obsoleteElements.isEmpty()) {
      // Delete the obsolete element from the document. The element removed
      // event callback will remove the item from the obsoleteElements set.
      document.deleteNode(obsoleteElements.iterator().next());
    }
  }

  /**
   * Gets the value of an entry.
   *
   * @param element entry to evaluate
   * @return the value embedded in the element.
   */
  private T valueOf(E element) {
    return serializer.fromString(getDocument().getAttribute(element, valueAttrName));
  }
}
TOP

Related Classes of org.waveprotocol.wave.model.adt.docbased.DocumentBasedBasicSet

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.