Package org.waveprotocol.wave.client.paging

Source Code of org.waveprotocol.wave.client.paging.ActiveRegion$EndPoint

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

import com.google.common.base.Preconditions;

import org.waveprotocol.wave.client.common.util.LinkedSequence;
import org.waveprotocol.wave.client.common.util.MathUtil;
import org.waveprotocol.wave.client.paging.Traverser.BlockSide;
import org.waveprotocol.wave.client.paging.Traverser.MoveablePoint;
import org.waveprotocol.wave.client.paging.Traverser.Point;
import org.waveprotocol.wave.client.paging.Traverser.SimplePoint;
import org.waveprotocol.wave.model.util.CollectionUtils;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;

/**
* Maintains a continuous active (paged-in) region within block tree.
*
*/
public final class ActiveRegion {

  /**
   * A point defining the start of an active region.
   */
  static final class StartPoint extends MoveablePoint {

    private StartPoint(BlockSide side, Block block) {
      super(side, block);
    }

    static StartPoint startOf(Block block) {
      return new StartPoint(BlockSide.START, block);
    }

    static StartPoint endOf(Block block) {
      return new StartPoint(BlockSide.END, block);
    }

    static StartPoint nowhere() {
      return new StartPoint(null, null);
    }

    static StartPoint at(Point point) {
      return new StartPoint(point.side, point.block);
    }

    /**
     * Moves to the next point, adding any blocks left behind into a
     * collection.
     *
     * @param toExclude collector of blocks left behind
     */
    void trawlNext(Collection<? super Block> toExclude) {
      // If this point moves over a block end, that block becomes excluded.
      if (side == BlockSide.END) {
        toExclude.add(block);
      }
      next();
    }

    /**
     * Moves to the previous point, adding any new blocks included into a
     * collection.
     *
     * @param toInclude collector of new blocks
     */
    void trawlPrevious(Collection<? super Block> toInclude) {
      // If this point moves into a block end, that block becomes included.
      previous();
      if (side == BlockSide.END) {
        toInclude.add(block);
      }
    }

    /**
     * Moves through next points until the last point before a given position,
     * adding any blocks left behind into a collection.
     *
     * @param position location to move to, but not beyond
     * @param toExclude collector of new blocks encountered
     */
    void trawlNextUntil(double position, Collection<? super Block> toExclude) {
      while (nextIfBefore(position, toExclude)) {
        // Condition does the work.
      }
    }


    /**
     * Moves through previous points until the first point before a given position,
     * adding any new blocks included into a collection.
     *
     * @param position location to move to, but not beyond
     * @param toInclude collector of new blocks encountered
     */
    void trawlPreviousUntil(double position, Collection<? super Block> toInclude) {
      while (previousIfAfter(position, toInclude)) {
        // Condition does the work.
      }
    }

    /**
     * Moves to the next point, if the next point is before a position.
     *
     * @param position boundary
     * @param toExclude
     * @return true if moved.
     */
    private boolean nextIfBefore(double position, Collection<? super Block> toExclude) {
      if (!hasNext()) {
        return false;
      }

      Block oldBlock = block;
      BlockSide oldSide = side;

      next();

      // Moved too far?
      if (absoluteLocation() >= position) {
        // Oops. Backtrack.
        previous();
        return false;
      } else {
        // Moved beyond the end of a block, so that block is now excluded.
        if (oldSide == BlockSide.END) {
          toExclude.add(oldBlock);
        }
        return true;
      }
    }

    /**
     * Moves to the previous point, if this point is at, or after, a position.
     *
     * @param position boundary
     * @return true if moved.
     */
    private boolean previousIfAfter(double position, Collection<? super Block> toInclude) {
      if (hasPrevious() && absoluteLocation() >= position) {
        trawlPrevious(toInclude);
        return true;
      } else {
        return false;
      }
    }
  }

  /**
   * A point defining the end of an active region.
   */
  final static class EndPoint extends MoveablePoint {

