]> git.donarmstrong.com Git - bamtools.git/blob - BGZF.cpp
Added empty block EOF to BGZF::Close
[bamtools.git] / BGZF.cpp
1 // ***************************************************************************\r
2 // BGZF.cpp (c) 2009 Derek Barnett, Michael Strömberg\r
3 // Marth Lab, Department of Biology, Boston College\r
4 // All rights reserved.\r
5 // ---------------------------------------------------------------------------\r
6 // Last modified: 8 December 2009 (DB)\r
7 // ---------------------------------------------------------------------------\r
8 // BGZF routines were adapted from the bgzf.c code developed at the Broad\r
9 // Institute.\r
10 // ---------------------------------------------------------------------------\r
11 // Provides the basic functionality for reading & writing BGZF files\r
12 // ***************************************************************************\r
13 \r
14 #include <algorithm>\r
15 #include "BGZF.h"\r
16 using namespace BamTools;\r
17 using std::string;\r
18 using std::min;\r
19 \r
20 BgzfData::BgzfData(void)\r
21     : UncompressedBlockSize(DEFAULT_BLOCK_SIZE)\r
22     , CompressedBlockSize(MAX_BLOCK_SIZE)\r
23     , BlockLength(0)\r
24     , BlockOffset(0)\r
25     , BlockAddress(0)\r
26     , IsOpen(false)\r
27     , IsWriteOnly(false)\r
28     , Stream(NULL)\r
29     , UncompressedBlock(NULL)\r
30     , CompressedBlock(NULL)\r
31 {\r
32     try {\r
33         CompressedBlock   = new char[CompressedBlockSize];\r
34         UncompressedBlock = new char[UncompressedBlockSize];\r
35     } catch( std::bad_alloc& ba ) {\r
36         printf("ERROR: Unable to allocate memory for our BGZF object.\n");\r
37         exit(1);\r
38     }\r
39 }\r
40 \r
41 // destructor\r
42 BgzfData::~BgzfData(void) {\r
43     if(CompressedBlock)   delete [] CompressedBlock;\r
44     if(UncompressedBlock) delete [] UncompressedBlock;\r
45 }\r
46 \r
47 // closes BGZF file\r
48 void BgzfData::Close(void) {\r
49 \r
50         // skip if file not open, otherwise set flag\r
51     if (!IsOpen) { return; }\r
52     IsOpen = false;\r
53 \r
54     // flush the current BGZF block\r
55     if ( IsWriteOnly ) { FlushBlock(); }\r
56 \r
57         // write an empty block (as EOF marker)\r
58         int blockLength = DeflateBlock();\r
59         fwrite(CompressedBlock, 1, blockLength, Stream);\r
60         \r
61     // flush and close\r
62     fflush(Stream);\r
63     fclose(Stream);\r
64 }\r
65 \r
66 // compresses the current block\r
67 int BgzfData::DeflateBlock(void) {\r
68 \r
69     // initialize the gzip header\r
70     char* buffer = CompressedBlock;\r
71     unsigned int bufferSize = CompressedBlockSize;\r
72 \r
73     memset(buffer, 0, 18);\r
74     buffer[0]  = GZIP_ID1;\r
75     buffer[1]  = (char)GZIP_ID2;\r
76     buffer[2]  = CM_DEFLATE;\r
77     buffer[3]  = FLG_FEXTRA;\r
78     buffer[9]  = (char)OS_UNKNOWN;\r
79     buffer[10] = BGZF_XLEN;\r
80     buffer[12] = BGZF_ID1;\r
81     buffer[13] = BGZF_ID2;\r
82     buffer[14] = BGZF_LEN;\r
83 \r
84     // loop to retry for blocks that do not compress enough\r
85     int inputLength = BlockOffset;\r
86     int compressedLength = 0;\r
87 \r
88     while(true) {\r
89 \r
90         z_stream zs;\r
91         zs.zalloc    = NULL;\r
92         zs.zfree     = NULL;\r
93         zs.next_in   = (Bytef*)UncompressedBlock;\r
94         zs.avail_in  = inputLength;\r
95         zs.next_out  = (Bytef*)&buffer[BLOCK_HEADER_LENGTH];\r
96         zs.avail_out = bufferSize - BLOCK_HEADER_LENGTH - BLOCK_FOOTER_LENGTH;\r
97 \r
98         // initialize the zlib compression algorithm\r
99         if(deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, GZIP_WINDOW_BITS, Z_DEFAULT_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {\r
100             printf("ERROR: zlib deflate initialization failed.\n");\r
101             exit(1);\r
102         }\r
103 \r
104         // compress the data\r
105         int status = deflate(&zs, Z_FINISH);\r
106         if(status != Z_STREAM_END) {\r
107 \r
108             deflateEnd(&zs);\r
109 \r
110             // reduce the input length and try again\r
111             if(status == Z_OK) {\r
112                 inputLength -= 1024;\r
113                 if(inputLength < 0) {\r
114                     printf("ERROR: input reduction failed.\n");\r
115                     exit(1);\r
116                 }\r
117                 continue;\r
118             }\r
119 \r
120             printf("ERROR: zlib deflate failed.\n");\r
121             exit(1);\r
122         }\r
123 \r
124         // finalize the compression routine\r
125         if(deflateEnd(&zs) != Z_OK) {\r
126             printf("ERROR: deflate end failed.\n");\r
127             exit(1);\r
128         }\r
129 \r
130         compressedLength = zs.total_out;\r
131         compressedLength += BLOCK_HEADER_LENGTH + BLOCK_FOOTER_LENGTH;\r
132 \r
133         if(compressedLength > MAX_BLOCK_SIZE) {\r
134             printf("ERROR: deflate overflow.\n");\r
135             exit(1);\r
136         }\r
137 \r
138         break;\r
139     }\r
140 \r
141     // store the compressed length\r
142     BgzfData::PackUnsignedShort(&buffer[16], (unsigned short)(compressedLength - 1));\r
143 \r
144     // store the CRC32 checksum\r
145     unsigned int crc = crc32(0, NULL, 0);\r
146     crc = crc32(crc, (Bytef*)UncompressedBlock, inputLength);\r
147     BgzfData::PackUnsignedInt(&buffer[compressedLength - 8], crc);\r
148     BgzfData::PackUnsignedInt(&buffer[compressedLength - 4], inputLength);\r
149 \r
150     // ensure that we have less than a block of data left\r
151     int remaining = BlockOffset - inputLength;\r
152     if(remaining > 0) {\r
153         if(remaining > inputLength) {\r
154             printf("ERROR: remainder too large.\n");\r
155             exit(1);\r
156         }\r
157         memcpy(UncompressedBlock, UncompressedBlock + inputLength, remaining);\r
158     }\r
159 \r
160     BlockOffset = remaining;\r
161     return compressedLength;\r
162 }\r
163 \r
164 // flushes the data in the BGZF block\r
165 void BgzfData::FlushBlock(void) {\r
166 \r
167     // flush all of the remaining blocks\r
168     while(BlockOffset > 0) {\r
169 \r
170         // compress the data block\r
171         int blockLength = DeflateBlock();\r
172 \r
173         // flush the data to our output stream\r
174         int numBytesWritten = fwrite(CompressedBlock, 1, blockLength, Stream);\r
175 \r
176         if(numBytesWritten != blockLength) {\r
177             printf("ERROR: Expected to write %u bytes during flushing, but wrote %u bytes.\n", blockLength, numBytesWritten);\r
178             exit(1);\r
179         }\r
180 \r
181         BlockAddress += blockLength;\r
182     }\r
183 }\r
184 \r
185 // de-compresses the current block\r
186 int BgzfData::InflateBlock(const int& blockLength) {\r
187 \r
188     // Inflate the block in m_BGZF.CompressedBlock into m_BGZF.UncompressedBlock\r
189     z_stream zs;\r
190     zs.zalloc    = NULL;\r
191     zs.zfree     = NULL;\r
192     zs.next_in   = (Bytef*)CompressedBlock + 18;\r
193     zs.avail_in  = blockLength - 16;\r
194     zs.next_out  = (Bytef*)UncompressedBlock;\r
195     zs.avail_out = UncompressedBlockSize;\r
196 \r
197     int status = inflateInit2(&zs, GZIP_WINDOW_BITS);\r
198     if (status != Z_OK) {\r
199         printf("inflateInit failed\n");\r
200         exit(1);\r
201     }\r
202 \r
203     status = inflate(&zs, Z_FINISH);\r
204     if (status != Z_STREAM_END) {\r
205         inflateEnd(&zs);\r
206         printf("inflate failed\n");\r
207         exit(1);\r
208     }\r
209 \r
210     status = inflateEnd(&zs);\r
211     if (status != Z_OK) {\r
212         printf("inflateEnd failed\n");\r
213         exit(1);\r
214     }\r
215 \r
216     return zs.total_out;\r
217 }\r
218 \r
219 void BgzfData::Open(const string& filename, const char* mode) {\r
220 \r
221     if ( strcmp(mode, "rb") == 0 ) {\r
222         IsWriteOnly = false;\r
223     } else if ( strcmp(mode, "wb") == 0) {\r
224         IsWriteOnly = true;\r
225     } else {\r
226         printf("ERROR: Unknown file mode: %s\n", mode);\r
227         exit(1);\r
228     }\r
229 \r
230     Stream = fopen(filename.c_str(), mode);\r
231     if(!Stream) {\r
232         printf("ERROR: Unable to open the BAM file %s\n", filename.c_str() );\r
233         exit(1);\r
234     }\r
235     IsOpen = true;\r
236 }\r
237 \r
238 int BgzfData::Read(char* data, const unsigned int dataLength) {\r
239 \r
240    if (dataLength == 0) { return 0; }\r
241 \r
242    char* output = data;\r
243    unsigned int numBytesRead = 0;\r
244    while (numBytesRead < dataLength) {\r
245 \r
246        int bytesAvailable = BlockLength - BlockOffset;\r
247        if (bytesAvailable <= 0) {\r
248            if ( ReadBlock() != 0 ) { return -1; }\r
249            bytesAvailable = BlockLength - BlockOffset;\r
250            if ( bytesAvailable <= 0 ) { break; }\r
251        }\r
252 \r
253        char* buffer   = UncompressedBlock;\r
254        int copyLength = min( (int)(dataLength-numBytesRead), bytesAvailable );\r
255        memcpy(output, buffer + BlockOffset, copyLength);\r
256 \r
257        BlockOffset  += copyLength;\r
258        output       += copyLength;\r
259        numBytesRead += copyLength;\r
260    }\r
261 \r
262    if ( BlockOffset == BlockLength ) {\r
263        BlockAddress = ftell(Stream);\r
264        BlockOffset  = 0;\r
265        BlockLength  = 0;\r
266    }\r
267 \r
268    return numBytesRead;\r
269 }\r
270 \r
271 int BgzfData::ReadBlock(void) {\r
272 \r
273     char    header[BLOCK_HEADER_LENGTH];\r
274     int64_t blockAddress = ftell(Stream);\r
275 \r
276     int count = fread(header, 1, sizeof(header), Stream);\r
277     if (count == 0) {\r
278         BlockLength = 0;\r
279         return 0;\r
280     }\r
281 \r
282     if (count != sizeof(header)) {\r
283         printf("read block failed - count != sizeof(header)\n");\r
284         return -1;\r
285     }\r
286 \r
287     if (!BgzfData::CheckBlockHeader(header)) {\r
288         printf("read block failed - CheckBlockHeader() returned false\n");\r
289         return -1;\r
290     }\r
291 \r
292     int blockLength = BgzfData::UnpackUnsignedShort(&header[16]) + 1;\r
293     char* compressedBlock = CompressedBlock;\r
294     memcpy(compressedBlock, header, BLOCK_HEADER_LENGTH);\r
295     int remaining = blockLength - BLOCK_HEADER_LENGTH;\r
296 \r
297     count = fread(&compressedBlock[BLOCK_HEADER_LENGTH], 1, remaining, Stream);\r
298     if (count != remaining) {\r
299         printf("read block failed - count != remaining\n");\r
300         return -1;\r
301     }\r
302 \r
303     count = InflateBlock(blockLength);\r
304     if (count < 0) { return -1; }\r
305 \r
306     if ( BlockLength != 0 ) {\r
307         BlockOffset = 0;\r
308     }\r
309 \r
310     BlockAddress = blockAddress;\r
311     BlockLength  = count;\r
312     return 0;\r
313 }\r
314 \r
315 bool BgzfData::Seek(int64_t position) {\r
316 \r
317     int     blockOffset  = (position & 0xFFFF);\r
318     int64_t blockAddress = (position >> 16) & 0xFFFFFFFFFFFFLL;\r
319 \r
320     if (fseek(Stream, blockAddress, SEEK_SET) != 0) {\r
321         printf("ERROR: Unable to seek in BAM file\n");\r
322         exit(1);\r
323     }\r
324 \r
325     BlockLength  = 0;\r
326     BlockAddress = blockAddress;\r
327     BlockOffset  = blockOffset;\r
328     return true;\r
329 }\r
330 \r
331 int64_t BgzfData::Tell(void) {\r
332     return ( (BlockAddress << 16) | (BlockOffset & 0xFFFF) );\r
333 }\r
334 \r
335 // writes the supplied data into the BGZF buffer\r
336 unsigned int BgzfData::Write(const char* data, const unsigned int dataLen) {\r
337 \r
338     // initialize\r
339     unsigned int numBytesWritten = 0;\r
340     const char* input = data;\r
341     unsigned int blockLength = UncompressedBlockSize;\r
342 \r
343     // copy the data to the buffer\r
344     while(numBytesWritten < dataLen) {\r
345         unsigned int copyLength = min(blockLength - BlockOffset, dataLen - numBytesWritten);\r
346         char* buffer = UncompressedBlock;\r
347         memcpy(buffer + BlockOffset, input, copyLength);\r
348 \r
349         BlockOffset     += copyLength;\r
350         input           += copyLength;\r
351         numBytesWritten += copyLength;\r
352 \r
353         if(BlockOffset == blockLength) {\r
354             FlushBlock();\r
355         }\r
356     }\r
357 \r
358     return numBytesWritten;\r
359 }\r