| /* | |
| * Copyright (C) 2010, Google Inc. and others | |
| * | |
| * This program and the accompanying materials are made available under the | |
| * terms of the Eclipse Distribution License v. 1.0 which is available at | |
| * https://www.eclipse.org/org/documents/edl-v10.php. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| 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; | |
| } | |
| @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); | |
| } | |
| 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); | |
| } | |
| /** | |
| * Get the root note bucket | |
| * | |
| * @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); | |
| } | |
| } |