NoteParser.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 static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
  12. import static org.eclipse.jgit.lib.Constants.encodeASCII;
  13. import static org.eclipse.jgit.lib.FileMode.TREE;
  14. import static org.eclipse.jgit.util.RawParseUtils.parseHexInt4;

  15. import java.io.IOException;

  16. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  17. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  18. import org.eclipse.jgit.lib.FileMode;
  19. import org.eclipse.jgit.lib.MutableObjectId;
  20. import org.eclipse.jgit.lib.ObjectId;
  21. import org.eclipse.jgit.lib.ObjectReader;
  22. import org.eclipse.jgit.treewalk.CanonicalTreeParser;

  23. /** Custom tree parser to select note bucket type and load it. */
  24. final class NoteParser extends CanonicalTreeParser {
  25.     /**
  26.      * Parse a tree object into a {@link NoteBucket} instance.
  27.      *
  28.      * The type of note tree is automatically detected by examining the items
  29.      * within the tree, and allocating the proper storage type based on the
  30.      * first note-like entry encountered. Since the method parses by guessing
  31.      * the type on the first element, malformed note trees can be read as the
  32.      * wrong type of tree.
  33.      *
  34.      * This method is not recursive, it parses the one tree given to it and
  35.      * returns the bucket. If there are subtrees for note storage, they are
  36.      * setup as lazy pointers that will be resolved at a later time.
  37.      *
  38.      * @param prefix
  39.      *            common hex digits that all notes within this tree share. The
  40.      *            root tree has {@code prefix.length() == 0}, the first-level
  41.      *            subtrees should be {@code prefix.length()==2}, etc.
  42.      * @param treeId
  43.      *            the tree to read from the repository.
  44.      * @param reader
  45.      *            reader to access the tree object.
  46.      * @return bucket to holding the notes of the specified tree.
  47.      * @throws IOException
  48.      *             {@code treeId} cannot be accessed.
  49.      */
  50.     static InMemoryNoteBucket parse(AbbreviatedObjectId prefix,
  51.             final ObjectId treeId, final ObjectReader reader)
  52.             throws IOException {
  53.         return new NoteParser(prefix, reader, treeId).parse();
  54.     }

  55.     private final int prefixLen;

  56.     private final int pathPadding;

  57.     private NonNoteEntry firstNonNote;

  58.     private NonNoteEntry lastNonNote;

  59.     private NoteParser(AbbreviatedObjectId prefix, ObjectReader r, ObjectId t)
  60.             throws IncorrectObjectTypeException, IOException {
  61.         super(encodeASCII(prefix.name()), r, t);
  62.         prefixLen = prefix.length();

  63.         // Our path buffer has a '/' that we don't want after the prefix.
  64.         // Drop it by shifting the path down one position.
  65.         pathPadding = 0 < prefixLen ? 1 : 0;
  66.         if (0 < pathPadding)
  67.             System.arraycopy(path, 0, path, pathPadding, prefixLen);
  68.     }

  69.     private InMemoryNoteBucket parse() {
  70.         InMemoryNoteBucket r = parseTree();
  71.         r.nonNotes = firstNonNote;
  72.         return r;
  73.     }

  74.     private InMemoryNoteBucket parseTree() {
  75.         for (; !eof(); next(1)) {
  76.             if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH && isHex())
  77.                 return parseLeafTree();

  78.             else if (getNameLength() == 2 && isHex() && isTree())
  79.                 return parseFanoutTree();

  80.             else
  81.                 storeNonNote();
  82.         }

  83.         // If we cannot determine the style used, assume its a leaf.
  84.         return new LeafBucket(prefixLen);
  85.     }

  86.     private LeafBucket parseLeafTree() {
  87.         final LeafBucket leaf = new LeafBucket(prefixLen);
  88.         final MutableObjectId idBuf = new MutableObjectId();

  89.         for (; !eof(); next(1)) {
  90.             if (parseObjectId(idBuf))
  91.                 leaf.parseOneEntry(idBuf, getEntryObjectId());
  92.             else
  93.                 storeNonNote();
  94.         }

  95.         return leaf;
  96.     }

  97.     private boolean parseObjectId(MutableObjectId id) {
  98.         if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH) {
  99.             try {
  100.                 id.fromString(path, pathPadding);
  101.                 return true;
  102.             } catch (ArrayIndexOutOfBoundsException notHex) {
  103.                 return false;
  104.             }
  105.         }
  106.         return false;
  107.     }

  108.     private FanoutBucket parseFanoutTree() {
  109.         final FanoutBucket fanout = new FanoutBucket(prefixLen);

  110.         for (; !eof(); next(1)) {
  111.             final int cell = parseFanoutCell();
  112.             if (0 <= cell)
  113.                 fanout.setBucket(cell, getEntryObjectId());
  114.             else
  115.                 storeNonNote();
  116.         }

  117.         return fanout;
  118.     }

  119.     private int parseFanoutCell() {
  120.         if (getNameLength() == 2 && isTree()) {
  121.             try {
  122.                 return (parseHexInt4(path[pathOffset + 0]) << 4)
  123.                         | parseHexInt4(path[pathOffset + 1]);
  124.             } catch (ArrayIndexOutOfBoundsException notHex) {
  125.                 return -1;
  126.             }
  127.         }
  128.         return -1;
  129.     }

  130.     private void storeNonNote() {
  131.         ObjectId id = getEntryObjectId();
  132.         FileMode fileMode = getEntryFileMode();

  133.         byte[] name = new byte[getNameLength()];
  134.         getName(name, 0);

  135.         NonNoteEntry ent = new NonNoteEntry(name, fileMode, id);
  136.         if (firstNonNote == null)
  137.             firstNonNote = ent;
  138.         if (lastNonNote != null)
  139.             lastNonNote.next = ent;
  140.         lastNonNote = ent;
  141.     }

  142.     private boolean isTree() {
  143.         return TREE.equals(mode);
  144.     }

  145.     private boolean isHex() {
  146.         try {
  147.             for (int i = pathOffset; i < pathLen; i++)
  148.                 parseHexInt4(path[i]);
  149.             return true;
  150.         } catch (ArrayIndexOutOfBoundsException fail) {
  151.             return false;
  152.         }
  153.     }
  154. }