Package org.waveprotocol.wave.model.document.indexed

Source Code of org.waveprotocol.wave.model.document.indexed.SimpleAnnotationSet$ContainerTraverser

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

import org.waveprotocol.wave.model.document.AnnotationCursor;
import org.waveprotocol.wave.model.document.AnnotationInterval;
import org.waveprotocol.wave.model.document.RangedAnnotation;
import org.waveprotocol.wave.model.document.indexed.OffsetPoint.Finder;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.util.AnnotationIntervalImpl;
import org.waveprotocol.wave.model.document.util.GenericAnnotationCursor;
import org.waveprotocol.wave.model.document.util.GenericAnnotationIntervalIterable;
import org.waveprotocol.wave.model.operation.OpCursorException;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.EvaluableOffsetList;
import org.waveprotocol.wave.model.util.OffsetList;
import org.waveprotocol.wave.model.util.OffsetList.Container;
import org.waveprotocol.wave.model.util.Pair;
import org.waveprotocol.wave.model.util.Preconditions;
import org.waveprotocol.wave.model.util.ReadableStringMap;
import org.waveprotocol.wave.model.util.ReadableStringSet;
import org.waveprotocol.wave.model.util.StringMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

/**
* Simple implementation of an annotation set.
*
* The implementation is based on a single {@link OffsetList}, with each
* container in the list representing a contiguous range of identical key-value
* pairs.
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class SimpleAnnotationSet implements RawAnnotationSet<Object> {

  /**
   * "Immutable" map of values (can be abused, but please don't).
   */
  private static final class Values {
    private static final Values EMPTY = new Values();

    /** Cached hash value, used for fast equality comparison */
    private final int hash;
    /** The values */
    private final Map<String, Object> map;

    Values() {
      map = Collections.emptyMap();
      hash = map.hashCode();
    }

    Values(Values other, Changes changes) {
      map = new HashMap<String, Object>(other.map);
      for (Map.Entry<String, Pair<Integer, Object>> entry : changes.entrySet()) {
        if (entry.getValue() == null) {
          map.remove(entry.getKey());
        } else {
          map.put(entry.getKey(), entry.getValue().getSecond());
        }
      }
      hash = map.hashCode();
    }

    Object get(String key) {
      return map.get(key);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == null) {
        return false;
      } else if (obj instanceof Values) {
        Values v = (Values) obj;
        if (v.hash != hash) {
          return false;
        } else {
          return map.equals(v.map);
        }
      } else {
        return false;
      }
    }

    @Override
    public int hashCode() {
      return hash;
    }

    @Override
    public String toString() {
      return map.toString();
    }
  }

  /** Convenience type alias */
  @SuppressWarnings("serial")
  private static class Changes extends HashMap<String, Pair<Integer, Object>> {
  }

  /**
   * Represents a notification for our listeners, which we bunch up until the
   * end of a mutation.
   */
  private class Notification {
    int start;
    int end;
    String key;
    Object value;

    Notification(int start, int end, String key, Object value) {
      this.start = start;
      this.end = end;
      this.key = key;
      this.value = value;
    }

    void exe() {
      listener.onAnnotationChange(start, end, key, value);
    }
  }

  /**
   * Handy interface for traversing the list. Changes may be made to it in the
   * called methods, but only to the given container or previous containers. The
   * next and following containers may not be touched.
   */
  private interface ContainerTraverser {
    /** Called when the whole container is being traversed */
    void wholeContainer(OffsetList.Container<Values> container);

    /** Called when a portion of the container is being traversed */
    void partialContainer(OffsetList.Container<Values> container, int startOffset, int endOffset);

    /**
     * Called after the traversal is finished, with the container that would
     * have been traversed next
     */
    void finished(OffsetList.Container<Values> missedContainer);
  }

  /**
   * Handy finder instance.
   */
  private final Finder<Values> finder = OffsetPoint.finder();

  /**
   * Listener for changes.
   */
  private final AnnotationSetListener<Object> listener;

  /**
   * List of annotation ranges.
   */
  private final OffsetList<Values> ranges;

  /**
   * Current location during a mutation from the streaming interface.
   */
  private int currentLocation;

  /**
   * The current key-value pairs being "painted" during a mutation as the
   * currentLocation progresses.
   */
  private Changes currentChanges;

  /**
   * Values to the left of the current location, ignoring the effects of
   * annotation changes.
   */
  private Values oldValues;

  /**
   * Notifications being batched to be sent to the listener at the end of a
   * modification
   */
  private List<Notification> notifications;

  /**
   * @param listener listener for changes.
   */
  public SimpleAnnotationSet(AnnotationSetListener<Object> listener) {
    this.listener = listener;
    ranges = new EvaluableOffsetList<Values, Void>();
  }

  @Override
  public void begin() {
    reset();
    notifications = new ArrayList<Notification>();
  }

  @Override
  public void finish() {
    // For good measure
    reset();

    for (Notification notification : notifications) {
      notification.exe();
    }
  }

  private void reset() {
    currentChanges = new Changes();
    oldValues = Values.EMPTY;
    currentLocation = 0;
  }

  @Override
  public void skip(int skipSize) {
    if (skipSize > size() - currentLocation) {
      throw new OpCursorException("attempt to skip beyond end of document (cursor at "
          + currentLocation + ", size is " + size() + ", distance is " + skipSize + ")");
    }
    traverse(skipTraverser, skipSize);
    currentLocation += skipSize;
  }

  /**
   * Traverser used by skip, which "paints" the current key-value pairs into the
   * data structure, also handling merging and splitting of containers where
   * appropriate.
   */
  private final ContainerTraverser paintingTraverser = new ContainerTraverser() {
    public void partialContainer(OffsetList.Container<Values> container,
        int startOffset, int endOffset) {
      Values newValue = applyChanges(container.getValue());
      if (newValue.equals(container.getValue())) {
        // Don't split it up if the change has no effect to this container
        return;
      }

      if (startOffset > 0) {
        container = container.split(startOffset, container.getValue());
      }
      int size = endOffset - startOffset;
      if (size < container.size()) {
        container.split(size, container.getValue());
        container.setValue(newValue);
      } else {
        container.setValue(newValue);
      }
    }

    public void wholeContainer(OffsetList.Container<Values> container) {
      container.setValue(applyChanges(container.getValue()));
      maybeMergeWithPrevious(container);
    }

    public void finished(OffsetList.Container<Values> missedContainer) {
      maybeMergeWithPrevious(missedContainer);
    }
  };

  private final ContainerTraverser skipTraverser = new ContainerTraverser() {
    public void partialContainer(Container<Values> container, int startOffset, int endOffset) {
      oldValues = container.getValue();
      paintingTraverser.partialContainer(container, startOffset, endOffset);
    }

    public void wholeContainer(Container<Values> container) {
      oldValues = container.getValue();
      paintingTraverser.wholeContainer(container);
    }

    public void finished(Container<Values> missedContainer) {
      paintingTraverser.finished(missedContainer);
    }
  };

  @Override
  public void insert(int insertSize) {
    // Semantics: inserts always push an annotation boundary.
    OffsetPoint<Values> p = ranges.performActionAt(currentLocation, finder);
    OffsetList.Container<Values> current = p.getContainer();

    if (p.getOffset() > 0) {
      current = current.split(p.getOffset(), current.getValue());
    }

    current.insertBefore(oldValues, insertSize);
    traverse(paintingTraverser, insertSize);
    currentLocation += insertSize;


  }

  @Override
  public void delete(int deleteSize) {
    if (deleteSize > size() - currentLocation) {
      throw new OpCursorException("attempt to delete beyond end of document (cursor at "
          + currentLocation + ", size is " + size() + ", deleteSize is " + deleteSize + ")");
    }
    traverse(deleteTraverser, deleteSize);
  }

  /**
   * Traverser for deleting containers or parts of containers across a range.
   */
  private final ContainerTraverser deleteTraverser = new ContainerTraverser() {
    public void partialContainer(OffsetList.Container<Values> container,
        int startOffset, int endOffset) {
      oldValues = container.getValue();
      container.increaseSize(startOffset - endOffset);
    }

    public void wholeContainer(OffsetList.Container<Values> container) {
      oldValues = container.getValue();
      container.remove();
    }

    public void finished(OffsetList.Container<Values> missedContainer) {
      maybeMergeWithPrevious(missedContainer);
    }
  };

  @Override
  public void startAnnotation(String key, Object value) {
    maybeNoteChange(key);
    currentChanges.put(key, new Pair<Integer, Object>(currentLocation, value));
  }

  @Override
  public void endAnnotation(String key) {
    assert currentChanges.containsKey(key);
    maybeNoteChange(key);
    currentChanges.remove(key);
  }

  /**
   * Check if a notification is relevant at this point for the given key, and if
   * so, note it for later.
   *
   * @param key
   */
  private void maybeNoteChange(String key) {
    if (listener == null) {
      return;
    }
    if (currentChanges.containsKey(key)) {
      Pair<Integer, Object> info = currentChanges.get(key);

      notifications.add(new Notification(info.getFirst().intValue(), currentLocation, key, info
          .getSecond()));
    }
  }

  /**
   * Return a new value with the currentChanges applied to the given value
   */
  private Values applyChanges(Values values) {
    return currentChanges.isEmpty() ? values : new Values(values, currentChanges);
  }

  /**
   * Merge the given container with the previous container, if they have equal
   * values
   */
  private void maybeMergeWithPrevious(OffsetList.Container<Values> container) {
    OffsetList.Container<Values> previous = container.getPreviousContainer();
    if (previous != ranges.sentinel() && previous.getValue().equals(container.getValue())) {
      container.increaseSize(previous.size());
      previous.remove();
    }
  }

  /**
   * @see ContainerTraverser
   */
  private void traverse(ContainerTraverser traverser, int distance) {
    OffsetPoint<Values> p = ranges.performActionAt(currentLocation, finder);
    int offset = p.getOffset();
    OffsetList.Container<Values> container = p.getContainer();

    while (distance > 0) {
      int containerMoveSize = Math.min(distance, container.size() - offset);
      OffsetList.Container<Values> next = container.getNextContainer();
      if (offset > 0) {
        traverser.partialContainer(container, offset, offset + containerMoveSize);
      } else if (containerMoveSize == container.size()) {
        traverser.wholeContainer(container);
      } else {
        traverser.partialContainer(container, 0, containerMoveSize);
      }

      offset = 0;
      distance -= containerMoveSize;
      container = next;
    }

    traverser.finished(container);
  }

  // Reader methods

  @Override
  public int size() {
    return ranges.size();
  }

  @Override
  public Object getAnnotation(int location, String key) {
    Preconditions.checkElementIndex(location, size());
    checkKeyNotNull(key);

    Values values = ranges.performActionAt(location, finder).getValue();
    return values == null ? null : values.get(key);
  }

  @Override
  public int firstAnnotationChange(int start, int end, String key, Object fromValue) {
    Preconditions.checkPositionIndexes(start, end, size());
    checkKeyNotNull(key);
    start = Math.max(0, start);
    end = Math.min(end, ranges.size());

    OffsetPoint<Values> point = ranges.performActionAt(start, finder);
    OffsetList.Container<Values> container = point.getContainer();
    int offset = point.getOffset();

    int location = start;
    while (location < end) {
      if (!eq(getValue(container, key), fromValue)) {
        return location;
      }

      if (container == ranges.sentinel()) {
        break;
      }

      location += container.size() - offset;
      container = container.getNextContainer();
      offset = 0;
    }

    return -1;
  }

  @Override
  public int lastAnnotationChange(int start, int end, String key, Object fromValue) {
    Preconditions.checkPositionIndexes(start, end, size());
    checkKeyNotNull(key);
    start = Math.max(0, start);
    end = Math.min(end, ranges.size());

    OffsetPoint<Values> point = ranges.performActionAt(end, finder);
    OffsetList.Container<Values> container = point.getContainer();
    int offset = point.getOffset();
    if (offset == 0) {
      container = container.getPreviousContainer();
      offset = container == ranges.sentinel() ? 0 : container.size();
    }

    int location = end;
    while (location > start) {
      if (!eq(getValue(container, key), fromValue)) {
        return location;
      }

      if (container == null) {
        break;
      }

      location -= offset;
      container = container.getPreviousContainer();
      offset = container == ranges.sentinel() ? 0 : container.size();
    }

    return -1;
  }

  @Override
  public AnnotationCursor annotationCursor(int start, int end, ReadableStringSet keys) {
    if (keys == null) {
      throw new RuntimeException("not implemented");
    }
    return new GenericAnnotationCursor<Object>(this, start, end, keys);
  }

  private Object getValue(OffsetList.Container<Values> container, String key) {
    // If only we had the Maybe monad in Java...
    return (container == ranges.sentinel()) ? null :
        (container.getValue() == null) ? null : container.getValue().get(key);
  }

  private boolean eq(Object a, Object b) {
    return a == null ? b == null : a.equals(b);
  }

  protected void checkKeyNotNull(String key) {
    Preconditions.checkNotNull(key, "key must not be null");
  }

  @Override
  public void forEachAnnotationAt(int location, ReadableStringMap.ProcV<Object> callback) {
    throw new RuntimeException("not implemented");
  }

  private class RangesIterator implements Iterator<AnnotationInterval<Object>> {

    Container<Values> next = ranges.firstContainer();

    @Override
    public boolean hasNext() {
      return next != ranges.sentinel();
    }

    @Override
    public AnnotationInterval<Object> next() {
      if (!hasNext()) {
        throw new NoSuchElementException("no more intervals");
      }
      StringMap<Object> annotations = CollectionUtils.createStringMap();
      annotations.putAll(next.getValue().map);
      StringMap<Object> diffFromLeft = CollectionUtils.createStringMap();

      for (Map.Entry<String, Object> e : next.getValue().map.entrySet()) {
        String key = e.getKey();
        Object value = e.getValue();
        if ((next.getPreviousContainer() == ranges.sentinel())) {
          if (value != null) {
            diffFromLeft.put(key, value);
          }
        } else {
          if (!eq(value, next.getPreviousContainer().getValue().get(key))) {
            diffFromLeft.put(key, value);
          }
        }
      }

      if (next.getPreviousContainer() != ranges.sentinel()) {
        // Find and add value=null entries that may be implicit.
        for (Map.Entry<String, Object> e : next.getPreviousContainer().getValue().map.entrySet()) {
          String key = e.getKey();
          Object value = e.getValue();
          if (value != null && !next.getValue().map.containsKey(key)) {
            diffFromLeft.put(key, null);
            annotations.put(key, null);
          }
        }
      }

      AnnotationInterval<Object> i = new AnnotationIntervalImpl<Object>(next.offset(),
          next.offset() + next.size(), annotations, diffFromLeft);

      next = next.getNextContainer();

      return i;

    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException("removing an annotation interval is not supported");
    }

  }

  @Override
  public Iterable<AnnotationInterval<Object>> annotationIntervals(int start, int end,
      ReadableStringSet keys) {
    if (keys == null) {
      if (start > 0 || end < size()) {
        throw new RuntimeException("not implemented");
      }
      return new Iterable<AnnotationInterval<Object>>() {
        @Override
        public Iterator<AnnotationInterval<Object>> iterator() {
          return new RangesIterator();
        }
      };
    }
    return new GenericAnnotationIntervalIterable<Object>(this, start, end, keys);
  }

  @Override
  public Iterable<RangedAnnotation<Object>> rangedAnnotations(int start, int end,
      ReadableStringSet keys) {
    throw new RuntimeException("not implemented");
  }

  @Override
  public ReadableStringSet knownKeysLive() {
    throw new UnsupportedOperationException("knownKeysLive");
  }

  @Override
  public ReadableStringSet knownKeys() {
    throw new UnsupportedOperationException("knownKeys");
  }

  @Override
  public String getInherited(String key) {
    throw new UnsupportedOperationException("getInherited");
  }
}
TOP

Related Classes of org.waveprotocol.wave.model.document.indexed.SimpleAnnotationSet$ContainerTraverser

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.