Package com.slytechs.utils.region

Source Code of com.slytechs.utils.region.FlexRegion

/**
* Copyright (C) 2007 Sly Technologies, Inc. This library is free software; you
* can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version. This
* library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details. You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.slytechs.utils.region;

import java.io.IOException;
import java.nio.ReadOnlyBufferException;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.slytechs.utils.collection.Readonly;
import com.slytechs.utils.region.FlexRegionListener.FlexRegiontSupport;

/**
* @author Mark Bednarczyk
* @author Sly Technologies, Inc.
*/
public class FlexRegion<T> implements Iterable<RegionSegment<T>>, Changable {

  public static final Log logger = LogFactory.getLog(FlexRegion.class);

  private final boolean append;

  private long changeId = System.currentTimeMillis();

  protected Region<T> initialRegion;

  protected long length = 0;

  private final boolean readonly;

  protected final LinkedList<RegionSegment<T>> segments = new LinkedList<RegionSegment<T>>();

  private final FlexRegiontSupport<T> support = new FlexRegiontSupport<T>(this);

  private boolean modifiedAtLeastOnce = false;

  /**
   * @param readonly
   * @param append
   */
  protected FlexRegion(final boolean readonly, final boolean append) {
    this.readonly = readonly;
    this.append = append;
  }

  /**
   * @param readonly
   *          creates a readonly flex region. No mutable operations are
   *          supported and will throw an immediate Readonly exception if any
   *          are used.
   * @param append
   *          TODO
   * @param length
   * @param data
   */
  public FlexRegion(final boolean readonly, final boolean append,
      final long length, final T data) {
    this.readonly = readonly;
    this.append = append;

    if (logger.isTraceEnabled()) {
      logger.trace("readonly=" + readonly + ", append=" + append + ", length="
          + length + ", data=" + data);
    }

    init(length, data);
  }

  /**
   * @param o
   * @return
   * @see java.util.ArrayList#add(java.lang.Object)
   */
  public boolean add(final FlexRegionListener<T> o) {
    return this.support.add(o);
  }

  public final RegionSegment[] append(final long length, final T data) {
    this.throwIfNoAppend();

    if (length == 0) {
      return null; // Nothing to do
    }
    this.modifiedAtLeastOnce = true;

    this.support.fireAppend(length, data);

    this.changeHappened();

    // The last element of data is at (length - 1), so we append after the last
    // byte
    final long position = this.length;
    final RegionSegment<T> newSegment = createSegment(position, length, data);
    this.segments.addLast(newSegment);

    this.length += length;

    final RegionSegment[] newSegments = new RegionSegment[1];
    newSegments[0] = newSegment;

    this.support.fireLinkSegment(newSegments);

    if (logger.isTraceEnabled()) {
      logger.trace("length=" + length + ", data=" + data + " AFTER:"
          + toString());
    }

    return newSegments;
  }

  private final void changeGlobalStart(final int index, final long delta) {

    final Iterator<RegionSegment<T>> i = this.segments.listIterator(index);

    while (i.hasNext()) {
      final RegionSegment<T> s = i.next();

      s.addToStartGlobal(delta);
    }
  }

  public final void changeHappened() {
    if (this.changeId == Long.MAX_VALUE) {
      this.changeId = Long.MIN_VALUE;
    } else {
      this.changeId++;
    }
  }

  public final boolean checkBoundsGlobal(final long global) {
    return (global >= 0) && (global < this.length);
  }

  public final RegionSegment[] clear() {
    if (this.isModified() == false) {
      return null; // Nothing to do
    }

    support.fireAbortAllChanges();

    this.segments.clear();

    final RegionSegment<T> s = createSegment(this.initialRegion, 0, 0,
        this.initialRegion.getLength());

    this.segments.add(s);

    this.length = this.initialRegion.getLength();

    this.changeHappened();

    final RegionSegment[] newSegments = new RegionSegment[] { s };

    support.fireLinkSegment(newSegments);

    return newSegments;
  }

