View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.buffer;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.util.CharsetUtil;
20  
21  import java.io.DataOutput;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  
26  import static java.util.Objects.requireNonNull;
27  
28  /**
29   * An {@link OutputStream} which writes data to a {@link Buffer}.
30   * <p>
31   * A write operation against this stream will occur at the {@code writerOffset}
32   * of its underlying buffer and the {@code writerOffset} will increase during
33   * the write operation.
34   * <p>
35   * This stream implements {@link DataOutput} for your convenience.
36   * The endianness of the stream is always big endian.
37   *
38   * @see BufferInputStream
39   */
40  public final class BufferOutputStream extends OutputStream implements DataOutput {
41      private final Buffer buffer;
42      private final int startIndex;
43      private DataOutputStream utf8out; // lazily-instantiated
44      private boolean closed;
45  
46      /**
47       * Creates a new stream which writes data to the specified {@code buffer}.
48       */
49      public BufferOutputStream(Buffer buffer) {
50          this.buffer = requireNonNull(buffer, "buffer");
51          startIndex = buffer.writerOffset();
52      }
53  
54      /**
55       * Returns the number of written bytes by this stream so far.
56       */
57      public int writtenBytes() {
58          return buffer.writerOffset() - startIndex;
59      }
60  
61      @Override
62      public void write(byte[] b, int off, int len) throws IOException {
63          prepareWrite(len);
64          if (len > 0) {
65              buffer.writeBytes(b, off, len);
66          }
67      }
68  
69      @Override
70      public void write(byte[] b) throws IOException {
71          prepareWrite(b.length);
72          if (b.length > 0) {
73              buffer.writeBytes(b);
74          }
75      }
76  
77      @Override
78      public void write(int b) throws IOException {
79          prepareWrite(Integer.BYTES);
80          buffer.writeByte((byte) b);
81      }
82  
83      @Override
84      public void writeBoolean(boolean v) throws IOException {
85          prepareWrite(1);
86          buffer.writeByte((byte) (v ? 1 : 0));
87      }
88  
89      @Override
90      public void writeByte(int v) throws IOException {
91          prepareWrite(1);
92          buffer.writeByte((byte) v);
93      }
94  
95      @Override
96      public void writeBytes(String s) throws IOException {
97          prepareWrite(s.length()); // US ASCII has no multibyte values, so this works.
98          buffer.writeCharSequence(s, CharsetUtil.US_ASCII);
99      }
100 
101     @Override
102     public void writeChar(int v) throws IOException {
103         prepareWrite(Character.BYTES);
104         buffer.writeChar((char) v);
105     }
106 
107     @Override
108     public void writeChars(String s) throws IOException {
109         int len = s.length();
110         prepareWrite(len * Character.BYTES);
111         for (int i = 0 ; i < len ; i ++) {
112             buffer.writeChar(s.charAt(i));
113         }
114     }
115 
116     @Override
117     public void writeDouble(double v) throws IOException {
118         prepareWrite(Double.BYTES);
119         buffer.writeDouble(v);
120     }
121 
122     @Override
123     public void writeFloat(float v) throws IOException {
124         prepareWrite(Float.BYTES);
125         buffer.writeFloat(v);
126     }
127 
128     @Override
129     public void writeInt(int v) throws IOException {
130         prepareWrite(Integer.BYTES);
131         buffer.writeInt(v);
132     }
133 
134     @Override
135     public void writeLong(long v) throws IOException {
136         prepareWrite(Long.BYTES);
137         buffer.writeLong(v);
138     }
139 
140     @Override
141     public void writeShort(int v) throws IOException {
142         prepareWrite(Short.BYTES);
143         buffer.writeShort((short) v);
144     }
145 
146     @Override
147     public void writeUTF(String s) throws IOException {
148         if (closed) {
149             throw new IOException("The stream is closed");
150         }
151         DataOutputStream out = utf8out;
152         if (out == null) {
153             // Suppress a warning since the stream is closed in the close() method
154             utf8out = out = new DataOutputStream(this); // lgtm[java/output-resource-leak]
155         }
156         out.writeUTF(s);
157     }
158 
159     /**
160      * Returns the buffer where this stream is writing data.
161      */
162     public Buffer buffer() {
163         return buffer;
164     }
165 
166     @Override
167     public void close() throws IOException {
168         if (closed) {
169             return;
170         }
171         closed = true;
172 
173         try {
174             super.close();
175         } finally {
176             if (utf8out != null) {
177                 utf8out.close();
178             }
179         }
180     }
181 
182     private void prepareWrite(int len) throws IOException {
183         if (closed) {
184             throw new IOException("Stream closed");
185         }
186         if (len > 0) {
187             buffer.ensureWritable(len, buffer.capacity() >> 2, true);
188         }
189     }
190 }