• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.timezone.location.storage.block.write;
18 
19 import com.android.timezone.location.storage.block.read.BlockData;
20 import com.android.timezone.location.storage.block.read.BlockInfo;
21 import com.android.timezone.location.storage.io.write.TypedOutputStream;
22 
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.nio.ByteBuffer;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /** Writes a block file. */
34 public final class BlockFileWriter implements AutoCloseable {
35 
36     private final char mMagic;
37 
38     private final int mVersion;
39 
40     private final List<BlockInfo> mBlockInfos = new ArrayList<>();
41 
42     private File mOutputFile;
43 
44     private File mTempFile;
45 
46     private TypedOutputStream mTempFileOutputStream;
47 
48     private long mCurrentBlockByteCount = 0;
49 
50     private int mBlockInfoExtraBytesCount;
51 
BlockFileWriter(char magic, int version)52     private BlockFileWriter(char magic, int version) {
53         mMagic = magic;
54         mVersion = version;
55     }
56 
57     /**
58      * Creates a {@link BlockFileWriter} ready for adding block to. The block file is created with
59      * the specified magic and version.
60      */
open(char magic, int version, File file)61     public static BlockFileWriter open(char magic, int version, File file) throws IOException {
62         BlockFileWriter writer = new BlockFileWriter(magic, version);
63         writer.open(file);
64         return writer;
65     }
66 
open(File file)67     private void open(File file) throws IOException {
68         mOutputFile = file;
69         mTempFile = File.createTempFile("block", "temp");
70         mTempFileOutputStream = new TypedOutputStream(new FileOutputStream(mTempFile));
71     }
72 
73     /**
74      * Add the supplied block info / block to the block file. Blocks written in order and assigned
75      * their ID based on their position.
76      */
addBlock(int blockType, byte[] blockInfoExtraBytes, BlockData blockData)77     public void addBlock(int blockType, byte[] blockInfoExtraBytes, BlockData blockData)
78             throws IOException {
79         Objects.requireNonNull(blockInfoExtraBytes);
80         Objects.requireNonNull(blockData);
81 
82         int blockId = mBlockInfos.size();
83         long startOfBlock = mCurrentBlockByteCount;
84 
85         // Don't write anything for empty blocks.
86         if (blockData.getSize() != 0) {
87             // Write the block's header.
88             mTempFileOutputStream.writeInt(blockId);
89             mCurrentBlockByteCount += Integer.BYTES;
90             mTempFileOutputStream.writeInt(blockType);
91             mCurrentBlockByteCount += Integer.BYTES;
92 
93             // Write the block's data.
94             mCurrentBlockByteCount += copyAll(blockData, mTempFileOutputStream);
95         }
96         long endOfBlock = mCurrentBlockByteCount;
97         long blockSizeBytes = endOfBlock - startOfBlock;
98         BlockInfo blockInfo = new BlockInfo(
99                 blockId, blockType, startOfBlock, blockSizeBytes, blockInfoExtraBytes);
100         mBlockInfoExtraBytesCount += blockInfoExtraBytes.length;
101         mBlockInfos.add(blockInfo);
102     }
103 
104     /** Finishes writing the file. Signals no more blocks are to be added. */
105     @Override
close()106     public void close() throws IOException {
107         mTempFileOutputStream.close();
108         writeHeaderAndAppendTempFile();
109     }
110 
writeHeaderAndAppendTempFile()111     private void writeHeaderAndAppendTempFile() throws IOException {
112         try (TypedOutputStream bos = new TypedOutputStream(new FileOutputStream(mOutputFile))) {
113             // File header
114             bos.writeChar(mMagic);
115             bos.writeInt(mVersion);
116             bos.writeBytes(new byte[10]); // Reserved
117             bos.writeInt(mBlockInfos.size());
118 
119             int blockInfoLengthWithoutExtraBytes =
120                     Integer.BYTES /* blockInfoSize */
121                             + Integer.BYTES /* blockType */
122                             + Long.BYTES /* blockOffset */
123                             + Long.BYTES /* blockSizeBytes */
124                             + Byte.BYTES /* extraBytesLength */;
125             int headerBytesAdjustment =
126                     Character.BYTES /* magic */
127                             + Integer.BYTES /* version */
128                             + (10 * Byte.BYTES) /* reserved */
129                             + Integer.BYTES /* blockInfos.size() */
130                             + (mBlockInfos.size() * blockInfoLengthWithoutExtraBytes)
131                             + mBlockInfoExtraBytesCount;
132 
133             // Write block info to the TypedOutputStream.
134             for (BlockInfo blockInfo : mBlockInfos) {
135                 byte[] extraBytes = blockInfo.getExtraBytes();
136                 int blockInfoSize = blockInfoLengthWithoutExtraBytes + extraBytes.length;
137                 bos.writeInt(blockInfoSize);
138                 bos.writeInt(blockInfo.getType());
139                 bos.writeLong(blockInfo.getBlockStartByteOffset() + headerBytesAdjustment);
140                 bos.writeLong(blockInfo.getBlockSizeBytes());
141                 bos.writeTinyByteArray(extraBytes);
142             }
143 
144             // Copy the block data from the temp file.
145             try (FileInputStream fis = new FileInputStream(mTempFile)) {
146                 copyAll(fis, bos);
147             }
148         }
149     }
150 
copyAll(InputStream inputStream, TypedOutputStream outputStream)151     private static int copyAll(InputStream inputStream, TypedOutputStream outputStream)
152             throws IOException {
153         byte[] buffer = new byte[8192];
154         int totalByteCount = 0;
155         int bytesRead;
156         while ((bytesRead = inputStream.read(buffer)) >= 0) {
157             outputStream.writeBytes(buffer, 0, bytesRead);
158             totalByteCount += bytesRead;
159         }
160         inputStream.close();
161         return totalByteCount;
162     }
163 
copyAll(BlockData blockData, TypedOutputStream outputStream)164     private static int copyAll(BlockData blockData, TypedOutputStream outputStream)
165             throws IOException {
166         byte[] buffer = new byte[8192];
167         ByteBuffer byteBuffer = blockData.getByteBuffer();
168         int totalByteCount = 0;
169         while (byteBuffer.remaining() > 0) {
170             int byteCount = Math.min(buffer.length, byteBuffer.remaining());
171             byteBuffer.get(buffer, 0, byteCount);
172             outputStream.writeBytes(buffer, 0, byteCount);
173             totalByteCount += byteCount;
174         }
175         return totalByteCount;
176     }
177 }
178