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