| /* |
| * 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 static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; |
| import static org.eclipse.jgit.lib.Constants.encodeASCII; |
| import static org.eclipse.jgit.lib.FileMode.TREE; |
| import static org.eclipse.jgit.util.RawParseUtils.parseHexInt4; |
| |
| import java.io.IOException; |
| |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.lib.AbbreviatedObjectId; |
| import org.eclipse.jgit.lib.FileMode; |
| import org.eclipse.jgit.lib.MutableObjectId; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.treewalk.CanonicalTreeParser; |
| |
| /** Custom tree parser to select note bucket type and load it. */ |
| final class NoteParser extends CanonicalTreeParser { |
| /** |
| * Parse a tree object into a {@link NoteBucket} instance. |
| * |
| * The type of note tree is automatically detected by examining the items |
| * within the tree, and allocating the proper storage type based on the |
| * first note-like entry encountered. Since the method parses by guessing |
| * the type on the first element, malformed note trees can be read as the |
| * wrong type of tree. |
| * |
| * This method is not recursive, it parses the one tree given to it and |
| * returns the bucket. If there are subtrees for note storage, they are |
| * setup as lazy pointers that will be resolved at a later time. |
| * |
| * @param prefix |
| * common hex digits that all notes within this tree share. The |
| * root tree has {@code prefix.length() == 0}, the first-level |
| * subtrees should be {@code prefix.length()==2}, etc. |
| * @param treeId |
| * the tree to read from the repository. |
| * @param reader |
| * reader to access the tree object. |
| * @return bucket to holding the notes of the specified tree. |
| * @throws IOException |
| * {@code treeId} cannot be accessed. |
| */ |
| static InMemoryNoteBucket parse(AbbreviatedObjectId prefix, |
| final ObjectId treeId, final ObjectReader reader) |
| throws IOException { |
| return new NoteParser(prefix, reader, treeId).parse(); |
| } |
| |
| private final int prefixLen; |
| |
| private final int pathPadding; |
| |
| private NonNoteEntry firstNonNote; |
| |
| private NonNoteEntry lastNonNote; |
| |
| private NoteParser(AbbreviatedObjectId prefix, ObjectReader r, ObjectId t) |
| throws IncorrectObjectTypeException, IOException { |
| super(encodeASCII(prefix.name()), r, t); |
| prefixLen = prefix.length(); |
| |
| // Our path buffer has a '/' that we don't want after the prefix. |
| // Drop it by shifting the path down one position. |
| pathPadding = 0 < prefixLen ? 1 : 0; |
| if (0 < pathPadding) |
| System.arraycopy(path, 0, path, pathPadding, prefixLen); |
| } |
| |
| private InMemoryNoteBucket parse() { |
| InMemoryNoteBucket r = parseTree(); |
| r.nonNotes = firstNonNote; |
| return r; |
| } |
| |
| private InMemoryNoteBucket parseTree() { |
| for (; !eof(); next(1)) { |
| if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH && isHex()) |
| return parseLeafTree(); |
| |
| else if (getNameLength() == 2 && isHex() && isTree()) |
| return parseFanoutTree(); |
| |
| else |
| storeNonNote(); |
| } |
| |
| // If we cannot determine the style used, assume its a leaf. |
| return new LeafBucket(prefixLen); |
| } |
| |
| private LeafBucket parseLeafTree() { |
| final LeafBucket leaf = new LeafBucket(prefixLen); |
| final MutableObjectId idBuf = new MutableObjectId(); |
| |
| for (; !eof(); next(1)) { |
| if (parseObjectId(idBuf)) |
| leaf.parseOneEntry(idBuf, getEntryObjectId()); |
| else |
| storeNonNote(); |
| } |
| |
| return leaf; |
| } |
| |
| private boolean parseObjectId(MutableObjectId id) { |
| if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH) { |
| try { |
| id.fromString(path, pathPadding); |
| return true; |
| } catch (ArrayIndexOutOfBoundsException notHex) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| private FanoutBucket parseFanoutTree() { |
| final FanoutBucket fanout = new FanoutBucket(prefixLen); |
| |
| for (; !eof(); next(1)) { |
| final int cell = parseFanoutCell(); |
| if (0 <= cell) |
| fanout.setBucket(cell, getEntryObjectId()); |
| else |
| storeNonNote(); |
| } |
| |
| return fanout; |
| } |
| |
| private int parseFanoutCell() { |
| if (getNameLength() == 2 && isTree()) { |
| try { |
| return (parseHexInt4(path[pathOffset + 0]) << 4) |
| | parseHexInt4(path[pathOffset + 1]); |
| } catch (ArrayIndexOutOfBoundsException notHex) { |
| return -1; |
| } |
| } |
| return -1; |
| } |
| |
| private void storeNonNote() { |
| ObjectId id = getEntryObjectId(); |
| FileMode fileMode = getEntryFileMode(); |
| |
| byte[] name = new byte[getNameLength()]; |
| getName(name, 0); |
| |
| NonNoteEntry ent = new NonNoteEntry(name, fileMode, id); |
| if (firstNonNote == null) |
| firstNonNote = ent; |
| if (lastNonNote != null) |
| lastNonNote.next = ent; |
| lastNonNote = ent; |
| } |
| |
| private boolean isTree() { |
| return TREE.equals(mode); |
| } |
| |
| private boolean isHex() { |
| try { |
| for (int i = pathOffset; i < pathLen; i++) |
| parseHexInt4(path[i]); |
| return true; |
| } catch (ArrayIndexOutOfBoundsException fail) { |
| return false; |
| } |
| } |
| } |