Skip to content

Commit 9909afe

Browse files
committed
Merge branch 'release/1.1'
2 parents a132f29 + ea99bdc commit 9909afe

8 files changed

Lines changed: 229 additions & 163 deletions

File tree

.classpath

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
</classpathentry>
1616
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
1717
<attributes>
18-
<attribute name="module" value="true"/>
1918
<attribute name="maven.pomderived" value="true"/>
2019
</attributes>
2120
</classpathentry>
@@ -25,5 +24,22 @@
2524
</attributes>
2625
</classpathentry>
2726
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
27+
<classpathentry kind="src" path="target/generated-sources/annotations">
28+
<attributes>
29+
<attribute name="ignore_optional_problems" value="true"/>
30+
<attribute name="optional" value="true"/>
31+
<attribute name="maven.pomderived" value="true"/>
32+
<attribute name="m2e-apt" value="true"/>
33+
</attributes>
34+
</classpathentry>
35+
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
36+
<attributes>
37+
<attribute name="optional" value="true"/>
38+
<attribute name="maven.pomderived" value="true"/>
39+
<attribute name="ignore_optional_problems" value="true"/>
40+
<attribute name="m2e-apt" value="true"/>
41+
<attribute name="test" value="true"/>
42+
</attributes>
43+
</classpathentry>
2844
<classpathentry kind="output" path="target/classes"/>
2945
</classpath>

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
/target/
2+
/.apt_generated/
3+
/.apt_generated_tests/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
eclipse.preferences.version=1
2+
org.eclipse.jdt.apt.aptEnabled=false
3+
org.eclipse.jdt.apt.genSrcDir=.apt_generated
4+
org.eclipse.jdt.apt.genTestSrcDir=.apt_generated_tests
5+
org.eclipse.jdt.apt.reconcileEnabled=true

.settings/org.eclipse.jdt.core.prefs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
1212
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
1313
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
1414
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
15-
org.eclipse.jdt.core.compiler.release=enabled
15+
org.eclipse.jdt.core.compiler.processAnnotations=disabled
16+
org.eclipse.jdt.core.compiler.release=disabled
1617
org.eclipse.jdt.core.compiler.source=11

pom.xml

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>io.cloudonix</groupId>
66
<artifactId>vertx-java.io</artifactId>
7-
<version>1.0.3</version>
7+
<version>1.1.0</version>
88
<name>Vert.x Java IO</name>
99
<description>Utilities for integrating Vert.x IO with Java IO</description>
1010

1111
<properties>
12-
<vertx.version>4.1.2</vertx.version>
12+
<vertx.version>4.1.5</vertx.version>
1313
<maven.compiler.release>11</maven.compiler.release>
1414
<maven.compiler.source>11</maven.compiler.source>
1515
<maven.compiler.target>11</maven.compiler.target>
@@ -31,8 +31,44 @@
3131
<groupId>org.hamcrest</groupId>
3232
<artifactId>hamcrest-core</artifactId>
3333
<version>2.2</version>
34-
<scope>test</scope>
35-
</dependency>
36-
34+
<scope>test</scope>
35+
</dependency>
3736
</dependencies>
38-
</project>
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.maven.plugins</groupId>
42+
<artifactId>maven-surefire-plugin</artifactId>
43+
<version>3.0.0-M5</version>
44+
</plugin>
45+
<plugin>
46+
<groupId>org.apache.maven.plugins</groupId>
47+
<artifactId>maven-source-plugin</artifactId>
48+
<version>3.2.1</version>
49+
<executions>
50+
<execution>
51+
<goals>
52+
<goal>jar-no-fork</goal>
53+
</goals>
54+
</execution>
55+
</executions>
56+
</plugin>
57+
<plugin>
58+
<groupId>org.apache.maven.plugins</groupId>
59+
<artifactId>maven-javadoc-plugin</artifactId>
60+
<version>3.3.1</version>
61+
<executions>
62+
<execution>
63+
<goals>
64+
<goal>jar</goal>
65+
</goals>
66+
</execution>
67+
</executions>
68+
<configuration>
69+
<release>17</release>
70+
</configuration>
71+
</plugin>
72+
</plugins>
73+
</build>
74+
</project>

src/main/java/io/cloudonix/vertx/javaio/OutputToReadStream.java

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,43 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.io.OutputStream;
6+
import java.util.Objects;
67
import java.util.concurrent.CountDownLatch;
8+
import java.util.concurrent.ForkJoinPool;
79
import java.util.concurrent.atomic.AtomicLong;
810
import java.util.concurrent.atomic.AtomicReference;
911

