PageRenderTime 97ms CodeModel.GetById 86ms app.highlight 9ms RepoModel.GetById 0ms app.codeStats 1ms

/bundles/plugins-trunk/CommonControls/common/io/AtomicOutputStream.java

#
Java | 178 lines | 91 code | 14 blank | 73 comment | 13 complexity | 1c6a96f54ac25b51764808a8a93065e4 MD5 | raw file
  1/*
  2 * Copyright (c) 2007 Marcelo Vanzin
  3 *
  4 * :tabSize=4:indentSize=4:noTabs=false:maxLineLen=0:
  5 *
  6 * This program is free software; you can redistribute it and/or
  7 * modify it under the terms of the GNU General Public License
  8 * as published by the Free Software Foundation; either version 2
  9 * of the License, or any later version.
 10 *
 11 * This program is distributed in the hope that it will be useful,
 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14 * GNU General Public License for more details.
 15 *
 16 * You should have received a copy of the GNU General Public License
 17 * along with this program; if not, write to the Free Software
 18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 19 */
 20package common.io;
 21
 22import java.io.File;
 23import java.io.FileOutputStream;
 24import java.io.IOException;
 25import java.io.OutputStream;
 26
 27/**
 28 *	<p>An "atomic" output stream. It is "atomic" in the sense that the
 29 *	target file won't be overwritten until "close()" is called, at which
 30 *	point a rename is done. A point of note that for this stream to
 31 *	actually be atomic, "rename" needs to be atomic, such as in POSIX
 32 *	systems. Windows, for example, doesn't have this property, so this
 33 *	stream tries to do the next best thing (delete the original, then
 34 *	rename).</p>
 35 *
 36 *	<p>This stream works a little bit differently than other streams, in the
 37 *	sense that it has a second close method called {@link #rollback()}. This
 38 *	method just discards the temporary file used to write the data and leaves
 39 *	the target file untouched. The only way to overwrite the target file is
 40 *	to call {@link #close()}. If the instance is left for garbage collection,
 41 *	<code>rollback()</code> will be called during finalization, instead of
 42 *	<code>close().</code></p>
 43 *
 44 *	@author		Marcelo Vanzin
 45 *	@since		CC 0.9.4
 46 */
 47public final class AtomicOutputStream extends OutputStream
 48{
 49
 50	private File			target;
 51	private File			temp;
 52	private OutputStream 	out;
 53
 54	/**
 55	 *	Creates a new atomic stream.
 56	 *
 57	 *	@see	#AtomicOutputStream(File)
 58	 */
 59	public AtomicOutputStream(String path)
 60		throws IOException
 61	{
 62		this(new File(path));
 63	}
 64
 65	/**
 66	 *	Creates a new atomic stream. The user must have permission to write
 67	 *	to the directory of <code>path</code>, otherwise this will throw
 68	 *	an IOException.
 69	 *
 70	 *	@param	path			The path of the target file.
 71	 *	@throws	IOException		If the temp file cannot be created.
 72	 */
 73	public AtomicOutputStream(File path)
 74		throws IOException
 75	{
 76		if (path.exists() && !path.canWrite()) {
 77			throw new IOException("Can't write to " + path.getAbsolutePath());
 78		}
 79		this.temp	= File.createTempFile(path.getName(), ".tmp", path.getParentFile());
 80		this.out 	= new FileOutputStream(temp);
 81		this.target	= path;
 82	}
 83
 84	/**
 85	 *	Closes the temporary stream and renames the temp file to the target file.
 86	 *	It is safe to call this method several times, or after a rollback()
 87	 *	(it won't do anything in those cases).
 88	 *
 89	 *	@throws	IOException	If there's a problem closing the temp stream, or
 90	 *						renaming the temp file to the target file.
 91	 */
 92	public void close() throws IOException
 93	{
 94		if (out == null) {
 95			return;
 96		}
 97		try {
 98			out.close();
 99			if (!temp.renameTo(target)) {
100				/*
101				 * Windows doesn't allow renaming to a file that already exists.
102				 * So take a "slow, non-atomic" path in this case. This is
103				 * pretty ugly and absolutely not atomic, but I'm trying to be
104				 * really paranoid.
105				 */
106				if (target.exists()) {
107					File backup = File.createTempFile(target.getName(),
108													  ".backup",
109													  target.getParentFile());
110					backup.delete();
111					if (!target.renameTo(backup) || !temp.renameTo(target)) {
112						if (backup.exists() && !target.exists()) {
113							backup.renameTo(target);
114						}
115						throw new IOException("Can't rename temp file to " + target.getName());
116					}
117					backup.delete();
118				} else {
119					throw new IOException("Can't rename temp file to " + target.getName());
120				}
121			}
122		} finally {
123			out = null;
124			temp = null;
125			target = null;
126		}
127	}
128
129	public void flush() throws IOException
130	{
131		out.flush();
132	}
133
134	public void write(byte[] b) throws IOException
135	{
136		out.write(b);
137	}
138
139	public void write(byte[] b, int off, int len) throws IOException
140	{
141		out.write(b, off, len);
142	}
143
144	public void write(int b) throws IOException
145	{
146		out.write(b);
147	}
148
149	/**
150	 *	Removes the temporary file without overwriting the target.
151	 *	It is safe to call this method several times, or after a close()
152	 *	(it won't do anything in those cases).
153	 */
154	public void rollback()
155	{
156		if (out == null) {
157			return;
158		}
159		try {
160			out.close();
161			temp.delete();
162		} catch (IOException ioe) {
163			// ignore.
164		} finally {
165			out = null;
166			temp = null;
167			target = null;
168		}
169	}
170
171	/** For the forgetful. Remember kids: streams want to be free(d). */
172	protected void finalize()
173	{
174		rollback();
175	}
176
177}
178