Package org.eclipse.jgit.dircache

Source Code of org.eclipse.jgit.dircache.DirCacheTree

/*
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
*   notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
*   copyright notice, this list of conditions and the following
*   disclaimer in the documentation and/or other materials provided
*   with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
*   names of its contributors may be used to endorse or promote
*   products derived from this software without specific prior
*   written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.dircache;

import static org.eclipse.jgit.lib.FileMode.TREE;
import static org.eclipse.jgit.lib.TreeFormatter.entrySize;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;

import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.RawParseUtils;

/**
* Single tree record from the 'TREE' {@link DirCache} extension.
* <p>
* A valid cache tree record contains the object id of a tree object and the
* total number of {@link DirCacheEntry} instances (counted recursively) from
* the DirCache contained within the tree. This information facilitates faster
* traversal of the index and quicker generation of tree objects prior to
* creating a new commit.
* <p>
* An invalid cache tree record indicates a known subtree whose file entries
* have changed in ways that cause the tree to no longer have a known object id.
* Invalid cache tree records must be revalidated prior to use.
*/
public class DirCacheTree {
  private static final byte[] NO_NAME = {};

  private static final DirCacheTree[] NO_CHILDREN = {};

  private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
    public int compare(final DirCacheTree o1, final DirCacheTree o2) {
      final byte[] a = o1.encodedName;
      final byte[] b = o2.encodedName;
      final int aLen = a.length;
      final int bLen = b.length;
      int cPos;
      for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
        final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
        if (cmp != 0)
          return cmp;
      }
      if (aLen == bLen)
        return 0;
      if (aLen < bLen)
        return '/' - (b[cPos] & 0xff);
      return (a[cPos] & 0xff) - '/';
    }
  };

  /** Tree this tree resides in; null if we are the root. */
  private DirCacheTree parent;

  /** Name of this tree within its parent. */
  private byte[] encodedName;

  /** Number of {@link DirCacheEntry} records that belong to this tree. */
  private int entrySpan;

  /** Unique SHA-1 of this tree; null if invalid. */
  private ObjectId id;

  /** Child trees, if any, sorted by {@link #encodedName}. */
  private DirCacheTree[] children;

  /** Number of valid children in {@link #children}. */
  private int childCnt;

  DirCacheTree() {
    encodedName = NO_NAME;
    children = NO_CHILDREN;
    childCnt = 0;
    entrySpan = -1;
  }

  private DirCacheTree(final DirCacheTree myParent, final byte[] path,
      final int pathOff, final int pathLen) {
    parent = myParent;
    encodedName = new byte[pathLen];
    System.arraycopy(path, pathOff, encodedName, 0, pathLen);
    children = NO_CHILDREN;
    childCnt = 0;
    entrySpan = -1;
  }

  DirCacheTree(final byte[] in, final MutableInteger off,
      final DirCacheTree myParent) {
    parent = myParent;

    int ptr = RawParseUtils.next(in, off.value, '\0');
    final int nameLen = ptr - off.value - 1;
    if (nameLen > 0) {
      encodedName = new byte[nameLen];
      System.arraycopy(in, off.value, encodedName, 0, nameLen);
    } else
      encodedName = NO_NAME;

    entrySpan = RawParseUtils.parseBase10(in, ptr, off);
    final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
    off.value = RawParseUtils.next(in, off.value, '\n');

    if (entrySpan >= 0) {
      // Valid trees have a positive entry count and an id of a
      // tree object that should exist in the object database.
      //
      id = ObjectId.fromRaw(in, off.value);
      off.value += Constants.OBJECT_ID_LENGTH;
    }

    if (subcnt > 0) {
      boolean alreadySorted = true;
      children = new DirCacheTree[subcnt];
      for (int i = 0; i < subcnt; i++) {
        children[i] = new DirCacheTree(in, off, this);

        // C Git's ordering differs from our own; it prefers to
        // sort by length first. This sometimes produces a sort
        // we do not desire. On the other hand it may have been
        // created by us, and be sorted the way we want.
        //
        if (alreadySorted && i > 0
            && TREE_CMP.compare(children[i - 1], children[i]) > 0)
          alreadySorted = false;
      }
      if (!alreadySorted)
        Arrays.sort(children, 0, subcnt, TREE_CMP);
    } else {
      // Leaf level trees have no children, only (file) entries.
      //
      children = NO_CHILDREN;
    }
    childCnt = subcnt;
  }

  void write(final byte[] tmp, final OutputStream os) throws IOException {
    int ptr = tmp.length;
    tmp[--ptr] = '\n';
    ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
    tmp[--ptr] = ' ';
    ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
    tmp[--ptr] = 0;

    os.write(encodedName);
    os.write(tmp, ptr, tmp.length - ptr);
    if (isValid()) {
      id.copyRawTo(tmp, 0);
      os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
    }
    for (int i = 0; i < childCnt; i++)
      children[i].write(tmp, os);
  }

  /**
   * Determine if this cache is currently valid.
   * <p>
   * A valid cache tree knows how many {@link DirCacheEntry} instances from
   * the parent {@link DirCache} reside within this tree (recursively
   * enumerated). It also knows the object id of the tree, as the tree should
   * be readily available from the repository's object database.
   *
   * @return true if this tree is knows key details about itself; false if the
   *         tree needs to be regenerated.
   */
  public boolean isValid() {
    return id != null;
  }

  /**
   * Get the number of entries this tree spans within the DirCache.
   * <p>
   * If this tree is not valid (see {@link #isValid()}) this method's return
   * value is always strictly negative (less than 0) but is otherwise an
   * undefined result.
   *
   * @return total number of entries (recursively) contained within this tree.
   */
  public int getEntrySpan() {
    return entrySpan;
  }

  /**
   * Get the number of cached subtrees contained within this tree.
   *
   * @return number of child trees available through this tree.
   */
  public int getChildCount() {
    return childCnt;
  }

  /**
   * Get the i-th child cache tree.
   *
   * @param i
   *            index of the child to obtain.
   * @return the child tree.
   */
  public DirCacheTree getChild(final int i) {
    return children[i];
  }

  ObjectId getObjectId() {
    return id;
  }

  /**
   * Get the tree's name within its parent.
   * <p>
   * This method is not very efficient and is primarily meant for debugging
   * and final output generation. Applications should try to avoid calling it,
   * and if invoked do so only once per interesting entry, where the name is
   * absolutely required for correct function.
   *
   * @return name of the tree. This does not contain any '/' characters.
   */
  public String getNameString() {
    final ByteBuffer bb = ByteBuffer.wrap(encodedName);
    return Constants.CHARSET.decode(bb).toString();
  }

  /**
   * Get the tree's path within the repository.
   * <p>
   * This method is not very efficient and is primarily meant for debugging
   * and final output generation. Applications should try to avoid calling it,
   * and if invoked do so only once per interesting entry, where the name is
   * absolutely required for correct function.
   *
   * @return path of the tree, relative to the repository root. If this is not
   *         the root tree the path ends with '/'. The root tree's path string
   *         is the empty string ("").
   */
  public String getPathString() {
    final StringBuilder r = new StringBuilder();
    appendName(r);
    return r.toString();
  }

  /**
   * Write (if necessary) this tree to the object store.
   *
   * @param cache
   *            the complete cache from DirCache.
   * @param cIdx
   *            first position of <code>cache</code> that is a member of this
   *            tree. The path of <code>cache[cacheIdx].path</code> for the
   *            range <code>[0,pathOff-1)</code> matches the complete path of
   *            this tree, from the root of the repository.
   * @param pathOffset
   *            number of bytes of <code>cache[cacheIdx].path</code> that
   *            matches this tree's path. The value at array position
   *            <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
   *            <code>pathOff</code> is > 0.
   * @param ow
   *            the writer to use when serializing to the store.
   * @return identity of this tree.
   * @throws UnmergedPathException
   *             one or more paths contain higher-order stages (stage > 0),
   *             which cannot be stored in a tree object.
   * @throws IOException
   *             an unexpected error occurred writing to the object store.
   */
  ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
      final int pathOffset, final ObjectInserter ow)
      throws UnmergedPathException, IOException {
    if (id == null) {
      final int endIdx = cIdx + entrySpan;
      final TreeFormatter fmt = new TreeFormatter(computeSize(cache,
          cIdx, pathOffset, ow));
      int childIdx = 0;
      int entryIdx = cIdx;

      while (entryIdx < endIdx) {
        final DirCacheEntry e = cache[entryIdx];
        final byte[] ep = e.path;
        if (childIdx < childCnt) {
          final DirCacheTree st = children[childIdx];
          if (st.contains(ep, pathOffset, ep.length)) {
            fmt.append(st.encodedName, TREE, st.id);
            entryIdx += st.entrySpan;
            childIdx++;
            continue;
          }
        }

        fmt.append(ep, pathOffset, ep.length - pathOffset, e
            .getFileMode(), e.idBuffer(), e.idOffset());
        entryIdx++;
      }

      id = ow.insert(fmt);
    }
    return id;
  }

  private int computeSize(final DirCacheEntry[] cache, int cIdx,
      final int pathOffset, final ObjectInserter ow)
      throws UnmergedPathException, IOException {
    final int endIdx = cIdx + entrySpan;
    int childIdx = 0;
    int entryIdx = cIdx;
    int size = 0;

    while (entryIdx < endIdx) {
      final DirCacheEntry e = cache[entryIdx];
      if (e.getStage() != 0)
        throw new UnmergedPathException(e);

      final byte[] ep = e.path;
      if (childIdx < childCnt) {
        final DirCacheTree st = children[childIdx];
        if (st.contains(ep, pathOffset, ep.length)) {
          final int stOffset = pathOffset + st.nameLength() + 1;
          st.writeTree(cache, entryIdx, stOffset, ow);

          size += entrySize(TREE, st.nameLength());

          entryIdx += st.entrySpan;
          childIdx++;
          continue;
        }
      }

      size += entrySize(e.getFileMode(), ep.length - pathOffset);
      entryIdx++;
    }

    return size;
  }

  private void appendName(final StringBuilder r) {
    if (parent != null) {
      parent.appendName(r);
      r.append(getNameString());
      r.append('/');
    } else if (nameLength() > 0) {
      r.append(getNameString());
      r.append('/');
    }
  }

  final int nameLength() {
    return encodedName.length;
  }

  final boolean contains(final byte[] a, int aOff, final int aLen) {
    final byte[] e = encodedName;
    final int eLen = e.length;
    for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
      if (e[eOff] != a[aOff])
        return false;
    if (aOff == aLen)
      return false;
    return a[aOff] == '/';
  }

  /**
   * Update (if necessary) this tree's entrySpan.
   *
   * @param cache
   *            the complete cache from DirCache.
   * @param cCnt
   *            number of entries in <code>cache</code> that are valid for
   *            iteration.
   * @param cIdx
   *            first position of <code>cache</code> that is a member of this
   *            tree. The path of <code>cache[cacheIdx].path</code> for the
   *            range <code>[0,pathOff-1)</code> matches the complete path of
   *            this tree, from the root of the repository.
   * @param pathOff
   *            number of bytes of <code>cache[cacheIdx].path</code> that
   *            matches this tree's path. The value at array position
   *            <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
   *            <code>pathOff</code> is > 0.
   */
  void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
      final int pathOff) {
    if (entrySpan >= 0) {
      // If we are valid, our children are also valid.
      // We have no need to validate them.
      //
      return;
    }

    entrySpan = 0;
    if (cCnt == 0) {
      // Special case of an empty index, and we are the root tree.
      //
      return;
    }

    final byte[] firstPath = cache[cIdx].path;
    int stIdx = 0;
    while (cIdx < cCnt) {
      final byte[] currPath = cache[cIdx].path;
      if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
        // The current entry is no longer in this tree. Our
        // span is updated and the remainder goes elsewhere.
        //
        break;
      }

      DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
      final int cc = namecmp(currPath, pathOff, st);
      if (cc > 0) {
        // This subtree is now empty.
        //
        removeChild(stIdx);
        continue;
      }

      if (cc < 0) {
        final int p = slash(currPath, pathOff);
        if (p < 0) {
          // The entry has no '/' and thus is directly in this
          // tree. Count it as one of our own.
          //
          cIdx++;
          entrySpan++;
          continue;
        }

        // Build a new subtree for this entry.
        //
        st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
        insertChild(stIdx, st);
      }

      // The entry is contained in this subtree.
      //
      st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
      cIdx += st.entrySpan;
      entrySpan += st.entrySpan;
      stIdx++;
    }

    // None of our remaining children can be in this tree
    // as the current cache entry is after our own name.
    //
    while (stIdx < childCnt)
      removeChild(childCnt - 1);
  }

  private void insertChild(final int stIdx, final DirCacheTree st) {
    final DirCacheTree[] c = children;
    if (childCnt + 1 <= c.length) {
      if (stIdx < childCnt)
        System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
      c[stIdx] = st;
      childCnt++;
      return;
    }

    final int n = c.length;
    final DirCacheTree[] a = new DirCacheTree[n + 1];
    if (stIdx > 0)
      System.arraycopy(c, 0, a, 0, stIdx);
    a[stIdx] = st;
    if (stIdx < n)
      System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
    children = a;
    childCnt++;
  }

  private void removeChild(final int stIdx) {
    final int n = --childCnt;
    if (stIdx < n)
      System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
    children[n] = null;
  }

  static boolean peq(final byte[] a, final byte[] b, int aLen) {
    if (b.length < aLen)
      return false;
    for (aLen--; aLen >= 0; aLen--)
      if (a[aLen] != b[aLen])
        return false;
    return true;
  }

  private static int namecmp(final byte[] a, int aPos, final DirCacheTree ct) {
    if (ct == null)
      return -1;
    final byte[] b = ct.encodedName;
    final int aLen = a.length;
    final int bLen = b.length;
    int bPos = 0;
    for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
      final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
      if (cmp != 0)
        return cmp;
    }
    if (bPos == bLen)
      return a[aPos] == '/' ? 0 : -1;
    return aLen - bLen;
  }

  private static int slash(final byte[] a, int aPos) {
    final int aLen = a.length;
    for (; aPos < aLen; aPos++)
      if (a[aPos] == '/')
        return aPos;
    return -1;
  }

  @Override
  public String toString() {
    return getNameString();
  }
}
TOP

Related Classes of org.eclipse.jgit.dircache.DirCacheTree

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.