12+
import io.vertx.core.AsyncResult;
1013
import io.vertx.core.Context;
14+
import io.vertx.core.Future;
1115
import io.vertx.core.Handler;
16+
import io.vertx.core.Promise;
1217
import io.vertx.core.Vertx;
1318
import io.vertx.core.buffer.Buffer;
1419
import io.vertx.core.streams.ReadStream;
20+
import io.vertx.core.streams.WriteStream;
1521

22+
/**
23+
* A conversion utility to help move data from a Java classic blocking IO to a Vert.x asynchronous Stream.
24+
*
25+
* Use this class to create an {@link OutputStream} that pushes data written to it to a {@link ReadStream}
26+
* API.
27+
*
28+
* The ReadStream handlers will be called on a Vert.x context, and the {@link #close()} method must be called
29+
* for the ReadStream end handler to be triggered.
30+
*
31+
* It is recommended to use this class in the context of a blocking try-with-resources block, to
32+
* ensure that streams are closed properly. For example:
33+
*
34+
* <tt><pre>
35+
* try (final OutputToReadStream os = new OutputToReadStream(vertx); final InputStream is = getInput()) {
36+
* os.pipeTo(someWriteStream);
37+
* is.transferTo(os);
38+
* }
39+
* </pre></tt>
40+
*
41+
* @author guss77
42+
*/
1643
public class OutputToReadStream extends OutputStream implements ReadStream<Buffer> {
1744

1845
private AtomicReference<CountDownLatch> paused = new AtomicReference<>(new CountDownLatch(0));
@@ -28,26 +55,50 @@ public OutputToReadStream(Vertx vertx) {
2855
}
2956

3057
/**
31-
* Helper utility to "convert" a Java {@link InputStream} to a {@link ReadStream}<code>&lt;Buffer&gt;</code>.
58+
* Helper utility to pipe a Java {@link InputStream} to a {@link WriteStream}.
3259
*
33-
* This method uses {@link InputStream#transferTo(OutputStream)}, and in addition it will attempt to close the
34-
* InputStream once it is done transferring its data, and call this ReadStream's end handler if no error occurred.
60+
* This method is non-blocking and Vert.x context safe. It uses the common ForkJoinPool to perform the
61+
* Java blocking IO and will try to propagate IO failures to the returned {@link Future}.
3562
*
36-
* If an error has occurred, the end handler is not guaranteed to be called, and a "best effort" attempt will be made
37-
* to close the input stream.
38-
* @param is InputStream instance to wrap
39-
* @return this instance, which will immediately start producing data from the specified input stream
63+
* This method uses {@link InputStream#transferTo(OutputStream)} to copy all the data, and will then
64+
* attempt to close both streams asynchronously. Some Java compilers might not detect that the streams
65+
* will be safely closed and will issue leak warnings.
66+
*
67+
* @param source InputStream to drain
68+
* @param sink WriteStream to pipe data to
69+
* @return a Future that will succeed when all the data have been written and the streams closed, or fail if an
70+
* {@link IOException} has occurred
4071
*/
41-
public OutputToReadStream wrap(InputStream is) {
42-
context.executeBlocking(p -> {
43-
try (is) {
44-
is.transferTo(this);
45-
p.complete();
46-
} catch (Throwable e) {
47-
p.fail(e);
72+
public Future<Void> pipeFromInput(InputStream source, WriteStream<Buffer> sink) {
73+
Promise<Void> promise = Promise.promise();
74+
pipeTo(sink, promise);
75+
ForkJoinPool.commonPool().submit(() -> {
76+
try (final InputStream is = source; final OutputStream os = this){
77+
source.transferTo(this);
78+
} catch (IOException e) {
79+
promise.tryFail(e);
4880
}
49-
}).onFailure(errorHandler::handle).onSuccess(v -> endHandler.handle(null));
50-
return this;
81+
});
82+
return promise.future();
83+
}
84+
85+
/**
86+
* Helper utility to pipe a Java {@link InputStream} to a {@link WriteStream}.
87+
*
88+
* This method is non-blocking and Vert.x context safe. It uses the common ForkJoinPool to perform the
89+
* Java blocking IO and will try to propagate IO failures to the returned {@link Future}
90+
*
91+
* This method uses {@link InputStream#transferTo(OutputStream)} to copy all the data, and will then
92+
* attempt to close both streams asynchronously. Some Java compilers might not detect that the streams
93+
* will be safely closed and will issue leak warnings.
94+
*
95+
* @param source InputStream to drain
96+
* @param sink WriteStream to pipe data to
97+
* @param handler a handler that will be called when all the data have been written and the streams closed,
98+
* or if an {@link IOException} has occurred.
99+
*/
100+
public void pipeFromInput(InputStream source, WriteStream<Buffer> sink, Handler<AsyncResult<Void>> handler) {
101+
pipeFromInput(source, sink).onComplete(handler);
51102
}
52103

53104
/**
@@ -71,13 +122,13 @@ public void sendError(Throwable t) {
71122
public OutputToReadStream exceptionHandler(Handler<Throwable> handler) {
72123
// we are usually not propagating exceptions as OutputStream has no mechanism for propagating exceptions down,
73124
// except when wrapping an input stream, in which case we can forward InputStream read errors to the error handler.
74-
errorHandler = handler;
125+
errorHandler = Objects.requireNonNullElse(handler, t -> {});
75126
return this;
76127
}
77128

78129
@Override
79130
public OutputToReadStream handler(Handler<Buffer> handler) {
80-
this.dataHandler = handler;
131+
this.dataHandler = Objects.requireNonNullElse(handler, d -> {});
81132
return this;
82133
}
83134

@@ -102,7 +153,7 @@ public OutputToReadStream fetch(long amount) {
102153

103154
@Override
104155
public OutputToReadStream endHandler(Handler<Void> endHandler) {
105-
this.endHandler = endHandler;
156+
this.endHandler = Objects.requireNonNullElse(endHandler, v -> {});
106157
return this;
107158
}
108159

@@ -134,6 +185,8 @@ synchronized public void write(byte[] b, int off, int len) throws IOException {
134185

135186
@Override
136187
synchronized public void close() throws IOException {
188+
if (closed)
189+
return;
137190
closed = true;
138191
try {
139192
paused.get().await();
@@ -155,10 +208,7 @@ private void push(Buffer data) {
155208
dataHandler.handle(data);
156209
awaiter.countDown();
157210
} catch (Throwable t) {
158-
if (errorHandler != null)
159-
errorHandler.handle(t);
160-
else
161-
System.err.println("Unexpected exception in OutputToReadStream and no error handler: " + t);
211+
errorHandler.handle(t);
162212
}
163213
});
164214
try {

src/main/java/io/cloudonix/vertx/javaio/WriteToInputStream.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
import io.vertx.core.buffer.Buffer;
1717
import io.vertx.core.streams.WriteStream;
1818

19+
/**
20+
* A conversion utility to help move data from a Vert.x asynchronous stream to Java classic blocking IO.
21+
*
22+
* Use this class to create a {@link WriteStream} that buffers data written to it so that a consumer
23+
* can use the {@link InputStream} API to read it.
24+
*
25+
* The default queue size is 1000 writes, but it can be changed using {@link #setWriteQueueMaxSize(int)}
26+
*
27+
* @author guss77
28+
*/
1929
public class WriteToInputStream extends InputStream implements WriteStream<Buffer>{
2030

2131
private class PendingWrite {
@@ -145,19 +155,21 @@ public boolean writeQueueFull() {
145155

146156
@Override
147157
synchronized public int read() throws IOException {
148-
while (!buffer.isEmpty() && buffer.peek().shouldDiscard()) buffer.poll();
149-
if (!buffer.isEmpty())
150-
return buffer.peek().readNext();
151-
// set latch to signal we are waiting
152-
var latch = new CountDownLatch(1);
153-
readsWaiting.add(latch);
154-
if (buffer.isEmpty())
155-
try {
156-
latch.await();
157-
} catch (InterruptedException e) {
158-
throw new IOException("Failed to wait for data", e);
159-
}
160-
return read(); // try to read again
158+
while (true) {
159+
while (!buffer.isEmpty() && buffer.peek().shouldDiscard()) buffer.poll();
160+
if (!buffer.isEmpty())
161+
return buffer.peek().readNext();
162+
// set latch to signal we are waiting
163+
var latch = new CountDownLatch(1);
164+
readsWaiting.add(latch);
165+
if (buffer.isEmpty())
166+
try {
167+
latch.await();
168+
} catch (InterruptedException e) {
169+
throw new IOException("Failed to wait for data", e);
170+
}
171+
// now try to read again
172+
}
161173
}
162174

163175
@Override
@@ -174,7 +186,6 @@ synchronized public int read(byte[] b, int off, int len) throws IOException {
174186
return val;
175187
b[off] = (byte) (val & 0xFF);
176188
return 1 + read(b, off + 1, len - 1);
177-
178189
}
179190

180191
@Override

0 commit comments

Comments
 (0)