  /**
   *
   */
  public void close() {
    /*
     * We really don't need to do anything here, except may be print out a debug
     * message.
     */

    if (logger.isDebugEnabled() && modifiedAtLeastOnce
        || logger.isTraceEnabled()) {
      logger.debug("       " + toString() + (modifiedAtLeastOnce ? "*Modified" : ""));
    }
  }

  /**
   * @param listener
   * @return
   * @see java.util.ArrayList#contains(java.lang.Object)
   */
  public boolean contains(final FlexRegionListener<T> listener) {
    return this.support.contains(listener);
  }

  /**
   * @param i
   * @return
   */
  public final RegionHandle<T> createHandle(final long global) {

    final RegionSegment<T> s = this.getSegment(global);
    final long regional = s.mapGlobalToRegional(global);

    final RegionHandle<T> h = new RegionHandle<T>(s, regional, this);

    return h;
  }

  /**
   * @param changable
   * @param i
   * @param length
   * @param data
   * @return
   */
  protected RegionSegment<T> createSegment(Changable changable, long global,
      long length, T data) {
    final RegionSegment<T> segment = new RegionSegment<T>(this, 0, length, data);

    return segment;
  }

  protected RegionSegment<T> createSegment(long global, long length, T data) {
    final RegionSegment<T> segment = new RegionSegment<T>(this, global, length,
        data);

    return segment;

  }

  /**
   * @param region
   * @param endGlobal
   * @param thirdStartRegional
   * @param thirdLength
   * @return
   */
  protected RegionSegment<T> createSegment(Region<T> region, long global,
      long regional, long length) {
    final RegionSegment<T> segment = new RegionSegment<T>(region, global,
        regional, length);

    return segment;
  }

  public final RegionSegment[] flatten(final T data) {
    return this.flatten(data, this.length);
  }

  public final RegionSegment[] flatten(final T data, final long length) {

    if (logger.isDebugEnabled()) {
      logger.debug("BEFORE:" + toString());

    }

    // final RegionSegment<T> initial = new RegionSegment<T>(this, 0, length,
    // data);

    final RegionSegment<T> initial = createSegment((Changable) this, 0, length,
        data);

    support.fireFlatten(data);

    /*
     * Set forward references for all segments. Each segment will use its
     * current GLOBAL address as a REGIONAL address into the new "initial"
     * segment.
     */
    for (final RegionSegment<T> segment : this.segments) {
      final Region<T> region = segment.getRegion();

      if (region.getForward() != initial.getRegion()) {
        region.setForward(initial.getRegion());

        /*
         * Tell each data element within each region that its underlying content
         * should be set to readonly, since we do not want any more changes to
         * the region's data. All changes should be propagated to the forwarded
         * region and its data.
         */
        final T d = region.getData();
        if (d instanceof Readonly) {
          ((Readonly) d).setReadonly(true);
        }
      }

    }

    /*
     * Remove all old segments
     */
    this.segments.clear();

    /*
     * Now add the replacement "init" segment which is the entire updated file
     */
    this.segments.add(initial);

    this.initialRegion = initial.getRegion();

    this.changeHappened();

    final RegionSegment[] newSegments = new RegionSegment[] { initial };

    support.fireLinkSegment(newSegments);

    if (logger.isDebugEnabled()) {
      logger.debug(" AFTER:" + toString());

    }

    return newSegments;
  }

  public final long getChangeId() {
    return this.changeId;
  }

  public final T getData(final long global) {
    final RegionSegment<T> s = this.getSegment(global);

    return s.getData();
  }

  public final long getLength() {
    return this.length;
  }

  /**
   * @param index
   * @return
   * @throws IOException
   */
  public Object getRegionalData(final int global) throws IOException {
    final RegionSegment<T> segment = this.getSegment(global);
    final long regional = segment.mapGlobalToRegional(global);
    final T data = segment.getData();

    if (data instanceof RegionDataGetter) {
      final RegionDataGetter getter = (RegionDataGetter) data;
      return getter.get(regional);
    }

    throw new UnsupportedOperationException(
        "This operation is not supported by the data object. "
            + "The data object needs to implement the RegionDataGetter "
            + "interface.");
  }

