PageRenderTime 310ms CodeModel.GetById 121ms RepoModel.GetById 1ms app.codeStats 0ms

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

#
Java | 178 lines | 91 code | 14 blank | 73 comment | 13 complexity | 1c6a96f54ac25b51764808a8a93065e4 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  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. */
  20. package common.io;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.OutputStream;
  25. /**
  26. * <p>An "atomic" output stream. It is "atomic" in the sense that the
  27. * target file won't be overwritten until "close()" is called, at which
  28. * point a rename is done. A point of note that for this stream to
  29. * actually be atomic, "rename" needs to be atomic, such as in POSIX
  30. * systems. Windows, for example, doesn't have this property, so this
  31. * stream tries to do the next best thing (delete the original, then
  32. * rename).</p>
  33. *
  34. * <p>This stream works a little bit differently than other streams, in the
  35. * sense that it has a second close method called {@link #rollback()}. This
  36. * method just discards the temporary file used to write the data and leaves
  37. * the target file untouched. The only way to overwrite the target file is
  38. * to call {@link #close()}. If the instance is left for garbage collection,
  39. * <code>rollback()</code> will be called during finalization, instead of
  40. * <code>close().</code></p>
  41. *
  42. * @author Marcelo Vanzin
  43. * @since CC 0.9.4
  44. */
  45. public final class AtomicOutputStream extends OutputStream
  46. {
  47. private File target;
  48. private File temp;
  49. private OutputStream out;
  50. /**
  51. * Creates a new atomic stream.
  52. *
  53. * @see #AtomicOutputStream(File)
  54. */
  55. public AtomicOutputStream(String path)
  56. throws IOException
  57. {
  58. this(new File(path));
  59. }
  60. /**
  61. * Creates a new atomic stream. The user must have permission to write
  62. * to the directory of <code>path</code>, otherwise this will throw
  63. * an IOException.
  64. *
  65. * @param path The path of the target file.
  66. * @throws IOException If the temp file cannot be created.
  67. */
  68. public AtomicOutputStream(File path)
  69. throws IOException
  70. {
  71. if (path.exists() && !path.canWrite()) {
  72. throw new IOException("Can't write to " + path.getAbsolutePath());
  73. }
  74. this.temp = File.createTempFile(path.getName(), ".tmp", path.getParentFile());
  75. this.out = new FileOutputStream(temp);
  76. this.target = path;
  77. }
  78. /**
  79. * Closes the temporary stream and renames the temp file to the target file.
  80. * It is safe to call this method several times, or after a rollback()
  81. * (it won't do anything in those cases).
  82. *
  83. * @throws IOException If there's a problem closing the temp stream, or
  84. * renaming the temp file to the target file.
  85. */
  86. public void close() throws IOException
  87. {
  88. if (out == null) {
  89. return;
  90. }
  91. try {
  92. out.close();
  93. if (!temp.renameTo(target)) {
  94. /*
  95. * Windows doesn't allow renaming to a file that already exists.
  96. * So take a "slow, non-atomic" path in this case. This is
  97. * pretty ugly and absolutely not atomic, but I'm trying to be
  98. * really paranoid.
  99. */
  100. if (target.exists()) {
  101. File backup = File.createTempFile(target.getName(),
  102. ".backup",
  103. target.getParentFile());
  104. backup.delete();
  105. if (!target.renameTo(backup) || !temp.renameTo(target)) {
  106. if (backup.exists() && !target.exists()) {
  107. backup.renameTo(target);
  108. }
  109. throw new IOException("Can't rename temp file to " + target.getName());
  110. }
  111. backup.delete();
  112. } else {
  113. throw new IOException("Can't rename temp file to " + target.getName());
  114. }
  115. }
  116. } finally {
  117. out = null;
  118. temp = null;
  119. target = null;
  120. }
  121. }
  122. public void flush() throws IOException
  123. {
  124. out.flush();
  125. }
  126. public void write(byte[] b) throws IOException
  127. {
  128. out.write(b);
  129. }
  130. public void write(byte[] b, int off, int len) throws IOException
  131. {
  132. out.write(b, off, len);
  133. }
  134. public void write(int b) throws IOException
  135. {
  136. out.write(b);
  137. }
  138. /**
  139. * Removes the temporary file without overwriting the target.
  140. * It is safe to call this method several times, or after a close()
  141. * (it won't do anything in those cases).
  142. */
  143. public void rollback()
  144. {
  145. if (out == null) {
  146. return;
  147. }
  148. try {
  149. out.close();
  150. temp.delete();
  151. } catch (IOException ioe) {
  152. // ignore.
  153. } finally {
  154. out = null;
  155. temp = null;
  156. target = null;
  157. }
  158. }
  159. /** For the forgetful. Remember kids: streams want to be free(d). */
  160. protected void finalize()
  161. {
  162. rollback();
  163. }
  164. }