View Javadoc
1   /*
2    * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com>
3    * Copyright (C) 2010, JetBrains s.r.o.
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.internal.storage.file;
46  
47  import java.io.File;
48  import java.io.IOException;
49  import java.util.Collection;
50  import java.util.HashSet;
51  import java.util.Set;
52  
53  import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
54  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
55  import org.eclipse.jgit.internal.storage.pack.PackWriter;
56  import org.eclipse.jgit.lib.AbbreviatedObjectId;
57  import org.eclipse.jgit.lib.AnyObjectId;
58  import org.eclipse.jgit.lib.Config;
59  import org.eclipse.jgit.lib.Constants;
60  import org.eclipse.jgit.lib.ObjectDatabase;
61  import org.eclipse.jgit.lib.ObjectId;
62  import org.eclipse.jgit.lib.ObjectIdOwnerMap;
63  import org.eclipse.jgit.lib.ObjectLoader;
64  import org.eclipse.jgit.util.FS;
65  
66  /**
67   * The cached instance of an {@link ObjectDirectory}.
68   * <p>
69   * This class caches the list of loose objects in memory, so the file system is
70   * not queried with stat calls.
71   */
72  class CachedObjectDirectory extends FileObjectDatabase {
73  	/**
74  	 * The set that contains unpacked objects identifiers, it is created when
75  	 * the cached instance is created.
76  	 */
77  	private ObjectIdOwnerMap<UnpackedObjectId> unpackedObjects;
78  
79  	private final ObjectDirectory wrapped;
80  
81  	private CachedObjectDirectory[] alts;
82  
83  	/**
84  	 * The constructor
85  	 *
86  	 * @param wrapped
87  	 *            the wrapped database
88  	 */
89  	CachedObjectDirectory(ObjectDirectory wrapped) {
90  		this.wrapped = wrapped;
91  		this.unpackedObjects = scanLoose();
92  	}
93  
94  	private ObjectIdOwnerMap<UnpackedObjectId> scanLoose() {
95  		ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<>();
96  		File objects = wrapped.getDirectory();
97  		String[] fanout = objects.list();
98  		if (fanout == null)
99  			return m;
100 		for (String d : fanout) {
101 			if (d.length() != 2)
102 				continue;
103 			String[] entries = new File(objects, d).list();
104 			if (entries == null)
105 				continue;
106 			for (String e : entries) {
107 				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
108 					continue;
109 				try {
110 					ObjectId id = ObjectId.fromString(d + e);
111 					m.add(new UnpackedObjectId(id));
112 				} catch (IllegalArgumentException notAnObject) {
113 					// ignoring the file that does not represent loose object
114 				}
115 			}
116 		}
117 		return m;
118 	}
119 
120 	/** {@inheritDoc} */
121 	@Override
122 	public void close() {
123 		// Don't close anything.
124 	}
125 
126 	/** {@inheritDoc} */
127 	@Override
128 	public ObjectDatabase newCachedDatabase() {
129 		return this;
130 	}
131 
132 	@Override
133 	File getDirectory() {
134 		return wrapped.getDirectory();
135 	}
136 
137 	@Override
138 	File fileFor(AnyObjectId id) {
139 		return wrapped.fileFor(id);
140 	}
141 
142 	@Override
143 	Config getConfig() {
144 		return wrapped.getConfig();
145 	}
146 
147 	@Override
148 	FS getFS() {
149 		return wrapped.getFS();
150 	}
151 
152 	@Override
153 	Set<ObjectId> getShallowCommits() throws IOException {
154 		return wrapped.getShallowCommits();
155 	}
156 
157 	private CachedObjectDirectory[] myAlternates() {
158 		if (alts == null) {
159 			ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates();
160 			alts = new CachedObjectDirectory[src.length];
161 			for (int i = 0; i < alts.length; i++)
162 				alts[i] = src[i].db.newCachedFileObjectDatabase();
163 		}
164 		return alts;
165 	}
166 
167 	private Set<AlternateHandle.Id> skipMe(Set<AlternateHandle.Id> skips) {
168 		Set<AlternateHandle.Id> withMe = new HashSet<>();
169 		if (skips != null) {
170 			withMe.addAll(skips);
171 		}
172 		withMe.add(getAlternateId());
173 		return withMe;
174 	}
175 
176 	@Override
177 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
178 			throws IOException {
179 		wrapped.resolve(matches, id);
180 	}
181 
182 	/** {@inheritDoc} */
183 	@Override
184 	public boolean has(AnyObjectId objectId) throws IOException {
185 		return has(objectId, null);
186 	}
187 
188 	private boolean has(AnyObjectId objectId, Set<AlternateHandle.Id> skips)
189 			throws IOException {
190 		if (unpackedObjects.contains(objectId)) {
191 			return true;
192 		}
193 		if (wrapped.hasPackedObject(objectId)) {
194 			return true;
195 		}
196 		skips = skipMe(skips);
197 		for (CachedObjectDirectory alt : myAlternates()) {
198 			if (!skips.contains(alt.getAlternateId())) {
199 				if (alt.has(objectId, skips)) {
200 					return true;
201 				}
202 			}
203 		}
204 		return false;
205 	}
206 
207 	@Override
208 	ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
209 			throws IOException {
210 		return openObject(curs, objectId, null);
211 	}
212 
213 	private ObjectLoader openObject(final WindowCursor curs,
214 			final AnyObjectId objectId, Set<AlternateHandle.Id> skips)
215 			throws IOException {
216 		ObjectLoader ldr = openLooseObject(curs, objectId);
217 		if (ldr != null) {
218 			return ldr;
219 		}
220 		ldr = wrapped.openPackedObject(curs, objectId);
221 		if (ldr != null) {
222 			return ldr;
223 		}
224 		skips = skipMe(skips);
225 		for (CachedObjectDirectory alt : myAlternates()) {
226 			if (!skips.contains(alt.getAlternateId())) {
227 				ldr = alt.openObject(curs, objectId, skips);
228 				if (ldr != null) {
229 					return ldr;
230 				}
231 			}
232 		}
233 		return null;
234 	}
235 
236 	@Override
237 	long getObjectSize(WindowCursor curs, AnyObjectId objectId)
238 			throws IOException {
239 		// Object size is unlikely to be requested from contexts using
240 		// this type. Don't bother trying to accelerate the lookup.
241 		return wrapped.getObjectSize(curs, objectId);
242 	}
243 
244 	@Override
245 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
246 			throws IOException {
247 		if (unpackedObjects.contains(id)) {
248 			ObjectLoader ldr = wrapped.openLooseObject(curs, id);
249 			if (ldr != null)
250 				return ldr;
251 			unpackedObjects = scanLoose();
252 		}
253 		return null;
254 	}
255 
256 	@Override
257 	InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId objectId,
258 			boolean createDuplicate) throws IOException {
259 		InsertLooseObjectResult result = wrapped.insertUnpackedObject(tmp,
260 				objectId, createDuplicate);
261 		switch (result) {
262 		case INSERTED:
263 		case EXISTS_LOOSE:
264 			unpackedObjects.addIfAbsent(new UnpackedObjectId(objectId));
265 			break;
266 
267 		case EXISTS_PACKED:
268 		case FAILURE:
269 			break;
270 		}
271 		return result;
272 	}
273 
274 	@Override
275 	PackFile openPack(File pack) throws IOException {
276 		return wrapped.openPack(pack);
277 	}
278 
279 	@Override
280 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
281 			WindowCursor curs) throws IOException {
282 		wrapped.selectObjectRepresentation(packer, otp, curs);
283 	}
284 
285 	@Override
286 	Collection<PackFile> getPacks() {
287 		return wrapped.getPacks();
288 	}
289 
290 	private static class UnpackedObjectId extends ObjectIdOwnerMap.Entry {
291 		UnpackedObjectId(AnyObjectId id) {
292 			super(id);
293 		}
294 	}
295 
296 	private AlternateHandle.Id getAlternateId() {
297 		return wrapped.getAlternateId();
298 	}
299 }