View Javadoc
1   /*
2    * Copyright (C) 2008, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
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.dircache;
46  
47  import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
48  import static org.eclipse.jgit.util.Paths.compareSameName;
49  
50  import java.io.IOException;
51  
52  import org.eclipse.jgit.errors.DirCacheNameConflictException;
53  
54  /**
55   * Generic update/editing support for {@link DirCache}.
56   * <p>
57   * The different update strategies extend this class to provide their own unique
58   * services to applications.
59   */
60  abstract class BaseDirCacheEditor {
61  	/** The cache instance this editor updates during {@link #finish()}. */
62  	protected DirCache cache;
63  
64  	/**
65  	 * Entry table this builder will eventually replace into {@link #cache}.
66  	 * <p>
67  	 * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
68  	 * make additions to this table. The table is automatically expanded if it
69  	 * is too small for a new addition.
70  	 * <p>
71  	 * Typically the entries in here are sorted by their path names, just like
72  	 * they are in the DirCache instance.
73  	 */
74  	protected DirCacheEntry[] entries;
75  
76  	/** Total number of valid entries in {@link #entries}. */
77  	protected int entryCnt;
78  
79  	/**
80  	 * Construct a new editor.
81  	 *
82  	 * @param dc
83  	 *            the cache this editor will eventually update.
84  	 * @param ecnt
85  	 *            estimated number of entries the editor will have upon
86  	 *            completion. This sizes the initial entry table.
87  	 */
88  	protected BaseDirCacheEditor(DirCache dc, int ecnt) {
89  		cache = dc;
90  		entries = new DirCacheEntry[ecnt];
91  	}
92  
93  	/**
94  	 * Get the {@code DirCache}
95  	 *
96  	 * @return the cache we will update on {@link #finish()}.
97  	 */
98  	public DirCache getDirCache() {
99  		return cache;
100 	}
101 
102 	/**
103 	 * Append one entry into the resulting entry list.
104 	 * <p>
105 	 * The entry is placed at the end of the entry list. The caller is
106 	 * responsible for making sure the final table is correctly sorted.
107 	 * <p>
108 	 * The {@link #entries} table is automatically expanded if there is
109 	 * insufficient space for the new addition.
110 	 *
111 	 * @param newEntry
112 	 *            the new entry to add.
113 	 */
114 	protected void fastAdd(DirCacheEntry newEntry) {
115 		if (entries.length == entryCnt) {
116 			final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
117 			System.arraycopy(entries, 0, n, 0, entryCnt);
118 			entries = n;
119 		}
120 		entries[entryCnt++] = newEntry;
121 	}
122 
123 	/**
124 	 * Add a range of existing entries from the destination cache.
125 	 * <p>
126 	 * The entries are placed at the end of the entry list, preserving their
127 	 * current order. The caller is responsible for making sure the final table
128 	 * is correctly sorted.
129 	 * <p>
130 	 * This method copies from the destination cache, which has not yet been
131 	 * updated with this editor's new table. So all offsets into the destination
132 	 * cache are not affected by any updates that may be currently taking place
133 	 * in this editor.
134 	 * <p>
135 	 * The {@link #entries} table is automatically expanded if there is
136 	 * insufficient space for the new additions.
137 	 *
138 	 * @param pos
139 	 *            first entry to copy from the destination cache.
140 	 * @param cnt
141 	 *            number of entries to copy.
142 	 */
143 	protected void fastKeep(int pos, int cnt) {
144 		if (entryCnt + cnt > entries.length) {
145 			final int m1 = (entryCnt + 16) * 3 / 2;
146 			final int m2 = entryCnt + cnt;
147 			final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
148 			System.arraycopy(entries, 0, n, 0, entryCnt);
149 			entries = n;
150 		}
151 
152 		cache.toArray(pos, entries, entryCnt, cnt);
153 		entryCnt += cnt;
154 	}
155 
156 	/**
157 	 * Finish this builder and update the destination
158 	 * {@link org.eclipse.jgit.dircache.DirCache}.
159 	 * <p>
160 	 * When this method completes this builder instance is no longer usable by
161 	 * the calling application. A new builder must be created to make additional
162 	 * changes to the index entries.
163 	 * <p>
164 	 * After completion the DirCache returned by {@link #getDirCache()} will
165 	 * contain all modifications.
166 	 * <p>
167 	 * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
168 	 * then invoke {@link #replace()} to update the DirCache with the new table.
169 	 */
170 	public abstract void finish();
171 
172 	/**
173 	 * Update the DirCache with the contents of {@link #entries}.
174 	 * <p>
175 	 * This method should be invoked only during an implementation of
176 	 * {@link #finish()}, and only after {@link #entries} is sorted.
177 	 */
178 	protected void replace() {
179 		checkNameConflicts();
180 		if (entryCnt < entries.length / 2) {
181 			final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
182 			System.arraycopy(entries, 0, n, 0, entryCnt);
183 			entries = n;
184 		}
185 		cache.replace(entries, entryCnt);
186 	}
187 
188 	private void checkNameConflicts() {
189 		int end = entryCnt - 1;
190 		for (int eIdx = 0; eIdx < end; eIdx++) {
191 			DirCacheEntry e = entries[eIdx];
192 			if (e.getStage() != 0) {
193 				continue;
194 			}
195 
196 			byte[] ePath = e.path;
197 			int prefixLen = lastSlash(ePath) + 1;
198 
199 			for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
200 				DirCacheEntry n = entries[nIdx];
201 				if (n.getStage() != 0) {
202 					continue;
203 				}
204 
205 				byte[] nPath = n.path;
206 				if (!startsWith(ePath, nPath, prefixLen)) {
207 					// Different prefix; this entry is in another directory.
208 					break;
209 				}
210 
211 				int s = nextSlash(nPath, prefixLen);
212 				int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
213 				int cmp = compareSameName(
214 						ePath, prefixLen, ePath.length,
215 						nPath, prefixLen, s, m);
216 				if (cmp < 0) {
217 					break;
218 				} else if (cmp == 0) {
219 					throw new DirCacheNameConflictException(
220 							e.getPathString(),
221 							n.getPathString());
222 				}
223 			}
224 		}
225 	}
226 
227 	private static int lastSlash(byte[] path) {
228 		for (int i = path.length - 1; i >= 0; i--) {
229 			if (path[i] == '/') {
230 				return i;
231 			}
232 		}
233 		return -1;
234 	}
235 
236 	private static int nextSlash(byte[] b, int p) {
237 		final int n = b.length;
238 		for (; p < n; p++) {
239 			if (b[p] == '/') {
240 				return p;
241 			}
242 		}
243 		return n;
244 	}
245 
246 	private static boolean startsWith(byte[] a, byte[] b, int n) {
247 		if (b.length < n) {
248 			return false;
249 		}
250 		for (n--; n >= 0; n--) {
251 			if (a[n] != b[n]) {
252 				return false;
253 			}
254 		}
255 		return true;
256 	}
257 
258 	/**
259 	 * Finish, write, commit this change, and release the index lock.
260 	 * <p>
261 	 * If this method fails (returns false) the lock is still released.
262 	 * <p>
263 	 * This is a utility method for applications as the finish-write-commit
264 	 * pattern is very common after using a builder to update entries.
265 	 *
266 	 * @return true if the commit was successful and the file contains the new
267 	 *         data; false if the commit failed and the file remains with the
268 	 *         old data.
269 	 * @throws java.lang.IllegalStateException
270 	 *             the lock is not held.
271 	 * @throws java.io.IOException
272 	 *             the output file could not be created. The caller no longer
273 	 *             holds the lock.
274 	 */
275 	public boolean commit() throws IOException {
276 		finish();
277 		cache.write();
278 		return cache.commit();
279 	}
280 }