1package org.jf.dexlib2.writer.io;
2
3import com.google.common.io.ByteStreams;
4
5import javax.annotation.Nonnull;
6import javax.annotation.Nullable;
7import java.io.*;
8
9/**
10 * A deferred output stream that uses a file as its backing store, with a in-memory intermediate buffer.
11 */
12public class FileDeferredOutputStream extends DeferredOutputStream {
13    private static final int DEFAULT_BUFFER_SIZE = 4 * 1024;
14
15    @Nonnull private final File backingFile;
16    @Nonnull private final NakedBufferedOutputStream output;
17    private int writtenBytes;
18
19    public FileDeferredOutputStream(@Nonnull File backingFile) throws FileNotFoundException {
20        this(backingFile, DEFAULT_BUFFER_SIZE);
21    }
22
23    public FileDeferredOutputStream(@Nonnull File backingFile, int bufferSize) throws FileNotFoundException {
24        this.backingFile = backingFile;
25        output = new NakedBufferedOutputStream(new FileOutputStream(backingFile), bufferSize);
26    }
27
28    @Override public void writeTo(@Nonnull OutputStream dest) throws IOException {
29        byte[] outBuf = output.getBuffer();
30        int count = output.getCount();
31        output.resetBuffer();
32        output.close();
33
34        // did we actually write something out to disk?
35        if (count != writtenBytes) {
36            InputStream fis = new FileInputStream(backingFile);
37            ByteStreams.copy(fis, dest);
38            backingFile.delete();
39        }
40
41        dest.write(outBuf, 0, count);
42    }
43
44    @Override public void write(int i) throws IOException {
45        output.write(i);
46        writtenBytes++;
47    }
48
49    @Override public void write(byte[] bytes) throws IOException {
50        output.write(bytes);
51        writtenBytes += bytes.length;
52    }
53
54    @Override public void write(byte[] bytes, int off, int len) throws IOException {
55        output.write(bytes, off, len);
56        writtenBytes += len;
57    }
58
59    @Override public void flush() throws IOException {
60        output.flush();
61    }
62
63    @Override public void close() throws IOException {
64        output.close();
65    }
66
67    private static class NakedBufferedOutputStream extends BufferedOutputStream {
68        public NakedBufferedOutputStream(OutputStream outputStream) {
69            super(outputStream);
70        }
71
72        public NakedBufferedOutputStream(OutputStream outputStream, int i) {
73            super(outputStream, i);
74        }
75
76        public int getCount() {
77            return count;
78        }
79
80        public void resetBuffer() {
81            count = 0;
82        }
83
84        public byte[] getBuffer() {
85            return buf;
86        }
87    }
88
89    @Nonnull
90    public static DeferredOutputStreamFactory getFactory(@Nullable File containingDirectory) {
91        return getFactory(containingDirectory, DEFAULT_BUFFER_SIZE);
92    }
93
94    @Nonnull
95    public static DeferredOutputStreamFactory getFactory(@Nullable final File containingDirectory,
96                                                         final int bufferSize) {
97        return new DeferredOutputStreamFactory() {
98            @Override public DeferredOutputStream makeDeferredOutputStream() throws IOException {
99                File tempFile = File.createTempFile("dexlibtmp", null, containingDirectory);
100                return new FileDeferredOutputStream(tempFile, bufferSize);
101            }
102        };
103    }
104}
105