Loading core/java/android/util/AtomicFileBufferedOutputStream.java 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedOutputStream; import java.io.IOException; /** * {@link BufferedOutputStream} for {@link AtomicFile}. * Allows user-code to write into file output stream backed by {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileBufferedOutputStream extends BufferedOutputStream implements AutoCloseable { private static final String TAG = "AtomicFileBufferedOutputStream"; private final AtomicFileOutputStream mAtomicFileOutputStream; /** * See {@link AtomicFileOutputStream#AtomicFileOutputStream(AtomicFile)}. */ public AtomicFileBufferedOutputStream(AtomicFile file) throws IOException { this(new AtomicFileOutputStream(file)); } private AtomicFileBufferedOutputStream(AtomicFileOutputStream atomicFileOutputStream) { super(atomicFileOutputStream); mAtomicFileOutputStream = atomicFileOutputStream; } /** * See {@link AtomicFile#startWrite()} with specific buffer size. */ public AtomicFileBufferedOutputStream(AtomicFile file, int bufferSize) throws IOException { this(new AtomicFileOutputStream(file), bufferSize); } private AtomicFileBufferedOutputStream(AtomicFileOutputStream atomicFileOutputStream, int bufferSize) { super(atomicFileOutputStream, bufferSize); mAtomicFileOutputStream = atomicFileOutputStream; } /** * Flushes output stream and marks the writing as finished. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutputStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return TAG + "[" + mAtomicFileOutputStream + "]"; } } core/java/android/util/AtomicFileBufferedPrintWriter.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; /** * Buffered {@link PrintWriter} for {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileBufferedPrintWriter extends PrintWriter { private final AtomicFileOutputStream mAtomicFileOutStream; /** * Construct from {@link AtomicFile} with {@link BufferedWriter} default buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFile atomicFile, Charset charset) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset); } /** * Create from {@link AtomicFileOutputStream} with {@link BufferedWriter} default buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFileOutputStream outStream, Charset charset) { super(new BufferedWriter(new OutputStreamWriter(outStream, charset))); mAtomicFileOutStream = outStream; } /** * Construct from {@link AtomicFile} with the specific buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFile atomicFile, Charset charset, int bufferSize) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset, bufferSize); } /** * Construct from {@link AtomicFileOutputStream} with the specific buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFileOutputStream outStream, Charset charset, int bufferSize) { super(new BufferedWriter(new OutputStreamWriter(outStream, charset), bufferSize)); mAtomicFileOutStream = outStream; } /** * When write is successful this needs to be called to flush the buffer and mark the writing as * successful. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return "AtomicFileBufferedPrintWriter[" + mAtomicFileOutStream + "]"; } } core/java/android/util/AtomicFileOutputStream.java 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.FileOutputStream; import java.io.IOException; /** * {@link FileOutputStream} for {@link AtomicFile}. * Allows user-code to write into file output stream backed by {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileOutputStream extends FileOutputStream implements AutoCloseable { private static final String TAG = "AtomicFileOutputStream"; private final AtomicFile mFile; private final FileOutputStream mOutStream; private boolean mWritingSuccessful; private boolean mClosed; /** * See {@link AtomicFile#startWrite()}. */ public AtomicFileOutputStream(AtomicFile file) throws IOException { this(file, file.startWrite()); } private AtomicFileOutputStream(AtomicFile file, FileOutputStream oStream) throws IOException { super(oStream.getFD()); mFile = file; mOutStream = oStream; } /** * Marks the writing as successful. */ public void markSuccess() { if (mWritingSuccessful) { throw new IllegalStateException(TAG + " success is already marked"); } mWritingSuccessful = true; } /** * Finishes writing to {@link #mFile}, see {@link AtomicFile#finishWrite(FileOutputStream)} * and {@link AtomicFile#failWrite(FileOutputStream)}. Closes {@link #mOutStream} which * is the owner of the file descriptor. */ @Override public void close() throws IOException { super.close(); synchronized (mOutStream) { if (mClosed) { // FileOutputStream#finalize() may call this #close() method. // We don't want to throw exceptions in this case. // CloseGuard#warnIfOpen() also called there, so no need to log warnings in // AtomicFileOutputStream#finalize(). return; } mClosed = true; } if (mWritingSuccessful) { mFile.finishWrite(mOutStream); } else { mFile.failWrite(mOutStream); } } /** * Creates string representation of the object. */ @Override public String toString() { return TAG + "[" + "mFile=" + mFile + ", mWritingSuccessful=" + mWritingSuccessful + ", mClosed=" + mClosed + "]"; } } core/java/android/util/AtomicFilePrintWriter.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; /** * {@link PrintWriter} for {@link AtomicFile} * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFilePrintWriter extends PrintWriter { private final AtomicFileOutputStream mAtomicFileOutStream; /** * Construct from {@link AtomicFile} with {@link BufferedWriter} default buffer size. */ public AtomicFilePrintWriter(AtomicFile atomicFile, Charset charset) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset); } /** * Create from {@link AtomicFileOutputStream}. */ public AtomicFilePrintWriter(AtomicFileOutputStream outStream, Charset charset) { super(new OutputStreamWriter(outStream, charset)); mAtomicFileOutStream = outStream; } /** * When write is successful this needs to be called to flush the buffer and mark the writing as * successful. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return "AtomicFilePrintWriter[" + mAtomicFileOutStream + "]"; } } core/tests/utiltests/src/android/util/AtomicFileBufferedOutputStreamTest.java 0 → 100644 +94 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; @RunWith(AndroidJUnit4.class) public class AtomicFileBufferedOutputStreamTest { private static final String BASE_NAME = "base"; private File mBaseFile; @Before public void setUp() throws Exception { File mDirectory = Files.createTempDirectory("AtomicFile").toFile(); mBaseFile = new File(mDirectory, BASE_NAME); } @After public void deleteFiles() { mBaseFile.delete(); } @Test public void testAtomicFileBufferedWrite() { AtomicFile atomicFile = new AtomicFile(mBaseFile); try (var oStream = new AtomicFileBufferedOutputStream(atomicFile)) { oStream.write(0); oStream.write(new byte[]{1, 2}); oStream.write(new byte[]{1, 2, 3}, /*off=*/ 2, /*len=*/ 1); oStream.markSuccess(); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } try { assertThat(atomicFile.readFully()).isEqualTo(new byte[]{0, 1, 2, 3}); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } } @Test(expected = FileNotFoundException.class) public void testAtomicFileBufferedNotFinishedWriting() throws FileNotFoundException { AtomicFile atomicFile = new AtomicFile(mBaseFile); try (var oStream = new AtomicFileBufferedOutputStream(atomicFile)) { oStream.write(0); oStream.write(new byte[]{1, 2}); oStream.write(new byte[]{1, 2, 3}, /*off=*/ 2, /*len=*/ 1); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } try { // openRead should throw FileNotFoundException. // close should never be called, but added here just in case openRead never throws. atomicFile.openRead().close(); } catch (FileNotFoundException e) { throw e; } catch (IOException e) { // Should never happen throw new RuntimeException(e); } } } Loading
core/java/android/util/AtomicFileBufferedOutputStream.java 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedOutputStream; import java.io.IOException; /** * {@link BufferedOutputStream} for {@link AtomicFile}. * Allows user-code to write into file output stream backed by {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileBufferedOutputStream extends BufferedOutputStream implements AutoCloseable { private static final String TAG = "AtomicFileBufferedOutputStream"; private final AtomicFileOutputStream mAtomicFileOutputStream; /** * See {@link AtomicFileOutputStream#AtomicFileOutputStream(AtomicFile)}. */ public AtomicFileBufferedOutputStream(AtomicFile file) throws IOException { this(new AtomicFileOutputStream(file)); } private AtomicFileBufferedOutputStream(AtomicFileOutputStream atomicFileOutputStream) { super(atomicFileOutputStream); mAtomicFileOutputStream = atomicFileOutputStream; } /** * See {@link AtomicFile#startWrite()} with specific buffer size. */ public AtomicFileBufferedOutputStream(AtomicFile file, int bufferSize) throws IOException { this(new AtomicFileOutputStream(file), bufferSize); } private AtomicFileBufferedOutputStream(AtomicFileOutputStream atomicFileOutputStream, int bufferSize) { super(atomicFileOutputStream, bufferSize); mAtomicFileOutputStream = atomicFileOutputStream; } /** * Flushes output stream and marks the writing as finished. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutputStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return TAG + "[" + mAtomicFileOutputStream + "]"; } }
core/java/android/util/AtomicFileBufferedPrintWriter.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; /** * Buffered {@link PrintWriter} for {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileBufferedPrintWriter extends PrintWriter { private final AtomicFileOutputStream mAtomicFileOutStream; /** * Construct from {@link AtomicFile} with {@link BufferedWriter} default buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFile atomicFile, Charset charset) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset); } /** * Create from {@link AtomicFileOutputStream} with {@link BufferedWriter} default buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFileOutputStream outStream, Charset charset) { super(new BufferedWriter(new OutputStreamWriter(outStream, charset))); mAtomicFileOutStream = outStream; } /** * Construct from {@link AtomicFile} with the specific buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFile atomicFile, Charset charset, int bufferSize) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset, bufferSize); } /** * Construct from {@link AtomicFileOutputStream} with the specific buffer size. */ public AtomicFileBufferedPrintWriter(AtomicFileOutputStream outStream, Charset charset, int bufferSize) { super(new BufferedWriter(new OutputStreamWriter(outStream, charset), bufferSize)); mAtomicFileOutStream = outStream; } /** * When write is successful this needs to be called to flush the buffer and mark the writing as * successful. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return "AtomicFileBufferedPrintWriter[" + mAtomicFileOutStream + "]"; } }
core/java/android/util/AtomicFileOutputStream.java 0 → 100644 +100 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.FileOutputStream; import java.io.IOException; /** * {@link FileOutputStream} for {@link AtomicFile}. * Allows user-code to write into file output stream backed by {@link AtomicFile}. * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFileOutputStream extends FileOutputStream implements AutoCloseable { private static final String TAG = "AtomicFileOutputStream"; private final AtomicFile mFile; private final FileOutputStream mOutStream; private boolean mWritingSuccessful; private boolean mClosed; /** * See {@link AtomicFile#startWrite()}. */ public AtomicFileOutputStream(AtomicFile file) throws IOException { this(file, file.startWrite()); } private AtomicFileOutputStream(AtomicFile file, FileOutputStream oStream) throws IOException { super(oStream.getFD()); mFile = file; mOutStream = oStream; } /** * Marks the writing as successful. */ public void markSuccess() { if (mWritingSuccessful) { throw new IllegalStateException(TAG + " success is already marked"); } mWritingSuccessful = true; } /** * Finishes writing to {@link #mFile}, see {@link AtomicFile#finishWrite(FileOutputStream)} * and {@link AtomicFile#failWrite(FileOutputStream)}. Closes {@link #mOutStream} which * is the owner of the file descriptor. */ @Override public void close() throws IOException { super.close(); synchronized (mOutStream) { if (mClosed) { // FileOutputStream#finalize() may call this #close() method. // We don't want to throw exceptions in this case. // CloseGuard#warnIfOpen() also called there, so no need to log warnings in // AtomicFileOutputStream#finalize(). return; } mClosed = true; } if (mWritingSuccessful) { mFile.finishWrite(mOutStream); } else { mFile.failWrite(mOutStream); } } /** * Creates string representation of the object. */ @Override public String toString() { return TAG + "[" + "mFile=" + mFile + ", mWritingSuccessful=" + mWritingSuccessful + ", mClosed=" + mClosed + "]"; } }
core/java/android/util/AtomicFilePrintWriter.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; /** * {@link PrintWriter} for {@link AtomicFile} * In order to "commit" the new content to the file, call {@link #markSuccess()} then * {@link #close()}. Calling{@link #markSuccess()} alone won't update the file. * This class does not confer any file locking semantics. Do not use this class when the file may be * accessed or modified concurrently by multiple threads or processes. The caller is responsible for * ensuring appropriate mutual exclusion invariants whenever it accesses the file. * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class AtomicFilePrintWriter extends PrintWriter { private final AtomicFileOutputStream mAtomicFileOutStream; /** * Construct from {@link AtomicFile} with {@link BufferedWriter} default buffer size. */ public AtomicFilePrintWriter(AtomicFile atomicFile, Charset charset) throws IOException { this(new AtomicFileOutputStream(atomicFile), charset); } /** * Create from {@link AtomicFileOutputStream}. */ public AtomicFilePrintWriter(AtomicFileOutputStream outStream, Charset charset) { super(new OutputStreamWriter(outStream, charset)); mAtomicFileOutStream = outStream; } /** * When write is successful this needs to be called to flush the buffer and mark the writing as * successful. */ public void markSuccess() throws IOException { flush(); mAtomicFileOutStream.markSuccess(); } /** * Creates string representation of the object. */ @Override public String toString() { return "AtomicFilePrintWriter[" + mAtomicFileOutStream + "]"; } }
core/tests/utiltests/src/android/util/AtomicFileBufferedOutputStreamTest.java 0 → 100644 +94 −0 Original line number Diff line number Diff line /* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import static com.google.common.truth.Truth.assertThat; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; @RunWith(AndroidJUnit4.class) public class AtomicFileBufferedOutputStreamTest { private static final String BASE_NAME = "base"; private File mBaseFile; @Before public void setUp() throws Exception { File mDirectory = Files.createTempDirectory("AtomicFile").toFile(); mBaseFile = new File(mDirectory, BASE_NAME); } @After public void deleteFiles() { mBaseFile.delete(); } @Test public void testAtomicFileBufferedWrite() { AtomicFile atomicFile = new AtomicFile(mBaseFile); try (var oStream = new AtomicFileBufferedOutputStream(atomicFile)) { oStream.write(0); oStream.write(new byte[]{1, 2}); oStream.write(new byte[]{1, 2, 3}, /*off=*/ 2, /*len=*/ 1); oStream.markSuccess(); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } try { assertThat(atomicFile.readFully()).isEqualTo(new byte[]{0, 1, 2, 3}); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } } @Test(expected = FileNotFoundException.class) public void testAtomicFileBufferedNotFinishedWriting() throws FileNotFoundException { AtomicFile atomicFile = new AtomicFile(mBaseFile); try (var oStream = new AtomicFileBufferedOutputStream(atomicFile)) { oStream.write(0); oStream.write(new byte[]{1, 2}); oStream.write(new byte[]{1, 2, 3}, /*off=*/ 2, /*len=*/ 1); } catch (IOException e) { // Should never happen throw new RuntimeException(e); } try { // openRead should throw FileNotFoundException. // close should never be called, but added here just in case openRead never throws. atomicFile.openRead().close(); } catch (FileNotFoundException e) { throw e; } catch (IOException e) { // Should never happen throw new RuntimeException(e); } } }