  public final RegionSegment<T> getSegment(final long position) {
    /*
     * Optimize for 1 single segment list, no need to initiate an Iterator
     * object
     */

    final RegionSegment<T> first = this.segments.getFirst();
    if (first.checkBoundsGlobal(position)) {
      return first;
    }
    final int s = this.segments.size();

    /*
     * Optimize for very large number of segments in the list Last else does a
     * direct search for list sizes under 1000
     */

    if (s >= 1000000) {
      return this.getSegment(position, 0, 1000000);
    } else if (s >= 100000) {
      return this.getSegment(position, 0, 100000);
    } else if (s >= 10000) {
      return this.getSegment(position, 0, 10000);
    } else if (s >= 1000) {
      return this.getSegment(position, 0, 1000);
    } else {
      return this.getSegment(position, 0);
    }
  }

  public final RegionSegment<T> getSegment(final long global, final int start) {

    int i = 0;
    final Iterator<RegionSegment<T>> l = this.segments.listIterator(start);
    while (l.hasNext()) {
      final RegionSegment<T> ds = l.next();
      if (ds.checkBoundsGlobal(global)) {
        return ds;
      }

      i++;
    }

    throw new IndexOutOfBoundsException("Position out of bounds [" + global
        + "]");
  }

  private final RegionSegment<T> getSegment(final long position,
      final int start, final int power) {
    final int s = this.segments.size();

    int l = start;

    if (power == 100) {
      return this.getSegment(position, start);
    }

    for (int i = start + power; i < s; i += power) {
      if (position < this.segments.get(i).getStartGlobal()) {
        break;
      }

      l = i;
    }

    return this.getSegment(position, l, power / 10);
  }

  public final int getSegmentCount() {
    return this.segments.size();
  }

  private final int getSegmentIndex(final long position) {

    /*
     * Optimize for 1 single segment list, no need to initiate an Iterator
     * object
     */
    if ((this.segments.size() == 1)
        && this.segments.getFirst().checkBoundsGlobal(position)) {
      return 0;
    }

    int i = 0;
    for (final RegionSegment<T> ds : this.segments) {
      if (ds.checkBoundsGlobal(position)) {
        return i;
      }

      i++;
    }

    throw new IndexOutOfBoundsException("Position out of bounds [" + position
        + "]");
  }

  /**
   * @return
   */
  public Iterable<RegionSegment<T>> getSegmentIterable() {
    return new Iterable<RegionSegment<T>>() {

      public Iterator<RegionSegment<T>> iterator() {
        return segments.iterator();
      }

    };
  }

  protected void init(final long length, final T data) {
    this.length = length;

    final RegionSegment<T> ds = new RegionSegment<T>(this, 0, length, data);
    this.initialRegion = ds.getRegion();

    this.segments.add(ds);
  }

  public final void insert(final long position, final long length, final T data) {

    if (length == 0) {
      return; // Nothing to do
    }

    /*
     * Special case: append at the end - the position is actually outside any
     * active region, therefore only if we are positioned just 1 byte past the
     * end of the last active region, we do an append instead of an insert.
     */
    if (position == this.length) {
      this.append(length, data);

    } else {
      this.replace(position, 0, length, data);
    }
  }

  /**
   * @return
   */
  public boolean isAppend() {
    return this.append;
  }

  /*
   * (non-Javadoc)
   *
   * @see com.slytechs.utils.region.Changable#isChanged(long)
   */
  public final boolean isChanged(final long changeId) {
    return this.changeId != changeId;
  }

  public final boolean isModified() {
    return this.segments.size() != 1;
  }

  public final boolean isModifiedByAppendOnly() {
    final RegionSegment<T> first = this.segments.getFirst();

    return (this.segments.size() > 1)
        && (first.getRegion() == this.initialRegion)
        && (first.getLength() == this.initialRegion.getLength());

  }

  /**
   * @return
   */
  public boolean isReadonly() {
    return this.readonly;
  }

  /*
   * (non-Javadoc)
   *
   * @see java.lang.Iterable#iterator()
   */
  public final Iterator<RegionSegment<T>> iterator() {
    return this.segments.iterator();
  }