    private EndPoint(BlockSide side, Block block) {
      super(side, block);
    }

    static EndPoint startOf(Block block) {
      return new EndPoint(BlockSide.START, block);
    }

    static EndPoint endOf(Block block) {
      return new EndPoint(BlockSide.END, block);
    }

    static EndPoint nowhere() {
      return new EndPoint(null, null);
    }

    static EndPoint at(Point point) {
      return new EndPoint(point.side, point.block);
    }

    /**
     * Moves to the previous point, adding any blocks left behind into a
     * collection.
     *
     * @param toExclude collector of blocks left behind
     */
    void trawlPrevious(Collection<? super Block> toExclude) {
      // If this point moves over a block start, that block becomes excluded.
      if (side == BlockSide.START) {
        toExclude.add(block);
      }
      previous();
    }

    /**
     * Moves to the next point, adding any new blocks included into a
     * collection.
     *
     * @param toInclude collector of new blocks
     */
    void trawlNext(Collection<? super Block> toInclude) {
      // If this point moves into a block start, that block becomes included.
      next();
      if (side == BlockSide.START) {
        toInclude.add(block);
      }
    }

    /**
     * Moves through previous points until the first point after a given
     * position, adding any blocks left behind into a collection.
     *
     * @param position location to move to, but not beyond
     * @param toExclude collector of blocks left behind
     */
    void trawlPreviousUntil(double position, Collection<? super Block> toExclude) {
      while (previousIfAfter(position, toExclude)) {
        // Condition does the work.
      }
    }

    /**
     * Moves through next points until the first point after a given position,
     * adding any new blocks included into a collection.
     *
     * @param position location to move to, but not beyond
     * @param toInclude collector of new blocks encountered
     */
    void trawlNextUntil(double position, Collection<? super Block> toInclude) {
      while (nextIfBefore(position, toInclude)) {
        // Condition does the work.
      }
    }

    /**
     * Moves to the previous point, if the previous point is after a position.
     *
     * @param position boundary
     * @param toExclude
     * @return true if moved.
     */
    private boolean previousIfAfter(double position, Collection<? super Block> toExclude) {
      if (!hasPrevious()) {
        return false;
      }

      Block oldBlock = block;
      BlockSide oldSide = side;

      previous();

      // Moved too far?
      if (absoluteLocation() <= position) {
        // Oops. Backtrack.
        next();

        return false;
      } else {
        // Moved beyond the end of a block, so that block is now excluded.
        if (oldSide == BlockSide.START) {
          toExclude.add(oldBlock);
        }
        return true;
      }
    }

    /**
     * Moves to the next point, if this point is at, or before, a position.
     *
     * @param position boundary
     * @return true if moved.
     */
    private boolean nextIfBefore(double position, Collection<? super Block> toInclude) {
      if (hasNext() && absoluteLocation() <= position) {
        trawlNext(toInclude);
        return true;
      } else {
        return false;
      }
    }
  }

  /** Start of the active region. */
  private final StartPoint start;

  /** End of the active region. */
  private final EndPoint end;

  /** Root block of pageable tree. */
  private final Block root;

  /** Paging queues. */
  private final Queue<Block> toPageIn = CollectionUtils.createQueue();
  private final Queue<Block> toPageOut = CollectionUtils.createQueue();

  private ActiveRegion(Block root, StartPoint start, EndPoint end) {
    this.root = root;
    this.start = start;
    this.end = end;
  }

  /**
   * Creates an active region.
   *
   * @param root root of the block tree to activate.
   * @return a new active region.
   */
  public static ActiveRegion over(Block root) {
    return new ActiveRegion(root, StartPoint.nowhere(), EndPoint.nowhere());
  }

  /** @return true if a region has been activated. */
  private boolean isActive() {
    return start.isActive();
  }

