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.Set;
51  
52  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
53  import org.eclipse.jgit.internal.storage.pack.PackWriter;
54  import org.eclipse.jgit.lib.AbbreviatedObjectId;
55  import org.eclipse.jgit.lib.AnyObjectId;
56  import org.eclipse.jgit.lib.Config;
57  import org.eclipse.jgit.lib.Constants;
58  import org.eclipse.jgit.lib.ObjectDatabase;
59  import org.eclipse.jgit.lib.ObjectId;
60  import org.eclipse.jgit.lib.ObjectIdOwnerMap;
61  import org.eclipse.jgit.lib.ObjectLoader;
62  import org.eclipse.jgit.util.FS;
63  
64  /**
65   * The cached instance of an {@link ObjectDirectory}.
66   * <p>
67   * This class caches the list of loose objects in memory, so the file system is
68   * not queried with stat calls.
69   */
70  class CachedObjectDirectory extends FileObjectDatabase {
71  	/**
72  	 * The set that contains unpacked objects identifiers, it is created when
73  	 * the cached instance is created.
74  	 */
75  	private ObjectIdOwnerMap<UnpackedObjectId> unpackedObjects;
76  
77  	private final ObjectDirectory wrapped;
78  
79  	private CachedObjectDirectory[] alts;
80  
81  	/**
82  	 * The constructor
83  	 *
84  	 * @param wrapped
85  	 *            the wrapped database
86  	 */
87  	CachedObjectDirectory(ObjectDirectory wrapped) {
88  		this.wrapped = wrapped;
89  		this.unpackedObjects = scanLoose();
90  	}
91  
92  	private ObjectIdOwnerMap<UnpackedObjectId> scanLoose() {
93  		ObjectIdOwnerMap<UnpackedObjectId> m = new ObjectIdOwnerMap<UnpackedObjectId>();
94  		File objects = wrapped.getDirectory();
95  		String[] fanout = objects.list();
96  		if (fanout == null)
97  			return m;
98  		for (String d : fanout) {
99  			if (d.length() != 2)
100 				continue;
101 			String[] entries = new File(objects, d).list();
102 			if (entries == null)
103 				continue;
104 			for (String e : entries) {
105 				if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
106 					continue;
107 				try {
108 					ObjectId id = ObjectId.fromString(d + e);
109 					m.add(new UnpackedObjectId(id));
110 				} catch (IllegalArgumentException notAnObject) {
111 					// ignoring the file that does not represent loose object
112 				}
113 			}
114 		}
115 		return m;
116 	}
117 
118 	@Override
119 	public void close() {
120 		// Don't close anything.
121 	}
122 
123 	@Override
124 	public ObjectDatabase newCachedDatabase() {
125 		return this;
126 	}
127 
128 	@Override
129 	File getDirectory() {
130 		return wrapped.getDirectory();
131 	}
132 
133 	@Override
134 	File fileFor(AnyObjectId id) {
135 		return wrapped.fileFor(id);
136 	}
137 
138 	@Override
139 	Config getConfig() {
140 		return wrapped.getConfig();
141 	}
142 
143 	@Override
144 	FS getFS() {
145 		return wrapped.getFS();
146 	}
147 
148 	@Override
149 	Set<ObjectId> getShallowCommits() throws IOException {
150 		return wrapped.getShallowCommits();
151 	}
152 
153 	private CachedObjectDirectory[] myAlternates() {
154 		if (alts == null) {
155 			ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates();
156 			alts = new CachedObjectDirectory[src.length];
157 			for (int i = 0; i < alts.length; i++)
158 				alts[i] = src[i].db.newCachedFileObjectDatabase();
159 		}
160 		return alts;
161 	}
162 
163 	@Override
164 	void resolve(Set<ObjectId> matches, AbbreviatedObjectId id)
165 			throws IOException {
166 		// In theory we could accelerate the loose object scan using our
167 		// unpackedObjects map, but its not worth the huge code complexity.
168 		// Scanning a single loose directory is fast enough, and this is
169 		// unlikely to be called anyway.
170 		//
171 		wrapped.resolve(matches, id);
172 	}
173 
174 	@Override
175 	public boolean has(final AnyObjectId objectId) throws IOException {
176 		if (unpackedObjects.contains(objectId))
177 			return true;
178 		if (wrapped.hasPackedObject(objectId))
179 			return true;
180 		for (CachedObjectDirectory alt : myAlternates()) {
181 			if (alt.has(objectId))
182 				return true;
183 		}
184 		return false;
185 	}
186 
187 	@Override
188 	ObjectLoader openObject(final WindowCursor curs,
189 			final AnyObjectId objectId) throws IOException {
190 		ObjectLoader ldr = openLooseObject(curs, objectId);
191 		if (ldr != null)
192 			return ldr;
193 		ldr = wrapped.openPackedObject(curs, objectId);
194 		if (ldr != null)
195 			return ldr;
196 		for (CachedObjectDirectory alt : myAlternates()) {
197 			ldr = alt.openObject(curs, objectId);
198 			if (ldr != null)
199 				return ldr;
200 		}
201 		return null;
202 	}
203 
204 	@Override
205 	long getObjectSize(WindowCursor curs, AnyObjectId objectId)
206 			throws IOException {
207 		// Object size is unlikely to be requested from contexts using
208 		// this type. Don't bother trying to accelerate the lookup.
209 		return wrapped.getObjectSize(curs, objectId);
210 	}
211 
212 	@Override
213 	ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id)
214 			throws IOException {
215 		if (unpackedObjects.contains(id)) {
216 			ObjectLoader ldr = wrapped.openLooseObject(curs, id);
217 			if (ldr != null)
218 				return ldr;
219 			unpackedObjects = scanLoose();
220 		}
221 		return null;
222 	}
223 
224 	@Override
225 	InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId objectId,
226 			boolean createDuplicate) throws IOException {
227 		InsertLooseObjectResult result = wrapped.insertUnpackedObject(tmp,
228 				objectId, createDuplicate);
229 		switch (result) {
230 		case INSERTED:
231 		case EXISTS_LOOSE:
232 			unpackedObjects.addIfAbsent(new UnpackedObjectId(objectId));
233 			break;
234 
235 		case EXISTS_PACKED:
236 		case FAILURE:
237 			break;
238 		}
239 		return result;
240 	}
241 
242 	@Override
243 	PackFile openPack(File pack) throws IOException {
244 		return wrapped.openPack(pack);
245 	}
246 
247 	@Override
248 	void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
249 			WindowCursor curs) throws IOException {
250 		wrapped.selectObjectRepresentation(packer, otp, curs);
251 	}
252 
253 	@Override
254 	Collection<PackFile> getPacks() {
255 		return wrapped.getPacks();
256 	}
257 
258 	private static class UnpackedObjectId extends ObjectIdOwnerMap.Entry {
259 		UnpackedObjectId(AnyObjectId id) {
260 			super(id);
261 		}
262 	}
263 }