  /**
   * Creates a new region which may contain different data and who is actively
   * synched with this region. Any changes to this region will be propagated and
   * translated via the supplied translator in the linked region.
   *
   * @param <C>
   *          new type prarameter of the new region
   * @param translator
   *          translator which will translate various even properties from one
   *          region type <T> to linked region <C>.
   * @return new region that is linked to this region
   */
  public <C> FlexRegion<C> linkedRegion(final RegionTranslator<C, T> translator) {

    final FlexRegion<C> linked = new LinkedFlexRegion<C, T>(this, translator);

    return linked;
  }

  /**
   * Removes the specified listener from the active listeners list. The listener
   * will no longer be notified of any events.
   *
   * @param listener
   *          listener to remove
   * @return true if found and removed, otherwise false
   */
  public boolean remove(final FlexRegionListener<T> listener) {
    return this.support.remove(listener);
  }

  public final void remove(final long position, final long length) {
    if (length == 0) {
      return; // Nothing to do
    }

    this.replace(position, length, 0, null);
  }

  /**
   * @param start
   *          global start
   * @param oldLength
   *          number of elements to be replaced
   * @param newLength
   *          number of elements to replace the old segment
   * @param data
   *          data associated with this replacement
   * @return TODO
   */
  public final RegionSegment[] replace(final long start, final long oldLength,
      final long newLength, final T data) {

    this.throwIfReadonly();

    if ((oldLength == 0) && (newLength == 0)) {
      return null; // Nothing to do
    }
    this.modifiedAtLeastOnce = true;

    this.support.fireReplace(start, oldLength, newLength, data);

    this.changeHappened();

    final int i = this.getSegmentIndex(start);
    final RegionSegment<T> first = this.segments.get(i);

    if ((first.checkBoundsGlobal(start) == false)
        || (first.checkBoundsGlobal(start, oldLength) == false)) {
      throw new IndexOutOfBoundsException("Replacement request [" + start
          + "] falls outside the segment's [" + first.toString()
          + "] boundaries");
    }

    final long startLocal = first.mapGlobalToLocal(start);
    final long firstEndLocal = first.getEndLocal();
    final long endLocal = startLocal + oldLength;

    final RegionSegment[] newSegment;

    if (startLocal == 0) {
      newSegment = new RegionSegment[1];
      newSegment[0] = this.replaceFront(first, start, oldLength, newLength,
          data);
    } else if (endLocal == firstEndLocal) {
      newSegment = new RegionSegment[1];
      newSegment[0] = this
          .replaceBack(first, start, oldLength, newLength, data);
    } else {
      newSegment = this.replaceMiddle(first, i, start, oldLength, newLength,
          data);
    }

    this.support.fireLinkSegment(newSegment);

    if (logger.isTraceEnabled()) {
      logger.trace("start" + start + ", old=" + oldLength + ", new="
          + newLength + ", data=" + data + " AFTER:" + toString());
    }

    return newSegment;
  }

  /**
   * @param start
   * @param oldLength
   * @param newLength
   * @param data
   */
  private final RegionSegment<T> replaceBack(final RegionSegment<T> first,
      final long start, final long oldLength, final long newLength, final T data) {

    final long delta = newLength - oldLength;
    final int i = this.segments.indexOf(first);
    this.changeGlobalStart(i + 1, delta);

    this.length += delta;

    final long firstOldLength = first.getLength();
    first.setLength(firstOldLength + delta);

    if (newLength != 0) {
      final RegionSegment<T> newSegment = createSegment(first.getEndGlobal(),
          newLength, data);

      this.segments.add(i + 1, newSegment);
      return newSegment;
    }

    return null;
  }

