View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.channels;
19  
20  import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.IOException;
27  import java.nio.ByteBuffer;
28  import java.nio.channels.ClosedChannelException;
29  import java.nio.channels.NonWritableChannelException;
30  import java.nio.channels.SeekableByteChannel;
31  import java.nio.charset.Charset;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.StandardOpenOption;
34  import java.util.stream.Stream;
35  
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.io.function.IOSupplier;
38  import org.apache.commons.lang3.ArrayUtils;
39  import org.junit.jupiter.api.Test;
40  import org.junit.jupiter.params.ParameterizedTest;
41  import org.junit.jupiter.params.provider.Arguments;
42  import org.junit.jupiter.params.provider.MethodSource;
43  
44  /**
45   * A sanity test to make sure {@link AbstractSeekableByteChannelTest} works for files.
46   */
47  public class ByteArraySeekableByteChannelTest extends AbstractSeekableByteChannelTest {
48  
49      private static final Charset CHARSET = StandardCharsets.UTF_8;
50      private static final String STRING = "Some data";
51      private static final byte[] BYTE_ARRAY = STRING.getBytes(CHARSET);
52  
53      static Stream<Arguments> testConstructor() {
54          return Stream.of(
55                  Arguments.of((IOSupplier<ByteArraySeekableByteChannel>) ByteArraySeekableByteChannel::new, EMPTY_BYTE_ARRAY, IOUtils.DEFAULT_BUFFER_SIZE),
56                  Arguments.of((IOSupplier<ByteArraySeekableByteChannel>) () -> new ByteArraySeekableByteChannel(8), EMPTY_BYTE_ARRAY, 8),
57                  Arguments.of((IOSupplier<ByteArraySeekableByteChannel>) () -> new ByteArraySeekableByteChannel(16), EMPTY_BYTE_ARRAY, 16),
58                  Arguments.of((IOSupplier<ByteArraySeekableByteChannel>) () -> ByteArraySeekableByteChannel.wrap(EMPTY_BYTE_ARRAY), EMPTY_BYTE_ARRAY, 0),
59                  Arguments.of((IOSupplier<ByteArraySeekableByteChannel>) () -> ByteArraySeekableByteChannel.wrap(BYTE_ARRAY), BYTE_ARRAY, BYTE_ARRAY.length));
60      }
61  
62      static Stream<Arguments> testShouldResizeWhenWritingMoreDataThanCapacity() {
63          return Stream.of(
64                  // Resize from 0
65                  Arguments.of(EMPTY_BYTE_ARRAY, 1),
66                  // Resize less than double
67                  Arguments.of(new byte[8], 1),
68                  // Resize more that double
69                  Arguments.of(new byte[8], 20));
70      }
71  
72      @Override
73      protected SeekableByteChannel createChannel() throws IOException {
74          return new ByteArraySeekableByteChannel();
75      }
76  
77      @Test
78      void testBuilderDefaultConstructor() throws IOException {
79          try (ByteArraySeekableByteChannel channel = new ByteArraySeekableByteChannel.Builder().get()) {
80              assertEquals(0, channel.position());
81              assertEquals(0, channel.size());
82              assertEquals(0, channel.array().length);
83          }
84      }
85  
86      @Test
87      void testBuilderDefaultMethod() throws IOException {
88          try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder().get()) {
89              assertEquals(0, channel.position());
90              assertEquals(0, channel.size());
91              assertEquals(0, channel.array().length);
92          }
93      }
94  
95      @Test
96      void testBuilderReadOnly() throws IOException {
97          try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
98                  .setByteArray(BYTE_ARRAY)
99                  .setOpenOptions(StandardOpenOption.READ)
100                 .get()) {
101             assertEquals(0, channel.position());
102             assertEquals(9, channel.size());
103             assertEquals(9, channel.array().length);
104             assertThrows(NonWritableChannelException.class, () -> channel.write(ByteBuffer.wrap(BYTE_ARRAY)));
105             assertThrows(NonWritableChannelException.class, () -> channel.truncate(0));
106         }
107     }
108 
109     private void testBuilderReadWrite(final ByteArraySeekableByteChannel channel) throws ClosedChannelException, IOException {
110         assertEquals(0, channel.position());
111         assertEquals(9, channel.size());
112         assertEquals(9, channel.array().length);
113         channel.truncate(0);
114         assertEquals(0, channel.position());
115         channel.write(ByteBuffer.wrap(BYTE_ARRAY));
116         assertEquals(9, channel.size());
117         assertEquals(9, channel.array().length);
118     }
119 
120     @Test
121     void testBuilderReadWriteExplict() throws IOException {
122         try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
123                 .setByteArray(BYTE_ARRAY)
124                 .setOpenOptions(StandardOpenOption.READ, StandardOpenOption.WRITE)
125                 .get()) {
126             testBuilderReadWrite(channel);
127         }
128     }
129 
130     @Test
131     void testBuilderReadWriteImplicit() throws IOException {
132         try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
133                 .setByteArray(BYTE_ARRAY)
134                 .get()) {
135             testBuilderReadWrite(channel);
136         }
137     }
138 
139     @Test
140     void testBuilderSetByteArray() throws IOException {
141         try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
142                 .setByteArray(BYTE_ARRAY)
143                 .get()) {
144             assertEquals(0, channel.position());
145             assertEquals(9, channel.size());
146             assertEquals(9, channel.array().length);
147         }
148     }
149 
150     @Test
151     void testBuilderSetByteArrayEmpty() throws IOException {
152         try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
153                 .setByteArray(ArrayUtils.EMPTY_BYTE_ARRAY)
154                 .get()) {
155             assertEquals(0, channel.position());
156             assertEquals(0, channel.size());
157             assertEquals(0, channel.array().length);
158         }
159     }
160 
161     @Test
162     void testBuilderSetCharSequence() throws IOException {
163         try (ByteArraySeekableByteChannel channel = ByteArraySeekableByteChannel.builder()
164                 .setCharSequence(STRING)
165                 .setCharset(CHARSET)
166                 .get()) {
167             assertEquals(0, channel.position());
168             assertEquals(9, channel.size());
169             assertEquals(9, channel.array().length);
170         }
171     }
172 
173     @ParameterizedTest
174     @MethodSource
175     void testConstructor(final IOSupplier<ByteArraySeekableByteChannel> supplier, final byte[] expected, final int capacity) throws IOException {
176         try (ByteArraySeekableByteChannel channel = supplier.get()) {
177             assertEquals(0, channel.position());
178             assertEquals(expected.length, channel.size());
179             assertEquals(capacity, channel.array().length);
180             assertArrayEquals(expected, channel.toByteArray());
181         }
182     }
183 
184     @Test
185     void testConstructorInvalid() {
186         assertThrows(IllegalArgumentException.class, () -> new ByteArraySeekableByteChannel(-1));
187         assertThrows(NullPointerException.class, () -> ByteArraySeekableByteChannel.wrap(null));
188     }
189 
190     @Test
191     void testPositionBeyondSizeReadWrite() throws IOException {
192         final ByteBuffer buffer = ByteBuffer.allocate(1);
193         channel.position(channel.size() + 1);
194         assertEquals(channel.size() + 1, channel.position());
195         assertEquals(-1, channel.read(buffer));
196         channel.position(Integer.MAX_VALUE + 1L);
197         assertEquals(Integer.MAX_VALUE + 1L, channel.position());
198         assertEquals(-1, channel.read(buffer));
199         // ByteArraySeekableByteChannel has a hard boundary at Integer.MAX_VALUE, files don't.
200         assertThrows(IOException.class, () -> channel.write(buffer));
201         assertThrows(IllegalArgumentException.class, () -> channel.position(-1));
202         assertThrows(IllegalArgumentException.class, () -> channel.position(Integer.MIN_VALUE));
203         assertThrows(IllegalArgumentException.class, () -> channel.position(Long.MIN_VALUE));
204     }
205 
206     @ParameterizedTest
207     @MethodSource
208     void testShouldResizeWhenWritingMoreDataThanCapacity(final byte[] data, final int wanted) throws IOException {
209         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(data)) {
210             c.position(data.length);
211             final ByteBuffer inData = ByteBuffer.wrap(new byte[wanted]);
212             final int writeCount = c.write(inData);
213             assertEquals(wanted, writeCount);
214             assertTrue(c.array().length >= data.length + wanted, "Capacity not increased sufficiently");
215         }
216     }
217 
218 }