org.eclipse.jgit.lib.TreeFormatter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jgit.lib.TreeFormatter.java

Source

/*
 * Copyright (C) 2010, Google Inc.
 * 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.lib;

import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import static org.eclipse.jgit.lib.Constants.encode;
import static org.eclipse.jgit.lib.FileMode.GITLINK;
import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
import static org.eclipse.jgit.lib.FileMode.TREE;

import java.io.IOException;

import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.util.TemporaryBuffer;

/**
 * Mutable formatter to construct a single tree object.
 *
 * This formatter does not process subtrees. Callers must handle creating each
 * subtree on their own.
 *
 * To maintain good performance for bulk operations, this formatter does not
 * validate its input. Callers are responsible for ensuring the resulting tree
 * object is correctly well formed by writing entries in the correct order.
 */
public class TreeFormatter {
    /**
     * Compute the size of a tree entry record.
     *
     * This method can be used to estimate the correct size of a tree prior to
     * allocating a formatter. Getting the size correct at allocation time
     * ensures the internal buffer is sized correctly, reducing copying.
     *
     * @param mode
     *            the mode the entry will have.
     * @param nameLen
     *            the length of the name, in bytes.
     * @return the length of the record.
     */
    public static int entrySize(FileMode mode, int nameLen) {
        return mode.copyToLength() + nameLen + OBJECT_ID_LENGTH + 2;
    }

    private byte[] buf;

    private int ptr;

    private TemporaryBuffer.Heap overflowBuffer;

    /**
     * Create an empty formatter with a default buffer size.
     */
    public TreeFormatter() {
        this(8192);
    }

    /**
     * Create an empty formatter with the specified buffer size.
     *
     * @param size
     *            estimated size of the tree, in bytes. Callers can use
     *            {@link #entrySize(FileMode, int)} to estimate the size of each
     *            entry in advance of allocating the formatter.
     */
    public TreeFormatter(int size) {
        buf = new byte[size];
    }

    /**
     * Add a link to a submodule commit, mode is {@link org.eclipse.jgit.lib.FileMode#GITLINK}.
     *
     * @param name
     *            name of the entry.
     * @param commit
     *            the ObjectId to store in this entry.
     */
    public void append(String name, RevCommit commit) {
        append(name, GITLINK, commit);
    }

    /**
     * Add a subtree, mode is {@link org.eclipse.jgit.lib.FileMode#TREE}.
     *
     * @param name
     *            name of the entry.
     * @param tree
     *            the ObjectId to store in this entry.
     */
    public void append(String name, RevTree tree) {
        append(name, TREE, tree);
    }

    /**
     * Add a regular file, mode is {@link org.eclipse.jgit.lib.FileMode#REGULAR_FILE}.
     *
     * @param name
     *            name of the entry.
     * @param blob
     *            the ObjectId to store in this entry.
     */
    public void append(String name, RevBlob blob) {
        append(name, REGULAR_FILE, blob);
    }

    /**
     * Append any entry to the tree.
     *
     * @param name
     *            name of the entry.
     * @param mode
     *            mode describing the treatment of {@code id}.
     * @param id
     *            the ObjectId to store in this entry.
     */
    public void append(String name, FileMode mode, AnyObjectId id) {
        append(encode(name), mode, id);
    }

    /**
     * Append any entry to the tree.
     *
     * @param name
     *            name of the entry. The name should be UTF-8 encoded, but file
     *            name encoding is not a well defined concept in Git.
     * @param mode
     *            mode describing the treatment of {@code id}.
     * @param id
     *            the ObjectId to store in this entry.
     */
    public void append(byte[] name, FileMode mode, AnyObjectId id) {
        append(name, 0, name.length, mode, id);
    }

