1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.graalvm.compiler.salver.writer;
24
25import java.io.IOException;
26import java.nio.ByteBuffer;
27import java.nio.CharBuffer;
28import java.nio.ReadOnlyBufferException;
29import java.nio.channels.WritableByteChannel;
30import java.nio.charset.CharsetEncoder;
31import java.nio.charset.CoderResult;
32import java.nio.charset.StandardCharsets;
33
34public class ChannelDumpWriter implements DumpWriter {
35
36    private static final int BUFFER_CAPACITY = 256 * 1024;
37
38    protected final WritableByteChannel channel;
39    protected final ByteBuffer buffer;
40
41    public ChannelDumpWriter(WritableByteChannel channel) {
42        this(channel, ByteBuffer.allocateDirect(BUFFER_CAPACITY));
43    }
44
45    public ChannelDumpWriter(WritableByteChannel channel, ByteBuffer buffer) {
46        this.channel = channel;
47        this.buffer = buffer;
48    }
49
50    private void ensureAvailable(int len) throws IOException {
51        if (buffer.isReadOnly()) {
52            throw new ReadOnlyBufferException();
53        }
54        if (buffer.capacity() < len) {
55            throw new IllegalArgumentException();
56        }
57        while (buffer.remaining() < len) {
58            flush();
59        }
60    }
61
62    @Override
63    public ChannelDumpWriter write(byte b) throws IOException {
64        ensureAvailable(1);
65        buffer.put(b);
66        return this;
67    }
68
69    @Override
70    public ChannelDumpWriter write(byte[] arr) throws IOException {
71        if (buffer.isReadOnly()) {
72            throw new ReadOnlyBufferException();
73        }
74        int offset = 0;
75        while (offset < arr.length) {
76            int available = buffer.remaining();
77            int length = Math.min(available, arr.length - offset);
78            buffer.put(arr, offset, length);
79            if (!buffer.hasRemaining()) {
80                flush();
81            }
82            offset += length;
83        }
84        return this;
85    }
86
87    @Override
88    public ChannelDumpWriter write(ByteBuffer buf) throws IOException {
89        if (buf == buffer) {
90            throw new IllegalArgumentException();
91        }
92        if (buffer.isReadOnly()) {
93            throw new ReadOnlyBufferException();
94        }
95        while (buf.hasRemaining()) {
96            int available = buffer.remaining();
97            int remaining = buf.remaining();
98            for (int i = 0, n = Math.min(available, remaining); i < n; i++) {
99                buffer.put(buf.get());
100            }
101            if (!buffer.hasRemaining()) {
102                flush();
103            }
104        }
105        return this;
106    }
107
108    @Override
109    public ChannelDumpWriter write(CharSequence csq) throws IOException {
110        if (buffer.isReadOnly()) {
111            throw new ReadOnlyBufferException();
112        }
113        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
114        CharBuffer buf = CharBuffer.wrap(csq);
115        while (true) {
116            CoderResult result = encoder.encode(buf, buffer, true);
117            if (result.isError()) {
118                throw new IOException(result.toString());
119            }
120            if (!buffer.hasRemaining()) {
121                flush();
122            }
123            if (result.isOverflow()) {
124                continue;
125            }
126            break;
127        }
128        return this;
129    }
130
131    @Override
132    public ChannelDumpWriter writeChar(char v) throws IOException {
133        ensureAvailable(1 << 1);
134        buffer.putChar(v);
135        return this;
136    }
137
138    @Override
139    public ChannelDumpWriter writeShort(short v) throws IOException {
140        ensureAvailable(1 << 1);
141        buffer.putShort(v);
142        return this;
143    }
144
145    @Override
146    public ChannelDumpWriter writeInt(int v) throws IOException {
147        ensureAvailable(1 << 2);
148        buffer.putInt(v);
149        return this;
150    }
151
152    @Override
153    public ChannelDumpWriter writeLong(long v) throws IOException {
154        ensureAvailable(1 << 3);
155        buffer.putLong(v);
156        return this;
157    }
158
159    @Override
160    public ChannelDumpWriter writeFloat(float v) throws IOException {
161        ensureAvailable(1 << 2);
162        buffer.putFloat(v);
163        return this;
164    }
165
166    @Override
167    public ChannelDumpWriter writeDouble(double v) throws IOException {
168        ensureAvailable(1 << 3);
169        buffer.putDouble(v);
170        return this;
171    }
172
173    @Override
174    public void flush() throws IOException {
175        if (buffer != null && channel != null) {
176            buffer.flip();
177            channel.write(buffer);
178            buffer.compact();
179        }
180    }
181
182    @Override
183    public void close() throws IOException {
184        if (channel != null) {
185            try {
186                flush();
187            } finally {
188                channel.close();
189            }
190        }
191    }
192}
193