1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
2 // Licensed under the MIT License:
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 
22 module capnproto.PackedOutputStream;
23 
24 import java.io.IOException;
25 import java.nio.channels.WritableByteChannel;
26 import java.nio.ByteBuffer;
27 
28 import capnproto.BufferedOutputStream;
29 
30 final class PackedOutputStream : WritableByteChannel
31 {
32 public: //Methods.
33 	this(BufferedOutputStream output)
34 	{
35 		this.inner = output;
36 	}
37 	
38 	size_t write(ref ByteBuffer inBuf)
39 	{
40 		auto length = inBuf.remaining();
41 		auto out_ = this.inner.getWriteBuffer();
42 		
43 		auto slowBuffer = ByteBuffer(new ubyte[](20));
44 		
45 		auto inPtr = inBuf.position;
46 		auto inEnd = inPtr + length;
47 		while(inPtr < inEnd)
48 		{
49 			if(out_.remaining() < 10)
50 			{
51 				//# Oops, we're out of space. We need at least 10
52 				//# bytes for the fast path, since we don't
53 				//# bounds-check on every byte.
54 				
55 				if(out_ is &slowBuffer)
56 				{
57 					auto oldLimit = out_.limit;
58 					out_.limit = out_.position;
59 					out_.rewind();
60 					this.inner.write(*out_);
61 					out_.limit = oldLimit;
62 				}
63 				out_ = &slowBuffer;
64 				out_.rewind();
65 			}
66 			
67 			auto tagPos = out_.position;
68 			out_.position = tagPos + 1;
69 			
70 			import std.meta;
71 			ubyte bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7;
72 			foreach(ref b; AliasSeq!(bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7))
73 			{
74 				ubyte curByte = inBuf.get!ubyte(inPtr);
75 				b = (curByte != 0)? cast(ubyte)1 : cast(ubyte)0;
76 				out_.put!ubyte(curByte);
77 				out_.position += b - 1;
78 				inPtr += 1;
79 			}
80 			
81 			ubyte tag = cast(ubyte)((bit0 << 0) | (bit1 << 1) | (bit2 << 2) | (bit3 << 3) |
82 			                        (bit4 << 4) | (bit5 << 5) | (bit6 << 6) | (bit7 << 7));
83 			
84 			out_.put!ubyte(tagPos, tag);
85 			
86 			if(tag == 0)
87 			{
88 				//# An all-zero word is followed by a count of
89 				//# consecutive zero words (not including the first
90 				//# one).
91 				auto runStart = inPtr;
92 				auto limit = inEnd;
93 				if(limit - inPtr > 255 * 8)
94 					limit = inPtr + 255 * 8;
95 				while(inPtr < limit && inBuf.get!long(inPtr) == 0)
96 					inPtr += 8;
97 				out_.put!ubyte(cast(byte)((inPtr - runStart)/8));
98 			}
99 			else if(tag == 0xff)
100 			{
101 				//# An all-nonzero word is followed by a count of
102 				//# consecutive uncompressed words, followed by the
103 				//# uncompressed words themselves.
104 				
105 				//# Count the number of consecutive words in the input
106 				//# which have no more than a single zero-byte. We look
107 				//# for at least two zeros because that's the point
108 				//# where our compression scheme becomes a net win.
109 				
110 				auto runStart = inPtr;
111 				auto limit = inEnd;
112 				if(limit - inPtr > 255 * 8)
113 					limit = inPtr + 255 * 8;
114 				
115 				while(inPtr < limit)
116 				{
117 					byte c = 0;
118 					foreach(ii; 0..8)
119 					{
120 						c += (inBuf.get!byte(inPtr) == 0? 1 : 0);
121 						inPtr += 1;
122 					}
123 					if(c >= 2)
124 					{
125 						//# Un-read the word with multiple zeros, since
126 						//# we'll want to compress that one.
127 						inPtr -= 8;
128 						break;
129 					}
130 				}
131 				
132 				auto count = inPtr - runStart;
133 				out_.put!ubyte(cast(byte)(count / 8));
134 				
135 				if(count <= out_.remaining())
136 				{
137 					//# There's enough space to memcpy.
138 					inBuf.position = runStart;
139 					ByteBuffer slice = inBuf.slice();
140 					slice.limit = count;
141 					out_.put!ByteBuffer(slice);
142 				}
143 				else
144 				{
145 					//# Input overruns the output buffer. We'll give it
146 					//# to the output stream in one chunk and let it
147 					//# decide what to do.
148 					
149 					if(out_ is &slowBuffer)
150 					{
151 						auto oldLimit = out_.limit;
152 						out_.limit = out_.position;
153 						out_.rewind();
154 						this.inner.write(*out_);
155 						out_.limit = oldLimit;
156 					}
157 					
158 					inBuf.position = runStart;
159 					ByteBuffer slice = inBuf.slice();
160 					slice.limit = count;
161 					while(!slice.empty())
162 						this.inner.write(slice);
163 					out_ = this.inner.getWriteBuffer();
164 				}
165 			}
166 		}
167 		
168 		if(out_ is &slowBuffer)
169 		{
170 			out_.limit = out_.position;
171 			out_.rewind();
172 			this.inner.write(*out_);
173 		}
174 		
175 		inBuf.position = inPtr;
176 		return length;
177 	}
178 	
179 	void close()
180 	{
181 		this.inner.close();
182 	}
183 	
184 	bool isOpen()
185 	{
186 		return this.inner.isOpen();
187 	}
188 
189 package: //Variables.
190 	BufferedOutputStream inner;
191 }