    /**
     * Append any entry to the tree.
     *
     * @param nameBuf
     *            buffer holding the name of the entry. The name should be UTF-8
     *            encoded, but file name encoding is not a well defined concept
     *            in Git.
     * @param namePos
     *            first position within {@code nameBuf} of the name data.
     * @param nameLen
     *            number of bytes from {@code nameBuf} to use as the name.
     * @param mode
     *            mode describing the treatment of {@code id}.
     * @param id
     *            the ObjectId to store in this entry.
     */
    public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode, AnyObjectId id) {
        append(nameBuf, namePos, nameLen, mode, id, false);
    }

    /**
     * Append any entry to the tree.
     *
     * @param nameBuf
     *            buffer holding the name of the entry. The name should be UTF-8
     *            encoded, but file name encoding is not a well defined concept
     *            in Git.
     * @param namePos
     *            first position within {@code nameBuf} of the name data.
     * @param nameLen
     *            number of bytes from {@code nameBuf} to use as the name.
     * @param mode
     *            mode describing the treatment of {@code id}.
     * @param id
     *            the ObjectId to store in this entry.
     * @param allowEmptyName
     *            allow an empty filename (creating a corrupt tree)
     * @since 4.6
     */
    public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode, AnyObjectId id,
            boolean allowEmptyName) {
        if (nameLen == 0 && !allowEmptyName) {
            throw new IllegalArgumentException(JGitText.get().invalidTreeZeroLengthName);
        }
        if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
            id.copyRawTo(buf, ptr);
            ptr += OBJECT_ID_LENGTH;

        } else {
            try {
                fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
                id.copyRawTo(overflowBuffer);
            } catch (IOException badBuffer) {
                // This should never occur.
                throw new RuntimeException(badBuffer);
            }
        }
    }

    /**
     * Append any entry to the tree.
     *
     * @param nameBuf
     *            buffer holding the name of the entry. The name should be UTF-8
     *            encoded, but file name encoding is not a well defined concept
     *            in Git.
     * @param namePos
     *            first position within {@code nameBuf} of the name data.
     * @param nameLen
     *            number of bytes from {@code nameBuf} to use as the name.
     * @param mode
     *            mode describing the treatment of {@code id}.
     * @param idBuf
     *            buffer holding the raw ObjectId of the entry.
     * @param idPos
     *            first position within {@code idBuf} to copy the id from.
     */
    public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode, byte[] idBuf, int idPos) {
        if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
            System.arraycopy(idBuf, idPos, buf, ptr, OBJECT_ID_LENGTH);
            ptr += OBJECT_ID_LENGTH;

        } else {
            try {
                fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
                overflowBuffer.write(idBuf, idPos, OBJECT_ID_LENGTH);
            } catch (IOException badBuffer) {
                // This should never occur.
                throw new RuntimeException(badBuffer);
            }
        }
    }

    private boolean fmtBuf(byte[] nameBuf, int namePos, int nameLen, FileMode mode) {
        if (buf == null || buf.length < ptr + entrySize(mode, nameLen))
            return false;

        mode.copyTo(buf, ptr);
        ptr += mode.copyToLength();
        buf[ptr++] = ' ';

        System.arraycopy(nameBuf, namePos, buf, ptr, nameLen);
        ptr += nameLen;
        buf[ptr++] = 0;
        return true;
    }

    private void fmtOverflowBuffer(byte[] nameBuf, int namePos, int nameLen, FileMode mode) throws IOException {
        if (buf != null) {
            overflowBuffer = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
            overflowBuffer.write(buf, 0, ptr);
            buf = null;
        }

        mode.copyTo(overflowBuffer);
        overflowBuffer.write((byte) ' ');
        overflowBuffer.write(nameBuf, namePos, nameLen);
        overflowBuffer.write((byte) 0);
    }

    /**
     * Insert this tree and obtain its ObjectId.
     *
     * @param ins
     *            the inserter to store the tree.
     * @return computed ObjectId of the tree
     * @throws java.io.IOException
     *             the tree could not be stored.
     */
    public ObjectId insertTo(ObjectInserter ins) throws IOException {
        if (buf != null)
            return ins.insert(OBJ_TREE, buf, 0, ptr);

        final long len = overflowBuffer.length();
        return ins.insert(OBJ_TREE, len, overflowBuffer.openInputStream());
    }

    /**
     * Compute the ObjectId for this tree
     *
     * @param ins a {@link org.eclipse.jgit.lib.ObjectInserter} object.
     * @return ObjectId for this tree
     */
    public ObjectId computeId(ObjectInserter ins) {
        if (buf != null)
            return ins.idFor(OBJ_TREE, buf, 0, ptr);

        final long len = overflowBuffer.length();
        try {
            return ins.idFor(OBJ_TREE, len, overflowBuffer.openInputStream());
        } catch (IOException e) {
            // this should never happen
            throw new RuntimeException(e);
        }
    }

    /**
     * Copy this formatter's buffer into a byte array.
     *
     * This method is not efficient, as it needs to create a copy of the
     * internal buffer in order to supply an array of the correct size to the
     * caller. If the buffer is just to pass to an ObjectInserter, consider
     * using {@link org.eclipse.jgit.lib.ObjectInserter#insert(TreeFormatter)}
     * instead.
     *
     * @return a copy of this formatter's buffer.
     */
    public byte[] toByteArray() {
        if (buf != null) {
            byte[] r = new byte[ptr];
            System.arraycopy(buf, 0, r, 0, ptr);
            return r;
        }

        try {
            return overflowBuffer.toByteArray();
        } catch (IOException err) {
            // This should never happen, its read failure on a byte array.
            throw new RuntimeException(err);
        }
    }

    /** {@inheritDoc} */
    @SuppressWarnings("nls")
    @Override
    public String toString() {
        byte[] raw = toByteArray();

        CanonicalTreeParser p = new CanonicalTreeParser();
        p.reset(raw);

        StringBuilder r = new StringBuilder();
        r.append("Tree={");
        if (!p.eof()) {
            r.append('\n');
            try {
                new ObjectChecker().checkTree(raw);
            } catch (CorruptObjectException error) {
                r.append("*** ERROR: ").append(error.getMessage()).append("\n");
                r.append('\n');
            }
        }
        while (!p.eof()) {
            final FileMode mode = p.getEntryFileMode();
            r.append(mode);
            r.append(' ');
            r.append(Constants.typeString(mode.getObjectType()));
            r.append(' ');
            r.append(p.getEntryObjectId().name());
            r.append(' ');
            r.append(p.getEntryPathString());
            r.append('\n');
            p.next();
        }
        r.append("}");
        return r.toString();
    }
}