/* * 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 { /** * 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 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: * *
	 * ObjectId dataId = thisMap.get(id);
	 * if (dataId != null)
	 * 	reader.open(dataId).openStream();
	 * 
* * @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); } /** @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); } }