  /**
   * Invalidates any positions cached in the terminal points. This needs to be
   * called on all public entry points whose control flow queries positions.
   */
  private void invalidatePositionCache() {
    start.invalidateCachedOrigin();
    end.invalidateCachedOrigin();
  }

  /**
   * @return the location of the activated part of the content.
   */
  public Region getActiveRegion() {
    invalidatePositionCache();
    return isActive() ? RegionImpl.at(start.absoluteLocation(), end.absoluteLocation()) : null;
  }

  /**
   * Grows the active region at the end.
   *
   * @param n number of blocks to page in
   */
  public double growDown(int n) {
    assert toPageIn.isEmpty()// Reminder that every public method flushes this.
    PagingDebugHelper.maybeCheckBlocks(root);
    Preconditions.checkState(isActive(), "No active region");

    while (toPageIn.size() < n && end.hasNext()) {
      end.trawlNext(toPageIn);
    }
    page();
    return 0;
  }

  /**
   * Grows the active region at the start.
   *
   * @param n number of blocks to page in
   * @return the change in
   */
  public double growUp(int n) {
    assert toPageIn.isEmpty(); // Reminder that every public method flushes this.
    PagingDebugHelper.maybeCheckBlocks(root);
    Preconditions.checkState(isActive(), "No active region");

    double oldEndLocation = end.absoluteLocation();
    while (toPageIn.size() < n && start.hasPrevious()) {
      start.trawlPrevious(toPageIn);
    }
    page();
    double newEndLocation = end.absoluteLocation();
    return newEndLocation - oldEndLocation;
  }

  /**
   * Ensures that all blocks within a given region are paged in, and all those
   * outside the region are paged out.
   *
   * @param viewport viewport region
   * @return by how much all preserved points (points that are active both
   *         before and after this method call) have shifted due to activation.
   *         If there are no preserved points, returns {@code 0}.
   */
  public double activate(Region viewport) {
    PagingDebugHelper.maybeCheckBlocks(root);
    PagingDebugHelper.enterActivate();

    Point toPreserve;  // A point whose location should be preserved through paging.
    Region active = getActiveRegion();
    if (active == null) {
      // Initialize
      Point init = Traverser.locateStartWithin(root, viewport.getStart());
      if (init == null) {
        init = Traverser.locateEndWithin(root, viewport.getStart());
      }
      init(init);
      expandTo(viewport);
      // Preserve the start of the viewport. No particular reason.
      toPreserve = start;
    } else {
      // Adjust from previous state.
      switch (OverlapKind.compare(viewport, active)) {
        case FULLY_AFTER:
          Point newStart = Traverser.locateStartAfter(end, viewport.getStart());
          shrinkToEnd();
          moveActivePoint(newStart);
          expandTo(viewport);
          toPreserve = start;
          break;
        case FULLY_BEFORE:
          Point newEnd = Traverser.locateEndBefore(start, viewport.getEnd());
          shrinkToStart();
          moveActivePoint(newEnd);
          expandTo(viewport);
          toPreserve = end;
          break;
        case ENCLOSED:
          toPreserve = SimplePoint.at(start);
          shrinkStartUntil(viewport.getStart());
          shrinkEndUntil(viewport.getEnd());
          break;
        case ENCLOSES:
          growStartUntil(viewport.getStart());
          growEndUntil(viewport.getEnd());
          toPreserve = SimplePoint.at(start);
          break;
        case INTERSECTS_BEFORE:
          toPreserve = SimplePoint.at(start);
          growStartUntil(viewport.getStart());
          shrinkEndUntil(viewport.getEnd());
          break;
        case INTERSECTS_AFTER:
          shrinkStartUntil(viewport.getStart());
          growEndUntil(viewport.getEnd());
          toPreserve = SimplePoint.at(start);
          break;
        default:
          throw new RuntimeException("invalid enum value");
      }
    }

    assert enclosesClipped(viewport);
    assert !new HashSet<Block>(toPageIn).removeAll(toPageOut);

    double shift = 0;
    if (!toPageIn.isEmpty() || !toPageOut.isEmpty()) {
      double oldLocation = toPreserve.absoluteLocation();
      page();
      double newLocation = toPreserve.absoluteLocation();
      shift += newLocation - oldLocation;

      // Paging may have changed block sizes, so another round of activation is
      // necessary.
      shift += activate(RegionImpl.at(viewport).moveBy(shift));
    } else {
      return 0;
    }

    PagingDebugHelper.leaveActivate();
    return shift;
  }

