| /* |
| * 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.notes; |
| |
| import java.io.IOException; |
| import java.util.Iterator; |
| |
| import org.eclipse.jgit.errors.CorruptObjectException; |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.errors.LargeObjectException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.lib.AbbreviatedObjectId; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.MutableObjectId; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevTree; |
| |
| /** |
| * Index of notes from a note branch. |
| * |
| * This class is not thread-safe, and relies on an |
| * {@link org.eclipse.jgit.lib.ObjectReader} that it borrows/shares with the |
| * caller. The reader can be used during any call, and is not released by this |
| * class. The caller should arrange for releasing the shared |
| * {@code ObjectReader} at the proper times. |
| */ |
| public class NoteMap implements Iterable<Note> { |
| /** |
| * Construct a new empty note map. |
| * |
| * @return an empty note map. |
| */ |
| public static NoteMap newEmptyMap() { |
| NoteMap r = new NoteMap(null /* no reader */); |
| r.root = new LeafBucket(0); |
| return r; |
| } |
| |
| /** |
| * Shorten the note ref name by trimming off the |
| * {@link org.eclipse.jgit.lib.Constants#R_NOTES} prefix if it exists. |
| * |
| * @param noteRefName |
| * a {@link java.lang.String} object. |
| * @return a more user friendly note name |
| */ |
| public static String shortenRefName(String noteRefName) { |
| if (noteRefName.startsWith(Constants.R_NOTES)) |
| return noteRefName.substring(Constants.R_NOTES.length()); |
| return noteRefName; |
| } |
| |
| /** |
| * Load a collection of notes from a branch. |
| * |
| * @param reader |
| * reader to scan the note branch with. This reader may be |
| * retained by the NoteMap for the life of the map in order to |
| * support lazy loading of entries. |
| * @param commit |
| * the revision of the note branch to read. |
| * @return the note map read from the commit. |
| * @throws java.io.IOException |
| * the repository cannot be accessed through the reader. |
| * @throws org.eclipse.jgit.errors.CorruptObjectException |
| * a tree object is corrupt and cannot be read. |
| * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException |
| * a tree object wasn't actually a tree. |
| * @throws org.eclipse.jgit.errors.MissingObjectException |
| * a reference tree object doesn't exist. |
| */ |
| public static NoteMap read(ObjectReader reader, RevCommit commit) |
| throws MissingObjectException, IncorrectObjectTypeException, |
| CorruptObjectException, IOException { |
| return read(reader, commit.getTree()); |
| } |
| |
| /** |
| * Load a collection of notes from a tree. |
| * |
| * @param reader |
| * reader to scan the note branch with. This reader may be |
| * retained by the NoteMap for the life of the map in order to |
| * support lazy loading of entries. |
| * @param tree |
| * the note tree to read. |
| * @return the note map read from the tree. |
| * @throws java.io.IOException |
| * the repository cannot be accessed through the reader. |
| * @throws org.eclipse.jgit.errors.CorruptObjectException |
| * a tree object is corrupt and cannot be read. |
| * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException |
| * a tree object wasn't actually a tree. |
| * @throws org.eclipse.jgit.errors.MissingObjectException |
| * a reference tree object doesn't exist. |
| */ |
| public static NoteMap read(ObjectReader reader, RevTree tree) |
| throws MissingObjectException, IncorrectObjectTypeException, |
| CorruptObjectException, IOException { |
| return readTree(reader, tree); |
| } |
| |
| /** |
| * Load a collection of notes from a tree. |
| * |
| * @param reader |
| * reader to scan the note branch with. This reader may be |
| * retained by the NoteMap for the life of the map in order to |
| * support lazy loading of entries. |
| * @param treeId |
| * the note tree to read. |
| * @return the note map read from the tree. |
| * @throws java.io.IOException |
| * the repository cannot be accessed through the reader. |
| * @throws org.eclipse.jgit.errors.CorruptObjectException |
| * a tree object is corrupt and cannot be read. |
| * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException |
| * a tree object wasn't actually a tree. |
| * @throws org.eclipse.jgit.errors.MissingObjectException |
| * a reference tree object doesn't exist. |
| */ |
| public static NoteMap readTree(ObjectReader reader, ObjectId treeId) |
| throws MissingObjectException, IncorrectObjectTypeException, |
| CorruptObjectException, IOException { |
| NoteMap map = new NoteMap(reader); |
| map.load(treeId); |
| return map; |
| } |
| |
| /** |
| * Construct a new note map from an existing note bucket. |
| * |
| * @param root |
| * the root bucket of this note map |
| * @param reader |
| * reader to scan the note branch with. This reader may be |
| * retained by the NoteMap for the life of the map in order to |
| * support lazy loading of entries. |
| * @return the note map built from the note bucket |
| */ |
| static NoteMap newMap(InMemoryNoteBucket root, ObjectReader reader) { |
| NoteMap map = new NoteMap(reader); |
| map.root = root; |
| return map; |
| } |
| |
| /** Borrowed reader to access the repository. */ |
| private final ObjectReader reader; |
| |
| /** All of the notes that have been loaded. */ |
| private InMemoryNoteBucket root; |
| |
| private NoteMap(ObjectReader reader) { |
| this.reader = reader; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Iterator<Note> iterator() { |
| try { |
| return root.iterator(new MutableObjectId(), reader); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /** |
| * Lookup a note for a specific ObjectId. |
| * |
| * @param id |
| * the object to look for. |
| * @return the note's blob ObjectId, or null if no note exists. |
| * @throws java.io.IOException |
| * a portion of the note space is not accessible. |
| */ |
| public ObjectId get(AnyObjectId id) throws IOException { |
| Note n = root.getNote(id, reader); |
| return n == null ? null : n.getData(); |
| } |
| |
| /** |
| * Lookup a note for a specific ObjectId. |
| * |
| * @param id |
| * the object to look for. |
| * @return the note for the given object id, or null if no note exists. |
| * @throws java.io.IOException |
| * a portion of the note space is not accessible. |
| */ |
| public Note getNote(AnyObjectId id) throws IOException { |
| return root.getNote(id, reader); |
| } |
| |
| /** |
| * Determine if a note exists for the specified ObjectId. |
| * |
| * @param id |
| * the object to look for. |
| * @return true if a note exists; false if there is no note. |
| * @throws java.io.IOException |
| * a portion of the note space is not accessible. |
| */ |
| public boolean contains(AnyObjectId id) throws IOException { |
| return get(id) != null; |
| } |
| |
| /** |
| * Open and return the content of an object's note. |
| * |
| * This method assumes the note is fairly small and can be accessed |
| * efficiently. Larger notes should be accessed by streaming: |
| * |
| * <pre> |
| * ObjectId dataId = thisMap.get(id); |
| * if (dataId != null) |
| * reader.open(dataId).openStream(); |
| * </pre> |
| * |
| * @param id |
| * object to lookup the note of. |
| * @param sizeLimit |
| * maximum number of bytes to return. If the note data size is |
| * larger than this limit, LargeObjectException will be thrown. |
| * @return if a note is defined for {@code id}, the note content. If no note |
| * is defined, null. |
| * @throws org.eclipse.jgit.errors.LargeObjectException |
| * the note data is larger than {@code sizeLimit}. |
| * @throws org.eclipse.jgit.errors.MissingObjectException |
| * the note's blob does not exist in the repository. |
| * @throws java.io.IOException |
| * the note's blob cannot be read from the repository |
| */ |
| public byte[] getCachedBytes(AnyObjectId id, int sizeLimit) |
| throws LargeObjectException, MissingObjectException, IOException { |
| ObjectId dataId = get(id); |
| if (dataId != null) |
| return reader.open(dataId).getCachedBytes(sizeLimit); |
| else |
| return null; |
| } |
| |
| /** |
| * Attach (or remove) a note on an object. |
| * |
| * If no note exists, a new note is stored. If a note already exists for the |
| * given object, it is replaced (or removed). |
| * |
| * This method only updates the map in memory. |
| * |
| * If the caller wants to attach a UTF-8 encoded string message to an |
| * object, {@link #set(AnyObjectId, String, ObjectInserter)} is a convenient |
| * way to encode and update a note in one step. |
| * |
| * @param noteOn |
| * the object to attach the note to. This same ObjectId can later |
| * be used as an argument to {@link #get(AnyObjectId)} or |
| * {@link #getCachedBytes(AnyObjectId, int)} to read back the |
| * {@code noteData}. |
| * @param noteData |
| * data to associate with the note. This must be the ObjectId of |
| * a blob that already exists in the repository. If null the note |
| * will be deleted, if present. |
| * @throws java.io.IOException |
| * a portion of the note space is not accessible. |
| */ |
| public void set(AnyObjectId noteOn, ObjectId noteData) throws IOException { |
| InMemoryNoteBucket newRoot = root.set(noteOn, noteData, reader); |
| if (newRoot == null) { |
| newRoot = new LeafBucket(0); |
| newRoot.nonNotes = root.nonNotes; |
| } |
| root = newRoot; |
| } |
| |
| /** |
| * Attach a note to an object. |
| * |
| * If no note exists, a new note is stored. If a note already exists for the |
| * given object, it is replaced (or removed). |
| * |
| * @param noteOn |
| * the object to attach the note to. This same ObjectId can later |
| * be used as an argument to {@link #get(AnyObjectId)} or |
| * {@link #getCachedBytes(AnyObjectId, int)} to read back the |
| * {@code noteData}. |
| * @param noteData |
| * text to store in the note. The text will be UTF-8 encoded when |
| * stored in the repository. If null the note will be deleted, if |
| * the empty string a note with the empty string will be stored. |
| * @param ins |
| * inserter to write the encoded {@code noteData} out as a blob. |
| * The caller must ensure the inserter is flushed before the |
| * updated note map is made available for reading. |
| * @throws java.io.IOException |
| * the note data could not be stored in the repository. |
| */ |
| public void set(AnyObjectId noteOn, String noteData, ObjectInserter ins) |
| throws IOException { |
| ObjectId dataId; |
| if (noteData != null) { |
| byte[] dataUTF8 = Constants.encode(noteData); |
| dataId = ins.insert(Constants.OBJ_BLOB, dataUTF8); |
| } else { |
| dataId = null; |
| } |
| set(noteOn, dataId); |
| } |
| |
| /** |
| * Remove a note from an object. |
| * |
| * If no note exists, no action is performed. |
| * |
| * This method only updates the map in memory. |
| * |
| * @param noteOn |
| * the object to remove the note from. |
| * @throws java.io.IOException |
| * a portion of the note space is not accessible. |
| */ |
| public void remove(AnyObjectId noteOn) throws IOException { |
| set(noteOn, null); |
| } |
| |
| /** |
| * Write this note map as a tree. |
| * |
| * @param inserter |
| * inserter to use when writing trees to the object database. |
| * Caller is responsible for flushing the inserter before trying |
| * to read the objects, or exposing them through a reference. |
| * @return the top level tree. |
| * @throws java.io.IOException |
| * a tree could not be written. |
| */ |
| public ObjectId writeTree(ObjectInserter inserter) throws IOException { |
| return root.writeTree(inserter); |
| } |
| |
| /** @return the root note bucket */ |
| InMemoryNoteBucket getRoot() { |
| return root; |
| } |
| |
| private void load(ObjectId rootTree) throws MissingObjectException, |
| IncorrectObjectTypeException, CorruptObjectException, IOException { |
| AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); //$NON-NLS-1$ |
| root = NoteParser.parse(none, rootTree, reader); |
| } |
| } |