View Javadoc
1   /*
2    * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
3    * Copyright (C) 2010, JetBrains s.r.o. and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.internal.storage.file;
13  
14  import java.io.File;
15  import java.io.IOException;
16  import java.util.Collection;
17  import java.util.HashSet;
18  import java.util.Set;
19  
20  import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
21  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
22  import org.eclipse.jgit.internal.storage.pack.PackWriter;
23  import org.eclipse.jgit.lib.AbbreviatedObjectId;
24  import org.eclipse.jgit.lib.AnyObjectId;
25  import org.eclipse.jgit.lib.Config;
26  import org.eclipse.jgit.lib.Constants;
27  import org.eclipse.jgit.lib.ObjectDatabase;
28  import org.eclipse.jgit.lib.ObjectId;
29  import org.eclipse.jgit.lib.ObjectIdOwnerMap;
30  import org.eclipse.jgit.lib.ObjectLoader;
31  import org.eclipse.jgit.util.FS;
32  
33  /**
34   * The cached instance of an {@link ObjectDirectory}.
35   * <p>
36   * This class caches the list of loose objects in memory, so the file system is
37   * not queried with stat calls.
38   */
39  class CachedObjectDirectory extends FileObjectDatabase {
40  	/**
41  	 * The set that contains unpacked objects identifiers, it is created when
42  	 * the cached instance is created.
43  	 */
44  	private ObjectIdOwnerMap<UnpackedObjectId> unpackedObjects;
45  
46  	private final ObjectDirectory wrapped;
47  
48  	private CachedObjectDirectory[] alts;
49  
50  	/**
51  	 * The constructor
52  	 *
53  	 * @param wrapped
54  	 *            the wrapped database
55  	 */
56  	CachedObjectDirectory(ObjectDirectory wrapped) {
57  		this.wrapped = wrapped;
58  		this.unpackedObjects = scanLoose();
59  	}
60  
61  	private ObjectIdOwnerMap<UnpackedObjectId> scanLoose() {
62  		ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<>();
63  		File objects = wrapped.getDirectory();
64  		String[] fanout = objects.list();
65  		if (fanout == null)
66  			return m;
67  		for (String d : fanout) {
68  			if (d.length() != 2)
69  				continue;
70  			String[] entries = new File(objects, d).list();
71  			if (entries == null)
72  				continue;
73  			for (String e : entries) {
74  				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
75  					continue;
76  				try {
77  					ObjectId id = ObjectId.fromString(d + e);
78  					m.add(new UnpackedObjectId(id));
79  				} catch (IllegalArgumentException notAnObject) {
80  					// ignoring the file that does not represent loose object
81  				}
82  			}
83  		}
84  		return m;
85  	}
86  
87  	/** {@inheritDoc} */
88  	@Override
89  	public void close() {
90  		// Don't close anything.
91  	}
92  
93  	/** {@inheritDoc} */
94  	@Override
95  	public ObjectDatabase newCachedDatabase() {
96  		return this;
97  	}
98  
99  	@Override
100 	File getDirectory() {
101 		return wrapped.getDirectory();
102 	}
103 
104 	@Override
105 	File fileFor(AnyObjectId id) {
106 		return wrapped.fileFor(id);
107 	}
108 
109 	@Override
110 	Config getConfig() {
111 		return wrapped.getConfig();
112 	}
113 
114 	@Override
115 	FS getFS() {
116 		return wrapped.getFS();
117 	}
118 
119 	@Override
120 	Set<ObjectId> getShallowCommits() throws IOException {
121 		return wrapped.getShallowCommits();
122 	}
123 
124 	private CachedObjectDirectory[] myAlternates() {
125 		if (alts == null) {
126 			ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates();
127 			alts = new CachedObjectDirectory[src.length];
128 			for (int i = 0; i < alts.length; i++)
129 				alts[i] = src[i].db.newCachedFileObjectDatabase();
130 		}
131 		return alts;
132 	}
133 
134 	private Set<AlternateHandle.Id> skipMe(Set<AlternateHandle.Id> skips) {
135 		Set<AlternateHandle.Id> withMe = new HashSet<>();
136 		if (skips != null) {
137 			withMe.addAll(skips);
138 		}
139 		withMe.add(getAlternateId());
140 		return withMe;
141 	}
142 
143 	@Override
144 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
145 			throws IOException {
146 		wrapped.resolve(matches, id);
147 	}
148 
149 	/** {@inheritDoc} */
150 	@Override
151 	public boolean has(AnyObjectId objectId) throws IOException {
152 		return has(objectId, null);
153 	}
154 
155 	private boolean has(AnyObjectId objectId, Set<AlternateHandle.Id> skips)
156 			throws IOException {
157 		if (unpackedObjects.contains(objectId)) {
158 			return true;
159 		}
160 		if (wrapped.hasPackedObject(objectId)) {
161 			return true;
162 		}
163 		skips = skipMe(skips);
164 		for (CachedObjectDirectory alt : myAlternates()) {
165 			if (!skips.contains(alt.getAlternateId())) {
166 				if (alt.has(objectId, skips)) {
167 					return true;
168 				}
169 			}
170 		}
171 		return false;
172 	}
173 
174 	@Override
175 	ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
176 			throws IOException {
177 		return openObject(curs, objectId, null);
178 	}
179 
180 	private ObjectLoader openObject(final WindowCursor curs,
181 			final AnyObjectId objectId, Set<AlternateHandle.Id> skips)
182 			throws IOException {
183 		ObjectLoader ldr = openLooseObject(curs, objectId);
184 		if (ldr != null) {
185 			return ldr;
186 		}
187 		ldr = wrapped.openPackedObject(curs, objectId);
188 		if (ldr != null) {
189 			return ldr;
190 		}
191 		skips = skipMe(skips);
192 		for (CachedObjectDirectory alt : myAlternates()) {
193 			if (!skips.contains(alt.getAlternateId())) {
194 				ldr = alt.openObject(curs, objectId, skips);
195 				if (ldr != null) {
196 					return ldr;
197 				}
198 			}
199 		}
200 		return null;
201 	}
202 
203 	@Override
204 	long getObjectSize(WindowCursor curs, AnyObjectId objectId)
205 			throws IOException {
206 		// Object size is unlikely to be requested from contexts using
207 		// this type. Don't bother trying to accelerate the lookup.
208 		return wrapped.getObjectSize(curs, objectId);
209 	}
210 
211 	@Override
212 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
213 			throws IOException {
214 		if (unpackedObjects.contains(id)) {
215 			ObjectLoader ldr = wrapped.openLooseObject(curs, id);
216 			if (ldr != null)
217 				return ldr;
218 			unpackedObjects = scanLoose();
219 		}
220 		return null;
221 	}
222 
223 	@Override
224 	InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId objectId,
225 			boolean createDuplicate) throws IOException {
226 		InsertLooseObjectResult result = wrapped.insertUnpackedObject(tmp,
227 				objectId, createDuplicate);
228 		switch (result) {
229 		case INSERTED:
230 		case EXISTS_LOOSE:
231 			unpackedObjects.addIfAbsent(new UnpackedObjectId(objectId));
232 			break;
233 
234 		case EXISTS_PACKED:
235 		case FAILURE:
236 			break;
237 		}
238 		return result;
239 	}
240 
241 	@Override
242 	PackFile openPack(File pack) throws IOException {
243 		return wrapped.openPack(pack);
244 	}
245 
246 	@Override
247 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
248 			WindowCursor curs) throws IOException {
249 		wrapped.selectObjectRepresentation(packer, otp, curs);
250 	}
251 
252 	@Override
253 	Collection<PackFile> getPacks() {
254 		return wrapped.getPacks();
255 	}
256 
257 	private static class UnpackedObjectId extends ObjectIdOwnerMap.Entry {
258 		UnpackedObjectId(AnyObjectId id) {
259 			super(id);
260 		}
261 	}
262 
263 	private AlternateHandle.Id getAlternateId() {
264 		return wrapped.getAlternateId();
265 	}
266 }