  /**
   * Initializes the active region at a point.
   *
   * @param init initial point.
   */
  private void init(Point init) {
    assert !isActive();
    // Set active ends, page in from root to initialising point.
    end.set(init);
    start.set(init);
    collectAncestors(true, toPageIn, start.block, null);
  }

  /**
   * Resets the active region to be nothing.
   */
  public void reset() {
    shrinkToStart();
    collectAncestors(false, toPageOut, start.block, null);
    end.clear();
    start.clear();
    page();
  }

  /**
   * Collects the ancestors of a block (inclusive).
   *
   * @param direction true if parents are to be collected before children; otherwise, false
   * @param list collector
   * @param block block whose ancestry is to be collected.
   * @param to  terminal (null for all)
   */
  private static void collectAncestors(boolean direction, Collection<? super Block> list,
      Block block, Block to) {
    if (block == to) {
      return;
    } else {
      if (direction) {
        collectAncestors(direction, list, block.getParent(), to);
        list.add(block);
      } else {
        list.add(block);
        collectAncestors(direction, list, block.getParent(), to);
      }
    }
  }

  /** @return true if the active region inclusively encloses a viewport. */
  private boolean enclosesClipped(Region viewport) {
    Region clipped = RegionImpl.at( // \u2620
        MathUtil.clip(root.getStart(), root.getEnd(), viewport.getStart()), // \u2620
        MathUtil.clip(root.getStart(), root.getEnd(), viewport.getEnd())); // \u2620
    Region active = getActiveRegion();
    return OverlapKind.compare(active, clipped) == OverlapKind.ENCLOSES;
  }

  /**
   * Shrinks the active region to its start point.
   */
  private void shrinkToStart() {
    // Shrink active region to a point.
    while (!end.equals(start)) {
      end.trawlPrevious(toPageOut);
    }
  }

  /**
   * Shrinks the active region to its end point.
   */
  private void shrinkToEnd() {
    // Shrink active region to a point.
    while (!start.equals(end)) {
      start.trawlNext(toPageOut);
    }
  }

  /**
   * Moves the active region (assumed to be collapsed) to another point. Unique
   * ancestors of the current point are paged out, and unique ancestors of the
   * new point are paged in.
   *
   * @param point new active point
   */
  private void moveActivePoint(Point point) {
    assert start.equals(end);

    // To find the unique ancestors of the old point, and the unique ancestors
    // of the new point, the LCA needs to be found.
    LinkedSequence<Block> oldAncestors = LinkedSequence.create();
    for (Block block = start.block; block != null; block = block.getParent()) {
      oldAncestors.append(block);
    }

    List<Block> newAncestors = CollectionUtils.newArrayList();
    Block lca = point.block;
    while (lca != null && !oldAncestors.contains(lca)) {
      newAncestors.add(lca);
      lca = lca.getParent();
    }

    if (lca == null) {
      throw new IllegalArgumentException("Point not in block tree");
    }

    // Page out unique old ancestors, in order from old point up to LCA.
    for (Block oldAncestor : oldAncestors) {
      if (oldAncestor == lca) {
        break;
      } else {
        toPageOut.add(oldAncestor);
      }
    }

    // Page in unique new ancestors, in order from LCA down to new point.
    while (!newAncestors.isEmpty()) {
      toPageIn.add(newAncestors.remove(newAncestors.size() - 1));
    }

    // Then initiate active region.
    start.set(point);
    end.set(point);
  }

