Always use a single WindowCache for the entire JVM
[egit/imyousuf.git] / org.spearce.jgit / src / org / spearce / jgit / lib / LockFile.java
blob8f262545677b65bd407f49c704b22d96e1b16ac8
1 /*
2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org.spearce.jgit.lib;
19 import java.io.BufferedOutputStream;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.nio.channels.FileLock;
27 import java.nio.channels.OverlappingFileLockException;
29 /**
30 * Git style file locking and replacement.
31 * <p>
32 * To modify a ref file Git tries to use an atomic update approach: we write the
33 * new data into a brand new file, then rename it in place over the old name.
34 * This way we can just delete the temporary file if anything goes wrong, and
35 * nothing has been damaged. To coordinate access from multiple processes at
36 * once Git tries to atomically create the new temporary file under a well-known
37 * name.
39 public class LockFile {
40 private final File ref;
42 private final File lck;
44 private FileLock fLck;
46 private boolean haveLck;
48 private FileOutputStream os;
50 private boolean needStatInformation;
52 private long commitLastModified;
54 /**
55 * Create a new lock for any file.
57 * @param f
58 * the file that will be locked.
60 public LockFile(final File f) {
61 ref = f;
62 lck = new File(ref.getParentFile(), ref.getName() + ".lock");
65 /**
66 * Try to establish the lock.
68 * @return true if the lock is now held by the caller; false if it is held
69 * by someone else.
70 * @throws IOException
71 * the temporary output file could not be created. The caller
72 * does not hold the lock.
74 public boolean lock() throws IOException {
75 lck.getParentFile().mkdirs();
76 if (lck.createNewFile()) {
77 haveLck = true;
78 try {
79 os = new FileOutputStream(lck);
80 try {
81 fLck = os.getChannel().tryLock();
82 if (fLck == null)
83 throw new OverlappingFileLockException();
84 } catch (OverlappingFileLockException ofle) {
85 // We cannot use unlock() here as this file is not
86 // held by us, but we thought we created it. We must
87 // not delete it, as it belongs to some other process.
89 haveLck = false;
90 try {
91 os.close();
92 } catch (IOException ioe) {
93 // Fail by returning haveLck = false.
95 os = null;
97 } catch (IOException ioe) {
98 unlock();
99 throw ioe;
102 return haveLck;
106 * Try to establish the lock for appending.
108 * @return true if the lock is now held by the caller; false if it is held
109 * by someone else.
110 * @throws IOException
111 * the temporary output file could not be created. The caller
112 * does not hold the lock.
114 public boolean lockForAppend() throws IOException {
115 if (!lock())
116 return false;
117 copyCurrentContent();
118 return true;
122 * Copy the current file content into the temporary file.
123 * <p>
124 * This method saves the current file content by inserting it into the
125 * temporary file, so that the caller can safely append rather than replace
126 * the primary file.
127 * <p>
128 * This method does nothing if the current file does not exist, or exists
129 * but is empty.
131 * @throws IOException
132 * the temporary file could not be written, or a read error
133 * occurred while reading from the current file. The lock is
134 * released before throwing the underlying IO exception to the
135 * caller.
136 * @throws RuntimeException
137 * the temporary file could not be written. The lock is released
138 * before throwing the underlying exception to the caller.
140 public void copyCurrentContent() throws IOException {
141 requireLock();
142 try {
143 final FileInputStream fis = new FileInputStream(ref);
144 try {
145 final byte[] buf = new byte[2048];
146 int r;
147 while ((r = fis.read(buf)) >= 0)
148 os.write(buf, 0, r);
149 } finally {
150 fis.close();
152 } catch (FileNotFoundException fnfe) {
153 // Don't worry about a file that doesn't exist yet, it
154 // conceptually has no current content to copy.
156 } catch (IOException ioe) {
157 unlock();
158 throw ioe;
159 } catch (RuntimeException ioe) {
160 unlock();
161 throw ioe;
162 } catch (Error ioe) {
163 unlock();
164 throw ioe;
169 * Write an ObjectId and LF to the temporary file.
171 * @param id
172 * the id to store in the file. The id will be written in hex,
173 * followed by a sole LF.
174 * @throws IOException
175 * the temporary file could not be written. The lock is released
176 * before throwing the underlying IO exception to the caller.
177 * @throws RuntimeException
178 * the temporary file could not be written. The lock is released
179 * before throwing the underlying exception to the caller.
181 public void write(final ObjectId id) throws IOException {
182 requireLock();
183 try {
184 final BufferedOutputStream b;
185 b = new BufferedOutputStream(os, Constants.OBJECT_ID_LENGTH * 2 + 1);
186 id.copyTo(b);
187 b.write('\n');
188 b.flush();
189 fLck.release();
190 b.close();
191 os = null;
192 } catch (IOException ioe) {
193 unlock();
194 throw ioe;
195 } catch (RuntimeException ioe) {
196 unlock();
197 throw ioe;
198 } catch (Error ioe) {
199 unlock();
200 throw ioe;
205 * Write arbitrary data to the temporary file.
207 * @param content
208 * the bytes to store in the temporary file. No additional bytes
209 * are added, so if the file must end with an LF it must appear
210 * at the end of the byte array.
211 * @throws IOException
212 * the temporary file could not be written. The lock is released
213 * before throwing the underlying IO exception to the caller.
214 * @throws RuntimeException
215 * the temporary file could not be written. The lock is released
216 * before throwing the underlying exception to the caller.
218 public void write(final byte[] content) throws IOException {
219 requireLock();
220 try {
221 os.write(content);
222 os.flush();
223 fLck.release();
224 os.close();
225 os = null;
226 } catch (IOException ioe) {
227 unlock();
228 throw ioe;
229 } catch (RuntimeException ioe) {
230 unlock();
231 throw ioe;
232 } catch (Error ioe) {
233 unlock();
234 throw ioe;
239 * Obtain the direct output stream for this lock.
240 * <p>
241 * The stream may only be accessed once, and only after {@link #lock()} has
242 * been successfully invoked and returned true. Callers must close the
243 * stream prior to calling {@link #commit()} to commit the change.
245 * @return a stream to write to the new file. The stream is unbuffered.
247 public OutputStream getOutputStream() {
248 requireLock();
249 return new OutputStream() {
250 @Override
251 public void write(final byte[] b, final int o, final int n)
252 throws IOException {
253 os.write(b, o, n);
256 @Override
257 public void write(final byte[] b) throws IOException {
258 os.write(b);
261 @Override
262 public void write(final int b) throws IOException {
263 os.write(b);
266 @Override
267 public void flush() throws IOException {
268 os.flush();
271 @Override
272 public void close() throws IOException {
273 try {
274 os.flush();
275 fLck.release();
276 os.close();
277 os = null;
278 } catch (IOException ioe) {
279 unlock();
280 throw ioe;
281 } catch (RuntimeException ioe) {
282 unlock();
283 throw ioe;
284 } catch (Error ioe) {
285 unlock();
286 throw ioe;
292 private void requireLock() {
293 if (os == null) {
294 unlock();
295 throw new IllegalStateException("Lock on " + ref + " not held.");
300 * Request that {@link #commit()} remember modification time.
302 * @param on
303 * true if the commit method must remember the modification time.
305 public void setNeedStatInformation(final boolean on) {
306 needStatInformation = on;
310 * Commit this change and release the lock.
311 * <p>
312 * If this method fails (returns false) the lock is still released.
314 * @return true if the commit was successful and the file contains the new
315 * data; false if the commit failed and the file remains with the
316 * old data.
317 * @throws IllegalStateException
318 * the lock is not held.
320 public boolean commit() {
321 if (os != null) {
322 unlock();
323 throw new IllegalStateException("Lock on " + ref + " not closed.");
326 saveStatInformation();
327 if (lck.renameTo(ref))
328 return true;
329 if (!ref.exists() || ref.delete())
330 if (lck.renameTo(ref))
331 return true;
332 unlock();
333 return false;
336 private void saveStatInformation() {
337 if (needStatInformation)
338 commitLastModified = lck.lastModified();
342 * Get the modification time of the output file when it was committed.
344 * @return modification time of the lock file right before we committed it.
346 public long getCommitLastModified() {
347 return commitLastModified;
351 * Unlock this file and abort this change.
352 * <p>
353 * The temporary file (if created) is deleted before returning.
355 public void unlock() {
356 if (os != null) {
357 if (fLck != null) {
358 try {
359 fLck.release();
360 } catch (IOException ioe) {
361 // Huh?
363 fLck = null;
365 try {
366 os.close();
367 } catch (IOException ioe) {
368 // Ignore this
370 os = null;
373 if (haveLck) {
374 haveLck = false;
375 lck.delete();