NoteMap.java

  1. /*
  2.  * Copyright (C) 2010, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.notes;

  11. import java.io.IOException;
  12. import java.util.Iterator;

  13. import org.eclipse.jgit.errors.CorruptObjectException;
  14. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  15. import org.eclipse.jgit.errors.LargeObjectException;
  16. import org.eclipse.jgit.errors.MissingObjectException;
  17. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  18. import org.eclipse.jgit.lib.AnyObjectId;
  19. import org.eclipse.jgit.lib.Constants;
  20. import org.eclipse.jgit.lib.MutableObjectId;
  21. import org.eclipse.jgit.lib.ObjectId;
  22. import org.eclipse.jgit.lib.ObjectInserter;
  23. import org.eclipse.jgit.lib.ObjectReader;
  24. import org.eclipse.jgit.revwalk.RevCommit;
  25. import org.eclipse.jgit.revwalk.RevTree;

  26. /**
  27.  * Index of notes from a note branch.
  28.  *
  29.  * This class is not thread-safe, and relies on an
  30.  * {@link org.eclipse.jgit.lib.ObjectReader} that it borrows/shares with the
  31.  * caller. The reader can be used during any call, and is not released by this
  32.  * class. The caller should arrange for releasing the shared
  33.  * {@code ObjectReader} at the proper times.
  34.  */
  35. public class NoteMap implements Iterable<Note> {
  36.     /**
  37.      * Construct a new empty note map.
  38.      *
  39.      * @return an empty note map.
  40.      */
  41.     public static NoteMap newEmptyMap() {
  42.         NoteMap r = new NoteMap(null /* no reader */);
  43.         r.root = new LeafBucket(0);
  44.         return r;
  45.     }

  46.     /**
  47.      * Shorten the note ref name by trimming off the
  48.      * {@link org.eclipse.jgit.lib.Constants#R_NOTES} prefix if it exists.
  49.      *
  50.      * @param noteRefName
  51.      *            a {@link java.lang.String} object.
  52.      * @return a more user friendly note name
  53.      */
  54.     public static String shortenRefName(String noteRefName) {
  55.         if (noteRefName.startsWith(Constants.R_NOTES))
  56.             return noteRefName.substring(Constants.R_NOTES.length());
  57.         return noteRefName;
  58.     }

  59.     /**
  60.      * Load a collection of notes from a branch.
  61.      *
  62.      * @param reader
  63.      *            reader to scan the note branch with. This reader may be
  64.      *            retained by the NoteMap for the life of the map in order to
  65.      *            support lazy loading of entries.
  66.      * @param commit
  67.      *            the revision of the note branch to read.
  68.      * @return the note map read from the commit.
  69.      * @throws java.io.IOException
  70.      *             the repository cannot be accessed through the reader.
  71.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  72.      *             a tree object is corrupt and cannot be read.
  73.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  74.      *             a tree object wasn't actually a tree.
  75.      * @throws org.eclipse.jgit.errors.MissingObjectException
  76.      *             a reference tree object doesn't exist.
  77.      */
  78.     public static NoteMap read(ObjectReader reader, RevCommit commit)
  79.             throws MissingObjectException, IncorrectObjectTypeException,
  80.             CorruptObjectException, IOException {
  81.         return read(reader, commit.getTree());
  82.     }

  83.     /**
  84.      * Load a collection of notes from a tree.
  85.      *
  86.      * @param reader
  87.      *            reader to scan the note branch with. This reader may be
  88.      *            retained by the NoteMap for the life of the map in order to
  89.      *            support lazy loading of entries.
  90.      * @param tree
  91.      *            the note tree to read.
  92.      * @return the note map read from the tree.
  93.      * @throws java.io.IOException
  94.      *             the repository cannot be accessed through the reader.
  95.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  96.      *             a tree object is corrupt and cannot be read.
  97.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  98.      *             a tree object wasn't actually a tree.
  99.      * @throws org.eclipse.jgit.errors.MissingObjectException
  100.      *             a reference tree object doesn't exist.
  101.      */
  102.     public static NoteMap read(ObjectReader reader, RevTree tree)
  103.             throws MissingObjectException, IncorrectObjectTypeException,
  104.             CorruptObjectException, IOException {
  105.         return readTree(reader, tree);
  106.     }

  107.     /**
  108.      * Load a collection of notes from a tree.
  109.      *
  110.      * @param reader
  111.      *            reader to scan the note branch with. This reader may be
  112.      *            retained by the NoteMap for the life of the map in order to
  113.      *            support lazy loading of entries.
  114.      * @param treeId
  115.      *            the note tree to read.
  116.      * @return the note map read from the tree.
  117.      * @throws java.io.IOException
  118.      *             the repository cannot be accessed through the reader.
  119.      * @throws org.eclipse.jgit.errors.CorruptObjectException
  120.      *             a tree object is corrupt and cannot be read.
  121.      * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
  122.      *             a tree object wasn't actually a tree.
  123.      * @throws org.eclipse.jgit.errors.MissingObjectException
  124.      *             a reference tree object doesn't exist.
  125.      */
  126.     public static NoteMap readTree(ObjectReader reader, ObjectId treeId)
  127.             throws MissingObjectException, IncorrectObjectTypeException,
  128.             CorruptObjectException, IOException {
  129.         NoteMap map = new NoteMap(reader);
  130.         map.load(treeId);
  131.         return map;
  132.     }

  133.     /**
  134.      * Construct a new note map from an existing note bucket.
  135.      *
  136.      * @param root
  137.      *            the root bucket of this note map
  138.      * @param reader
  139.      *            reader to scan the note branch with. This reader may be
  140.      *            retained by the NoteMap for the life of the map in order to
  141.      *            support lazy loading of entries.
  142.      * @return the note map built from the note bucket
  143.      */
  144.     static NoteMap newMap(InMemoryNoteBucket root, ObjectReader reader) {
  145.         NoteMap map = new NoteMap(reader);
  146.         map.root = root;
  147.         return map;
  148.     }

  149.     /** Borrowed reader to access the repository. */
  150.     private final ObjectReader reader;

  151.     /** All of the notes that have been loaded. */
  152.     private InMemoryNoteBucket root;

  153.     private NoteMap(ObjectReader reader) {
  154.         this.reader = reader;
  155.     }

  156.     /** {@inheritDoc} */
  157.     @Override
  158.     public Iterator<Note> iterator() {
  159.         try {
  160.             return root.iterator(new MutableObjectId(), reader);
  161.         } catch (IOException e) {
  162.             throw new RuntimeException(e);
  163.         }
  164.     }

  165.     /**
  166.      * Lookup a note for a specific ObjectId.
  167.      *
  168.      * @param id
  169.      *            the object to look for.
  170.      * @return the note's blob ObjectId, or null if no note exists.
  171.      * @throws java.io.IOException
  172.      *             a portion of the note space is not accessible.
  173.      */
  174.     public ObjectId get(AnyObjectId id) throws IOException {
  175.         Note n = root.getNote(id, reader);
  176.         return n == null ? null : n.getData();
  177.     }

  178.     /**
  179.      * Lookup a note for a specific ObjectId.
  180.      *
  181.      * @param id
  182.      *            the object to look for.
  183.      * @return the note for the given object id, or null if no note exists.
  184.      * @throws java.io.IOException
  185.      *             a portion of the note space is not accessible.
  186.      */
  187.     public Note getNote(AnyObjectId id) throws IOException {
  188.         return root.getNote(id, reader);
  189.     }

  190.     /**
  191.      * Determine if a note exists for the specified ObjectId.
  192.      *
  193.      * @param id
  194.      *            the object to look for.
  195.      * @return true if a note exists; false if there is no note.
  196.      * @throws java.io.IOException
  197.      *             a portion of the note space is not accessible.
  198.      */
  199.     public boolean contains(AnyObjectId id) throws IOException {
  200.         return get(id) != null;
  201.     }

  202.     /**
  203.      * Open and return the content of an object's note.
  204.      *
  205.      * This method assumes the note is fairly small and can be accessed
  206.      * efficiently. Larger notes should be accessed by streaming:
  207.      *
  208.      * <pre>
  209.      * ObjectId dataId = thisMap.get(id);
  210.      * if (dataId != null)
  211.      *  reader.open(dataId).openStream();
  212.      * </pre>
  213.      *
  214.      * @param id
  215.      *            object to lookup the note of.
  216.      * @param sizeLimit
  217.      *            maximum number of bytes to return. If the note data size is
  218.      *            larger than this limit, LargeObjectException will be thrown.
  219.      * @return if a note is defined for {@code id}, the note content. If no note
  220.      *         is defined, null.
  221.      * @throws org.eclipse.jgit.errors.LargeObjectException
  222.      *             the note data is larger than {@code sizeLimit}.
  223.      * @throws org.eclipse.jgit.errors.MissingObjectException
  224.      *             the note's blob does not exist in the repository.
  225.      * @throws java.io.IOException
  226.      *             the note's blob cannot be read from the repository
  227.      */
  228.     public byte[] getCachedBytes(AnyObjectId id, int sizeLimit)
  229.             throws LargeObjectException, MissingObjectException, IOException {
  230.         ObjectId dataId = get(id);
  231.         if (dataId != null) {
  232.             return reader.open(dataId).getCachedBytes(sizeLimit);
  233.         }
  234.         return null;
  235.     }

  236.     /**
  237.      * Attach (or remove) a note on an object.
  238.      *
  239.      * If no note exists, a new note is stored. If a note already exists for the
  240.      * given object, it is replaced (or removed).
  241.      *
  242.      * This method only updates the map in memory.
  243.      *
  244.      * If the caller wants to attach a UTF-8 encoded string message to an
  245.      * object, {@link #set(AnyObjectId, String, ObjectInserter)} is a convenient
  246.      * way to encode and update a note in one step.
  247.      *
  248.      * @param noteOn
  249.      *            the object to attach the note to. This same ObjectId can later
  250.      *            be used as an argument to {@link #get(AnyObjectId)} or
  251.      *            {@link #getCachedBytes(AnyObjectId, int)} to read back the
  252.      *            {@code noteData}.
  253.      * @param noteData
  254.      *            data to associate with the note. This must be the ObjectId of
  255.      *            a blob that already exists in the repository. If null the note
  256.      *            will be deleted, if present.
  257.      * @throws java.io.IOException
  258.      *             a portion of the note space is not accessible.
  259.      */
  260.     public void set(AnyObjectId noteOn, ObjectId noteData) throws IOException {
  261.         InMemoryNoteBucket newRoot = root.set(noteOn, noteData, reader);
  262.         if (newRoot == null) {
  263.             newRoot = new LeafBucket(0);
  264.             newRoot.nonNotes = root.nonNotes;
  265.         }
  266.         root = newRoot;
  267.     }

  268.     /**
  269.      * Attach a note to an object.
  270.      *
  271.      * If no note exists, a new note is stored. If a note already exists for the
  272.      * given object, it is replaced (or removed).
  273.      *
  274.      * @param noteOn
  275.      *            the object to attach the note to. This same ObjectId can later
  276.      *            be used as an argument to {@link #get(AnyObjectId)} or
  277.      *            {@link #getCachedBytes(AnyObjectId, int)} to read back the
  278.      *            {@code noteData}.
  279.      * @param noteData
  280.      *            text to store in the note. The text will be UTF-8 encoded when
  281.      *            stored in the repository. If null the note will be deleted, if
  282.      *            the empty string a note with the empty string will be stored.
  283.      * @param ins
  284.      *            inserter to write the encoded {@code noteData} out as a blob.
  285.      *            The caller must ensure the inserter is flushed before the
  286.      *            updated note map is made available for reading.
  287.      * @throws java.io.IOException
  288.      *             the note data could not be stored in the repository.
  289.      */
  290.     public void set(AnyObjectId noteOn, String noteData, ObjectInserter ins)
  291.             throws IOException {
  292.         ObjectId dataId;
  293.         if (noteData != null) {
  294.             byte[] dataUTF8 = Constants.encode(noteData);
  295.             dataId = ins.insert(Constants.OBJ_BLOB, dataUTF8);
  296.         } else {
  297.             dataId = null;
  298.         }
  299.         set(noteOn, dataId);
  300.     }

  301.     /**
  302.      * Remove a note from an object.
  303.      *
  304.      * If no note exists, no action is performed.
  305.      *
  306.      * This method only updates the map in memory.
  307.      *
  308.      * @param noteOn
  309.      *            the object to remove the note from.
  310.      * @throws java.io.IOException
  311.      *             a portion of the note space is not accessible.
  312.      */
  313.     public void remove(AnyObjectId noteOn) throws IOException {
  314.         set(noteOn, null);
  315.     }

  316.     /**
  317.      * Write this note map as a tree.
  318.      *
  319.      * @param inserter
  320.      *            inserter to use when writing trees to the object database.
  321.      *            Caller is responsible for flushing the inserter before trying
  322.      *            to read the objects, or exposing them through a reference.
  323.      * @return the top level tree.
  324.      * @throws java.io.IOException
  325.      *             a tree could not be written.
  326.      */
  327.     public ObjectId writeTree(ObjectInserter inserter) throws IOException {
  328.         return root.writeTree(inserter);
  329.     }

  330.     /** @return the root note bucket */
  331.     InMemoryNoteBucket getRoot() {
  332.         return root;
  333.     }

  334.     private void load(ObjectId rootTree) throws MissingObjectException,
  335.             IncorrectObjectTypeException, CorruptObjectException, IOException {
  336.         AbbreviatedObjectId none = AbbreviatedObjectId.fromString(""); //$NON-NLS-1$
  337.         root = NoteParser.parse(none, rootTree, reader);
  338.     }
  339. }