  /**
   * Expands a collapsed region.
   *
   * @param viewport
   */
  private void expandTo(Region viewport) {
    assert isActive();
    growStartUntil(viewport.getStart());
    growEndUntil(viewport.getEnd());
  }

  /**
   * Grows the active region from the start, until it includes a given position,
   * or the start of the entire tree is reached.
   *
   * @param position position to include in the active region
   */
  private void growStartUntil(double position) {
    start.trawlPreviousUntil(position, toPageIn);
  }

  /**
   * Grows the active region from the end, until it includes a given position,
   * or the end of the entire tree is reached.
   *
   * @param position position to include in the active region
   */
  private void growEndUntil(double position) {
    end.trawlNextUntil(position, toPageIn);
  }

  /**
   * Shrinks the active region from the start, until it includes a given
   * position, or the start of the entire tree is reached.
   *
   * @param position position to include in the active region
   */
  private void shrinkStartUntil(double position) {
    start.trawlNextUntil(position, toPageOut);
  }

  /**
   * Shrinks the active region from the end, until it includes a given position,
   * or the end of the entire tree is reached.
   *
   * @param position position to include in the active region
   */
  private void shrinkEndUntil(double position) {
    end.trawlPreviousUntil(position, toPageOut);
  }

  /**
   * Pages-in all the blocks in the page-in queue, and pages-out all the blocks
   * in the page-out queue.
   */
  private void page() {
    // Do page-out then page-in, so that moving via reset() ; init() will work.
    while (!toPageOut.isEmpty()) {
      toPageOut.poll().pageOut();
    }
    while (!toPageIn.isEmpty()) {
      toPageIn.poll().pageIn();
    }
    invalidatePositionCache();
  }

  //
  // Dynamic block handling.
  //

  /**
   * Notifies this region that a new subtree is now attached and ready to be
   * included as a pageable entity.
   */
  public void onAfterBlockAdded(Block added) {
    if (!isActive()) {
      return;
    }
    if (Traverser.isBetween(start, end, SimplePoint.startOf(added))) {
      pageInCompletely(added);
    }
  }

  private void pageInCompletely(Block block) {
    block.pageIn();
    for (Block child = block.getFirstChild(); child != null; child = child.getNextSibling()) {
      pageInCompletely(child);
    }
  }

  /**
   * Notifies this region that a subtree is about to be removed. All parts of
   * the accumulated block tree (i.e., the original tree, plus changes observed
   * through {@link #onAfterBlockAdded(Block)} and
   * {@link #onBeforeBlockRemoved(Block)}) are assumed to be still attached.
   *
   * @param block root of a subtree that is about to be removed
   */
  public void onBeforeBlockRemoved(Block block) {
    if (!isActive()) {
      return;
    }
    if (block == root) {
      start.clear();
      end.clear();
    } else {
      // If the block is an ancestor of a terminal (start or end), then that
      // terminal needs to be moved.
      boolean startIsDescendant = Traverser.isDescendant(block, start.block);
      boolean endIsDescendant = Traverser.isDescendant(block, end.block);
      if (startIsDescendant && endIsDescendant) {
        start.set(SimplePoint.startOf(block.getParent()));
        end.set(start);
      } else if (startIsDescendant) {
        start.set(SimplePoint.endOf(block));
        start.next();
      } else if (endIsDescendant) {
        end.set(SimplePoint.startOf(block));
        end.previous();
      }
    }
  }

  //@VisibleForTesting(productionVisibility = Visibility.PRIVATE)
  public Point getStart() {
    return start;
  }

  // @VisibleForTesting(productionVisibility = Visibility.PRIVATE)
  public Point getEnd() {
    return end;
  }
}
TOP

Related Classes of org.waveprotocol.wave.client.paging.ActiveRegion$EndPoint

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.