View Javadoc
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  
11  package org.eclipse.jgit.notes;
12  
13  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
14  import static org.eclipse.jgit.lib.Constants.encodeASCII;
15  import static org.eclipse.jgit.lib.FileMode.TREE;
16  import static org.eclipse.jgit.util.RawParseUtils.parseHexInt4;
17  
18  import java.io.IOException;
19  
20  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
21  import org.eclipse.jgit.lib.AbbreviatedObjectId;
22  import org.eclipse.jgit.lib.FileMode;
23  import org.eclipse.jgit.lib.MutableObjectId;
24  import org.eclipse.jgit.lib.ObjectId;
25  import org.eclipse.jgit.lib.ObjectReader;
26  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
27  
28  /** Custom tree parser to select note bucket type and load it. */
29  final class NoteParser extends CanonicalTreeParser {
30  	/**
31  	 * Parse a tree object into a {@link NoteBucket} instance.
32  	 *
33  	 * The type of note tree is automatically detected by examining the items
34  	 * within the tree, and allocating the proper storage type based on the
35  	 * first note-like entry encountered. Since the method parses by guessing
36  	 * the type on the first element, malformed note trees can be read as the
37  	 * wrong type of tree.
38  	 *
39  	 * This method is not recursive, it parses the one tree given to it and
40  	 * returns the bucket. If there are subtrees for note storage, they are
41  	 * setup as lazy pointers that will be resolved at a later time.
42  	 *
43  	 * @param prefix
44  	 *            common hex digits that all notes within this tree share. The
45  	 *            root tree has {@code prefix.length() == 0}, the first-level
46  	 *            subtrees should be {@code prefix.length()==2}, etc.
47  	 * @param treeId
48  	 *            the tree to read from the repository.
49  	 * @param reader
50  	 *            reader to access the tree object.
51  	 * @return bucket to holding the notes of the specified tree.
52  	 * @throws IOException
53  	 *             {@code treeId} cannot be accessed.
54  	 */
55  	static InMemoryNoteBucket parse(AbbreviatedObjectId prefix,
56  			final ObjectId treeId, final ObjectReader reader)
57  			throws IOException {
58  		return new NoteParser(prefix, reader, treeId).parse();
59  	}
60  
61  	private final int prefixLen;
62  
63  	private final int pathPadding;
64  
65  	private NonNoteEntry firstNonNote;
66  
67  	private NonNoteEntry lastNonNote;
68  
69  	private NoteParser(AbbreviatedObjectId prefix, ObjectReader r, ObjectId t)
70  			throws IncorrectObjectTypeException, IOException {
71  		super(encodeASCII(prefix.name()), r, t);
72  		prefixLen = prefix.length();
73  
74  		// Our path buffer has a '/' that we don't want after the prefix.
75  		// Drop it by shifting the path down one position.
76  		pathPadding = 0 < prefixLen ? 1 : 0;
77  		if (0 < pathPadding)
78  			System.arraycopy(path, 0, path, pathPadding, prefixLen);
79  	}
80  
81  	private InMemoryNoteBucket parse() {
82  		InMemoryNoteBucket r = parseTree();
83  		r.nonNotes = firstNonNote;
84  		return r;
85  	}
86  
87  	private InMemoryNoteBucket parseTree() {
88  		for (; !eof(); next(1)) {
89  			if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH && isHex())
90  				return parseLeafTree();
91  
92  			else if (getNameLength() == 2 && isHex() && isTree())
93  				return parseFanoutTree();
94  
95  			else
96  				storeNonNote();
97  		}
98  
99  		// If we cannot determine the style used, assume its a leaf.
100 		return new LeafBucket(prefixLen);
101 	}
102 
103 	private LeafBucket parseLeafTree() {
104 		final LeafBucket leaf = new LeafBucket(prefixLen);
105 		final MutableObjectId idBuf = new MutableObjectId();
106 
107 		for (; !eof(); next(1)) {
108 			if (parseObjectId(idBuf))
109 				leaf.parseOneEntry(idBuf, getEntryObjectId());
110 			else
111 				storeNonNote();
112 		}
113 
114 		return leaf;
115 	}
116 
117 	private boolean parseObjectId(MutableObjectId id) {
118 		if (pathLen == pathPadding + OBJECT_ID_STRING_LENGTH) {
119 			try {
120 				id.fromString(path, pathPadding);
121 				return true;
122 			} catch (ArrayIndexOutOfBoundsException notHex) {
123 				return false;
124 			}
125 		}
126 		return false;
127 	}
128 
129 	private FanoutBucket parseFanoutTree() {
130 		final FanoutBucket fanout = new FanoutBucket(prefixLen);
131 
132 		for (; !eof(); next(1)) {
133 			final int cell = parseFanoutCell();
134 			if (0 <= cell)
135 				fanout.setBucket(cell, getEntryObjectId());
136 			else
137 				storeNonNote();
138 		}
139 
140 		return fanout;
141 	}
142 
143 	private int parseFanoutCell() {
144 		if (getNameLength() == 2 && isTree()) {
145 			try {
146 				return (parseHexInt4(path[pathOffset + 0]) << 4)
147 						| parseHexInt4(path[pathOffset + 1]);
148 			} catch (ArrayIndexOutOfBoundsException notHex) {
149 				return -1;
150 			}
151 		}
152 		return -1;
153 	}
154 
155 	private void storeNonNote() {
156 		ObjectId id = getEntryObjectId();
157 		FileMode fileMode = getEntryFileMode();
158 
159 		byte[] name = new byte[getNameLength()];
160 		getName(name, 0);
161 
162 		NonNoteEntry ent = new NonNoteEntry(name, fileMode, id);
163 		if (firstNonNote == null)
164 			firstNonNote = ent;
165 		if (lastNonNote != null)
166 			lastNonNote.next = ent;
167 		lastNonNote = ent;
168 	}
169 
170 	private boolean isTree() {
171 		return TREE.equals(mode);
172 	}
173 
174 	private boolean isHex() {
175 		try {
176 			for (int i = pathOffset; i < pathLen; i++)
177 				parseHexInt4(path[i]);
178 			return true;
179 		} catch (ArrayIndexOutOfBoundsException fail) {
180 			return false;
181 		}
182 	}
183 }