  /**
   * @param start
   * @param replacementLength
   * @param newLength
   * @param data
   */
  private final RegionSegment<T> replaceFront(final RegionSegment<T> first,
      final long start, final long replacementLength, final long newLength,
      final T data) {

    final long firstOldLength = first.getLength();
    final int i = this.segments.indexOf(first);
    final long delta = newLength - replacementLength;
    this.changeGlobalStart(i + 1, delta);

    this.length += delta;

    /*
     * Check if this is a total remove
     */
    if (newLength == 0) {
      if (replacementLength == firstOldLength) {
        /*
         * Removing entire segment?
         */

        this.segments.remove(first);

      } else {
        /*
         * Removing only the front portion of the segment. So shrink length and
         * push out the regional start. Local and global starts are not affected
         */
        first.setLength(firstOldLength - replacementLength);
        first.addToStartRegional(replacementLength);

      }
    } else {
      /*
       * Ok, we're replacing a portion of the first segment with a new region
       */

      final RegionSegment<T> newSegment = createSegment(start, newLength, data);

      /*
       * Check if we are replacing the entire segment
       */
      if ((newLength == replacementLength) && (first.getLength() == newLength)) {
        first.setValid(false);
        this.segments.remove(i);
        this.segments.add(i, newSegment);

      } else {
        first.addToStartRegional(replacementLength);
        first.addToStartGlobal(newLength);
        first.setLength(firstOldLength - replacementLength);

        this.segments.add(i, newSegment);
      }

      return newSegment;
    }

    return null;
  }

  /**
   * @param start
   * @param replacedLength
   * @param newLength
   * @param data
   */
  private final RegionSegment[] replaceMiddle(final RegionSegment<T> first,
      final int i, final long start, final long replacedLength,
      final long newLength, final T data) {

    final long firstOldLength = first.getLength();
    final long firstNewLength = first.mapGlobalToRegional(start
        - first.getStartRegional());
    first.setLength(firstNewLength);

    final long thirdStartRegional = first.getEndRegional() + replacedLength;
    final long thirdLength = firstOldLength - firstNewLength - replacedLength;

    final RegionSegment<T> second = createSegment(first.getEndGlobal(),
        newLength, data);

    final RegionSegment<T> third = createSegment(first.getRegion(), second
        .getEndGlobal(), thirdStartRegional, thirdLength);

    final long globalDelta = newLength - replacedLength;
    this.length += globalDelta;

    this.changeGlobalStart(i + 1, globalDelta);

    final RegionSegment[] newSegments;

    if (second.getLength() == 0) {
      this.segments.add(i + 1, third);
      second.getRegion().remove(second);

      newSegments = new RegionSegment[1];
      newSegments[0] = third;

    } else {
      this.segments.add(i + 1, second);
      this.segments.add(i + 2, third);

      newSegments = new RegionSegment[2];
      newSegments[0] = second;
      newSegments[1] = third;
    }

    return newSegments;
  }

  private final void throwIfNoAppend() {
    if (this.append) {
      return;
    }

    throw new ReadOnlyBufferException();
  }

  private final void throwIfReadonly() {
    if (this.readonly == false) {
      return;
    }

    throw new ReadOnlyBufferException();
  }

  /**
   * <p>
   * Prints the current FlexRegion and its segments. The output will have the
   * following format:
   *
   * <pre>
   *                                       [[A.0 - A.1/l=2/g=0/r=0], [B.2 - B.8/l=7/g=2/r=1]]
   * </pre>
   *
   * Note: A and B are user supplied T type parameters supplied to the region
   * and the operations on the region.
   * </p>
   * <p>
   * The output shows the entire FlexRegion enclosed in the outter []. It has 2
   * segments, each enclosed in inner []. First segment, is associated with a
   * region who's user data parameter was "A". The segments range is from
   * abstract position 0 to 1, inclusive. The other paramters:
   * <ul>
   * <li>l - length or number of elements within the segment (2)
   * <li>g - global position of the first element within the segment (0)
   * <li>r - regional position of the first element within the segment (0)1
   * </ul>
   * The second segment shows the same parameters but with differnt values.
   * Range is from 2 to 8, inclusive:
   * <ul>
   * <li>l - length or number of elements within the segment (72)
   * <li>g - global position of the first element within the segment (2)
   * <li>r - regional position of the first element within the segment (1)
   * </ul>
   * </p>
   */
  @Override
  public String toString() {
    return this.segments.toString();
  }
}
TOP

Related Classes of com.slytechs.utils.region.FlexRegion

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.