001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.internal.io;
017
018import java.io.DataOutput;
019import java.io.IOException;
020import java.io.OutputStream;
021import java.nio.ByteBuffer;
022import java.nio.ByteOrder;
023import java.nio.CharBuffer;
024import java.nio.channels.Channels;
025import java.nio.channels.WritableByteChannel;
026import java.nio.charset.CharsetEncoder;
027import java.nio.charset.CoderResult;
028import java.nio.charset.StandardCharsets;
029import java.text.CharacterIterator;
030import java.text.StringCharacterIterator;
031import java.util.ArrayList;
032import java.util.List;
033
034/**
035 * An implementation of {@link DataOutput} with additional methods needed for writing BSON formatted content. Specifically, this
036 * class reads little-endian byte order (purposefully in violation of the DataInput interface specification) and provides a way to
037 * read C-style strings (where the length is not known up front but instead contain all characters until the zero-byte
038 * terminator), which are commonly used within the BSON specification.
039 * 
040 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
041 */
042public class BsonDataOutput implements DataOutput {
043
044    private static final int DEFAULT_BUFFER_SIZE = 1048 * 8;
045
046    private final List<ByteBuffer> buffers = new ArrayList<>();
047    private final int bufferSize;
048    private final byte[] bytes = new byte[8];
049    private int position = 0;
050    private int size = 0;
051    private ByteBuffer remainderBuffer;
052
053    public BsonDataOutput() {
054        this.bufferSize = DEFAULT_BUFFER_SIZE;
055    }
056
057    protected ByteBuffer getBufferFor( int position ) {
058        int bufferIndex = position / bufferSize;
059        while (bufferIndex >= buffers.size()) {
060            buffers.add(newByteBuffer(bufferSize));
061        }
062        return buffers.get(bufferIndex);
063    }
064
065    protected ByteBuffer newByteBuffer( int bufferSize ) {
066        return ByteBuffer.allocate(bufferSize).order(ByteOrder.LITTLE_ENDIAN);
067    }
068
069    protected void updateSize( int newSize ) {
070        if (size < newSize) {
071            size = newSize;
072        }
073    }
074
075    public int size() {
076        return size;
077    }
078
079    @Override
080    public void writeByte( int value ) {
081        writeByte(position, value);
082        position += 1;
083    }
084
085    public void writeByte( int position,
086                           int value ) {
087        updateSize(position + 1);
088        ByteBuffer buffer = getBufferFor(position);
089        int index = position % bufferSize;
090        buffer.put(index, (byte)value);
091    }
092
093    @Override
094    public void write( byte[] b ) {
095        write(position, b, 0, b.length);
096        position += b.length;
097    }
098
099    @Override
100    public void write( byte[] b,
101                       int off,
102                       int len ) {
103        write(position, b, off, len);
104        position += len;
105    }
106
107    public void write( int position,
108                       byte[] value,
109                       int offset,
110                       int length ) {
111        updateSize(position + length);
112        ByteBuffer buffer = getBufferFor(position);
113        int index = position % bufferSize;
114        for (int i = offset; i != length; ++i) {
115            byte b = value[i];
116            if (index == bufferSize) {
117                // We have to use the next buffer ...
118                buffer = getBufferFor(position);
119                index = position % bufferSize;
120            }
121            buffer.put(index, b);
122            ++index;
123            ++position;
124        }
125    }
126
127    @Override
128    public void write( int b ) {
129        writeByte((byte) b);
130    }
131
132    @Override
133    public void writeBoolean( boolean value ) {
134        writeBoolean(position, value);
135        position += 1;
136    }
137
138    public void writeBoolean( int position,
139                              boolean value ) {
140        updateSize(position + 1);
141        ByteBuffer buffer = getBufferFor(position);
142        int index = position % bufferSize;
143        buffer.put(index, value ? (byte)1 : (byte)0);
144    }
145
146    @Override
147    public void writeChar( int value ) {
148        writeChar(position, value);
149        position += 2;
150    }
151
152    public void writeChar( int position,
153                           int value ) {
154        updateSize(position + 2);
155        ByteBuffer buffer = getBufferFor(position);
156        int index = position % bufferSize;
157        if (buffer.limit() - index >= 2) {
158            // There's enough room to write on the buffer ...
159            buffer.putChar(index, (char)value);
160        } else {
161            // The value will have to span buffers ...
162            bytes[0] = (byte)value;
163            bytes[1] = (byte)(value >> 8);
164            write(position, bytes, 0, 2);
165        }
166    }
167
168    @Override
169    public void writeInt( int value ) {
170        writeInt(position, value);
171        position += 4;
172    }
173
174    public void writeInt( int position,
175                          int value ) {
176        updateSize(position + 4);
177        ByteBuffer buffer = getBufferFor(position);
178        int index = position % bufferSize;
179        if (buffer.limit() - index >= 4) {
180            // There's enough room to write on the buffer ...
181            buffer.putInt(index, value);
182        } else {
183            // The value will have to span buffers ...
184            bytes[0] = (byte)value;
185            bytes[1] = (byte)(value >> 8);
186            bytes[2] = (byte)(value >> 16);
187            bytes[3] = (byte)(value >> 24);
188            write(position, bytes, 0, 4);
189        }
190    }
191
192    @Override
193    public void writeShort( int value ) {
194        writeShort(position, value);
195        position += 2;
196    }
197
198    public void writeShort( int position,
199                            int value ) {
200        updateSize(position + 2);
201        ByteBuffer buffer = getBufferFor(position);
202        int index = position % bufferSize;
203        if (buffer.limit() - index >= 2) {
204            // There's enough room to write on the buffer ...
205            buffer.putShort(index, (short)value);
206        } else {
207            // The value will have to span buffers ...
208            bytes[0] = (byte)value;
209            bytes[1] = (byte)(value >> 8);
210            write(position, bytes, 0, 2);
211        }
212    }
213
214    @Override
215    public void writeLong( long value ) {
216        writeLong(position, value);
217        position += 8;
218    }
219
220    public void writeLong( int position,
221                           long value ) {
222        updateSize(position + 8);
223        ByteBuffer buffer = getBufferFor(position);
224        int index = position % bufferSize;
225        if (buffer.limit() - index >= 8) {
226            // There's enough room to write on the buffer ...
227            buffer.putLong(index, value);
228        } else {
229            // The value will have to span buffers ...
230            bytes[0] = (byte)value;
231            bytes[1] = (byte)(value >> 8);
232            bytes[2] = (byte)(value >> 16);
233            bytes[3] = (byte)(value >> 24);
234            bytes[4] = (byte)(value >> 32);
235            bytes[5] = (byte)(value >> 40);
236            bytes[6] = (byte)(value >> 48);
237            bytes[7] = (byte)(value >> 56);
238            write(position, bytes, 0, 8);
239        }
240    }
241
242    @Override
243    public void writeFloat( float value ) {
244        writeFloat(position, value);
245        position += 4;
246    }
247
248    public void writeFloat( int position,
249                            float value ) {
250        writeInt(position, Float.floatToRawIntBits(value));
251    }
252
253    @Override
254    public void writeDouble( double value ) {
255        writeDouble(position, value);
256        position += 8;
257    }
258
259    public void writeDouble( int position,
260                             double value ) {
261        writeLong(position, Double.doubleToRawLongBits(value));
262    }
263
264    /**
265     * Writes a string to the output stream. For every character in the string <code>s</code>, taken in order, one byte is written
266     * to the output stream. If <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown.
267     * <p>
268     * If <code>s.length</code> is zero, then no bytes are written. Otherwise, the character <code>s[0]</code> is written first,
269     * then <code>s[1]</code>, and so on; the last character written is <code>s[s.length-1]</code>. For each character, one byte
270     * is written, the low-order byte, in exactly the manner of the <code>writeByte</code> method . The high-order eight bits of
271     * each character in the string are ignored.
272     * 
273     * @param str the string value to be written.
274     * @deprecated The semantics of {@code writeBytes(String s)} are considered dangerous. Please use {@link #writeUTF(String s)},
275     *             {@link #writeChars(String s)} or another write method instead.
276     */
277    @Deprecated
278    @Override
279    public void writeBytes( String str ) {
280        CharacterIterator iter = new StringCharacterIterator(str);
281        for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
282            writeByte(c);
283        }
284    }
285
286    /**
287     * Writes every character in the string <code>s</code>, to the output stream, in order, two bytes per character. If
288     * <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown. If <code>s.length</code> is zero, then
289     * no characters are written. Otherwise, the character <code>s[0]</code> is written first, then <code>s[1]</code>, and so on;
290     * the last character written is <code>s[s.length-1]</code>. For each character, two bytes are actually written, low-order
291     * byte first, in exactly the manner of the <code>writeChar</code> method.
292     * 
293     * @param str the string value to be written.
294     */
295    @Override
296    public void writeChars( String str ) {
297        CharacterIterator iter = new StringCharacterIterator(str);
298        for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
299            writeChar(c);
300        }
301    }
302
303    @Override
304    public void writeUTF( String str ) {
305        int numBytesWritten = 0;
306        int numBytesPosition = this.position;
307        // Write a placeholder for the length ...
308        writeShort(numBytesPosition, numBytesWritten);
309        numBytesWritten = writeUTF(position, str);
310        position += numBytesWritten;
311        // Now write the real number of bytes written ...
312        writeShort(numBytesPosition, numBytesWritten);
313    }
314
315    /**
316     * Writes the <a href="DataInput.html#modified-utf-8">modified UTF-8</a> representation of every character in the string
317     * <code>s</code>. <i>This is similar to the standard {@link #writeUTF(String)} but without the leading two byte length.</i>
318     * If <code>s</code> is <code>null</code>, a <code>NullPointerException</code> is thrown. Each character in the string
319     * <code>s</code> is converted to a group of one, two, or three bytes, depending on the value of the character.
320     * <p>
321     * If a character <code>c</code> is in the range <code>&#92;u0001</code> through <code>&#92;u007f</code>, it is represented by
322     * one byte:
323     * <p>
324     * 
325     * <pre>
326     * (byte)c
327     * </pre>
328     * <p>
329     * If a character <code>c</code> is <code>&#92;u0000</code> or is in the range <code>&#92;u0080</code> through
330     * <code>&#92;u07ff</code>, then it is represented by two bytes, to be written in the order shown:
331     * <p>
332     * 
333     * <pre>
334     * <code>
335     * (byte)(0xc0 | (0x1f &amp; (c &gt;&gt; 6)))
336     * (byte)(0x80 | (0x3f &amp; c))
337     *  </code>
338     * </pre>
339     * <p>
340     * If a character <code>c</code> is in the range <code>&#92;u0800</code> through <code>uffff</code>, then it is represented by
341     * three bytes, to be written in the order shown:
342     * <p>
343     * 
344     * <pre>
345     * <code>
346     * (byte)(0xe0 | (0x0f &amp; (c &gt;&gt; 12)))
347     * (byte)(0x80 | (0x3f &amp; (c &gt;&gt;  6)))
348     * (byte)(0x80 | (0x3f &amp; c))
349     *  </code>
350     * </pre>
351     * <p>
352     * First, the total number of bytes needed to represent all the characters of <code>s</code> is calculated. If this number is
353     * larger than <code>65535</code>, then a <code>UTFDataFormatException</code> is thrown. Otherwise, this length is written to
354     * the output stream in exactly the manner of the <code>writeShort</code> method; after this, the one-, two-, or three-byte
355     * representation of each character in the string <code>s</code> is written.
356     * <p>
357     * The bytes written by this method may be read by the <code>readUTF</code> method of interface <code>DataInput</code> , which
358     * will then return a <code>String</code> equal to <code>s</code>.
359     * 
360     * @param str the string value to be written.
361     * @return the number of bytes written
362     */
363    public int writeUTFString( String str ) {
364        int numBytesWritten = writeUTF(position, str);
365        position += numBytesWritten;
366        return numBytesWritten;
367    }
368
369    public int writeUTF( int position,
370                         String str ) {
371        CharBuffer chars = CharBuffer.wrap(str);
372        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
373        ByteBuffer output = getBufferFor(position);
374        int index = position % bufferSize;
375        int newPosition = position;
376        output.position(index);
377        try {
378            while (chars.hasRemaining()) {
379                CoderResult result = encoder.encode(chars, output, true);
380
381                if (output == remainderBuffer) {
382                    // There is some remainder from the previous iteration ...
383                    output.flip();
384                    while (output.remaining() > 0) {
385                        writeByte(newPosition, output.get());
386                        ++newPosition;
387                    }
388                } else {
389                    // Calculate the new position based upon where we started ...
390                    newPosition += output.position() - index;
391                }
392
393                if (result.isError()) {
394                    // Some problem occurred ...
395                    result.throwException();
396                } else if (result.isOverflow()) {
397                    // Unable to write all of the content to the output buffer, so there's still some characters left ...
398                    if (output.remaining() > 0) {
399
400                        // We ran out of space in 'buffer', so go again with a small 'remainder' buffer ...
401                        if (remainderBuffer != null) {
402                            // There is one, so clear it out and prepare it for use ...
403                            remainderBuffer.clear();
404                        } else {
405                            // There is none, so allocate one for this object ...
406                            remainderBuffer = ByteBuffer.allocate(4);
407                        }
408                        output = remainderBuffer;
409                        index = 0;
410                    } else {
411                        // We ran out of space by fully utilizing this buffer, so just go to the next buffer ...
412                        output = getBufferFor(newPosition);
413                        index = newPosition % bufferSize;
414                        output.position(index);
415                    }
416                }
417            }
418        } catch (Exception e) {
419            throw new RuntimeException("Unable to encode string", e);
420        }
421        updateSize(newPosition);
422        return newPosition - position;
423    }
424
425    /**
426     * Write all content to the supplied stream.
427     * 
428     * @param stream the stream to which the content is to be written.
429     * @throws IOException if there is a problem writing to the supplied stream
430     */
431    public void writeTo( OutputStream stream ) throws IOException {
432        writeTo(Channels.newChannel(stream));
433    }
434
435    /**
436     * Write all content to the supplied channel.
437     * 
438     * @param channel the channel to which the content is to be written.
439     * @throws IOException if there is a problem writing to the supplied stream
440     */
441    public void writeTo( WritableByteChannel channel ) throws IOException {
442        int numberOfBytesToWrite = size;
443        for (ByteBuffer buffer : buffers) {
444            if (buffer == null) {
445                // already flushed
446                continue;
447            }
448            int numBytesInBuffer = Math.min(numberOfBytesToWrite, bufferSize);
449            buffer.position(numBytesInBuffer);
450            buffer.flip();
451            channel.write(buffer);
452            numberOfBytesToWrite -= numBytesInBuffer;
453        }
454        buffers.clear();
455    }
456}