Skip to content

Commit 52e1c5a

Browse files
author
Alexander Kozlov
committed
Add tests including multithreading case for ByteBufferRandomAccessSource
DEVSIX-6279
1 parent 01493a1 commit 52e1c5a

File tree

2 files changed

+204
-4
lines changed

2 files changed

+204
-4
lines changed

io/src/main/java/com/itextpdf/io/source/ByteBufferRandomAccessSource.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ This file is part of the iText (R) project.
5353
/**
5454
* A RandomAccessSource that is based on an underlying {@link java.nio.ByteBuffer}. This class takes steps to ensure
5555
* that the byte buffer
56-
* is completely freed from memory during {@link ByteBufferRandomAccessSource#close()}
56+
* is completely freed from memory during {@link ByteBufferRandomAccessSource#close()} if unmapping functionality is enabled
5757
*/
5858
class ByteBufferRandomAccessSource implements IRandomAccessSource {
5959

@@ -77,9 +77,16 @@ public ByteBufferRandomAccessSource(java.nio.ByteBuffer byteBuffer) {
7777
}
7878

7979
/**
80-
* Enables unmapping hack
80+
* Enables ByteBuffer memory unmapping hack
8181
*/
82-
public static void disableUnmapping() {
82+
public static void enableByteBufferMemoryUnmapping() {
83+
allowUnmapping = false;
84+
}
85+
86+
/**
87+
* Disables ByteBuffer memory unmapping hack
88+
*/
89+
public static void disableByteBufferMemoryUnmapping() {
8390
allowUnmapping = false;
8491
}
8592

@@ -140,7 +147,7 @@ public long length() {
140147

141148
/**
142149
* @see java.io.RandomAccessFile#close()
143-
* Cleans the mapped bytebuffers and closes the channel
150+
* Cleans the mapped bytebuffers and closes the channel if unmapping functionality is enabled
144151
*/
145152
public void close() throws java.io.IOException {
146153
if (allowUnmapping) {
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2022 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.io.source;
44+
45+
import com.itextpdf.test.AssertUtil;
46+
import com.itextpdf.test.ExtendedITextTest;
47+
import com.itextpdf.test.annotations.type.UnitTest;
48+
49+
import java.io.IOException;
50+
import java.io.RandomAccessFile;
51+
import java.nio.ByteBuffer;
52+
import java.nio.channels.FileChannel;
53+
import java.util.ArrayList;
54+
import java.util.Arrays;
55+
import java.util.List;
56+
import java.util.concurrent.Callable;
57+
import java.util.concurrent.ExecutionException;
58+
import java.util.concurrent.ExecutorService;
59+
import java.util.concurrent.Executors;
60+
import java.util.concurrent.Future;
61+
import org.junit.Assert;
62+
import org.junit.Test;
63+
import org.junit.experimental.categories.Category;
64+
65+
@Category(UnitTest.class)
66+
public class ByteBufferRandomAccessSourceTest extends ExtendedITextTest {
67+
68+
private final static String SOURCE_FILE = "./src/test/resources/com/itextpdf/io/source/RAF.txt";
69+
70+
@Test
71+
public void heapByteBufferTest() {
72+
IRandomAccessSource source = new ByteBufferRandomAccessSource(ByteBuffer.allocate(10));
73+
AssertUtil.doesNotThrow(source::close);
74+
}
75+
76+
@Test
77+
public void nullByteBufferTest() {
78+
IRandomAccessSource source = new ByteBufferRandomAccessSource(null);
79+
AssertUtil.doesNotThrow(source::close);
80+
}
81+
82+
@Test
83+
public void disableUnmappingTest() throws IOException {
84+
ByteBufferRandomAccessSource.disableByteBufferMemoryUnmapping();
85+
IRandomAccessSource source;
86+
try (RandomAccessFile raf = new RandomAccessFile(SOURCE_FILE, "r")) {
87+
FileChannel channel = raf.getChannel();
88+
89+
source = new ByteBufferRandomAccessSource(
90+
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
91+
}
92+
AssertUtil.doesNotThrow(() -> source.get(0));
93+
94+
source.close();
95+
AssertUtil.doesNotThrow(() -> source.get(0));
96+
97+
ByteBufferRandomAccessSource.enableByteBufferMemoryUnmapping();
98+
}
99+
100+
@Test
101+
public void readIntFromFile() throws IOException {
102+
try (RandomAccessFile raf = new RandomAccessFile(SOURCE_FILE, "r")) {
103+
FileChannel channel = raf.getChannel();
104+
105+
IRandomAccessSource source = new ByteBufferRandomAccessSource(
106+
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
107+
108+
Assert.assertEquals(13, source.length());
109+
Assert.assertEquals(72, source.get(0));
110+
Assert.assertEquals(44, source.get(5));
111+
Assert.assertEquals(33, source.get(12));
112+
Assert.assertEquals(100, source.get(11));
113+
Assert.assertEquals(-1, source.get(13));
114+
115+
long position = Integer.MAX_VALUE + 1L;
116+
Assert.assertThrows(IllegalArgumentException.class, () -> source.get(position));
117+
}
118+
}
119+
120+
@Test
121+
public void readBytesFromFile() throws IOException {
122+
try (RandomAccessFile raf = new RandomAccessFile(SOURCE_FILE, "r")) {
123+
FileChannel channel = raf.getChannel();
124+
125+
IRandomAccessSource source = new ByteBufferRandomAccessSource(
126+
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
127+
128+
byte[] expected = new byte[] {72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33};
129+
byte[] result = new byte[13];
130+
source.get(0, result, 0, 13);
131+
Assert.assertArrayEquals(expected, result);
132+
133+
expected = new byte[] {111, 44, 32, 119, 111, 114, 108, 100};
134+
result = new byte[8];
135+
source.get(4, result, 0, 8);
136+
Assert.assertArrayEquals(expected, result);
137+
138+
long position = Integer.MAX_VALUE + 1L;
139+
Assert.assertThrows(IllegalArgumentException.class, () -> source.get(position, new byte[6], 2, 4));
140+
}
141+
}
142+
143+
@Test
144+
public void readFileWithMultipleThreadsTest() throws InterruptedException, ExecutionException, IOException {
145+
146+
try (RandomAccessFile raf = new RandomAccessFile(SOURCE_FILE, "r")) {
147+
FileChannel channel = raf.getChannel();
148+
149+
IRandomAccessSource source = new ByteBufferRandomAccessSource(
150+
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
151+
152+
ReadFileTask task1 = new ReadFileTask(source);
153+
ReadFileTask task2 = new ReadFileTask(source);
154+
155+
ExecutorService executor = Executors.newFixedThreadPool(2);
156+
Future<List<Integer>> future1 = executor.submit(task1);
157+
Future<List<Integer>> future2 = executor.submit(task2);
158+
159+
List<Integer> expected = Arrays.asList(13,
160+
72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33,
161+
33, 100, 108, 114, 111, 119, 32, 44, 111, 108, 108, 101, 72);
162+
163+
List<Integer> result1 = future1.get();
164+
List<Integer> result2 = future2.get();
165+
166+
Assert.assertEquals(expected, result1);
167+
Assert.assertEquals(expected, result2);
168+
}
169+
}
170+
171+
private static class ReadFileTask implements Callable<List<Integer>> {
172+
173+
final private IRandomAccessSource source;
174+
175+
ReadFileTask(IRandomAccessSource source) {
176+
this.source = source;
177+
}
178+
179+
public List<Integer> call() throws IOException {
180+
List<Integer> result = new ArrayList<>();
181+
182+
result.add((int) source.length());
183+
for (long position = 0; position < source.length(); ++position) {
184+
result.add(source.get(position));
185+
}
186+
for (long position = source.length() - 1; position >= 0; --position) {
187+
result.add(source.get(position));
188+
}
189+
190+
return result;
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)