| /* | |
| * 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; | |
| } | |
| } | |
| } |