Skip to content

Commit e180578

Browse files
committed
Added the ObservableInputStream, and the MessageDigestCalculatingInputStream.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@1750760 13f79535-47bb-0310-9956-ffa450edef68
1 parent 2b94433 commit e180578

6 files changed

Lines changed: 510 additions & 0 deletions

File tree

src/changes/changes.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ The <action> type attribute can be add,update,fix,remove.
4646

4747
<body>
4848
<!-- The release date is the date RC is cut -->
49+
<release version="2.7" date="Not yet published">
50+
<action dev="jochen" type="add">
51+
Added the ObservableInputStream, and the MessageDigestCalculatingInputStream.
52+
</action>
53+
</release>
4954
<release version="2.6" date="2016-MM-DD" description="New features and bug fixes.">
5055
<action issue="IO-511" dev="britter" type="fix" due-to="Ahmet Celik">
5156
After a few unit tests, a few newly created directories not cleaned completely.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
* http://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+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.security.MessageDigest;
22+
import java.security.NoSuchAlgorithmException;
23+
24+
25+
/**
26+
* This class is an example for using an {@link ObservableInputStream}. It
27+
* creates its own {@link Observer}, which calculates a checksum using a
28+
* MessageDigest, for example an MD5 sum.
29+
* {@em Note}: Neither {@link ObservableInputStream}, nor {@link MessageDigest},
30+
* are thread safe. So is {@link MessageDigestCalculatingInputStream}.
31+
*/
32+
public class MessageDigestCalculatingInputStream extends ObservableInputStream {
33+
public static class MessageDigestMaintainingObserver extends Observer {
34+
private final MessageDigest md;
35+
36+
public MessageDigestMaintainingObserver(MessageDigest pMd) {
37+
md = pMd;
38+
}
39+
40+
@Override
41+
void data(int pByte) throws IOException {
42+
md.update((byte) pByte);
43+
}
44+
45+
@Override
46+
void data(byte[] pBuffer, int pOffset, int pLength) throws IOException {
47+
md.update(pBuffer, pOffset, pLength);
48+
}
49+
}
50+
51+
private final MessageDigest messageDigest;
52+
53+
/** Creates a new instance, which calculates a signature on the given stream,
54+
* using the given {@link MessageDigest}.
55+
*/
56+
public MessageDigestCalculatingInputStream(InputStream pStream, MessageDigest pDigest) {
57+
super(pStream);
58+
messageDigest = pDigest;
59+
add(new MessageDigestMaintainingObserver(pDigest));
60+
}
61+
/** Creates a new instance, which calculates a signature on the given stream,
62+
* using a {@link MessageDigest} with the given algorithm.
63+
*/
64+
public MessageDigestCalculatingInputStream(InputStream pStream, String pAlgorithm) throws NoSuchAlgorithmException {
65+
this(pStream, MessageDigest.getInstance(pAlgorithm));
66+
}
67+
/** Creates a new instance, which calculates a signature on the given stream,
68+
* using a {@link MessageDigest} with the "MD5" algorithm.
69+
*/
70+
public MessageDigestCalculatingInputStream(InputStream pStream) throws NoSuchAlgorithmException {
71+
this(pStream, MessageDigest.getInstance("MD5"));
72+
}
73+
74+
/** Returns the {@link MessageDigest}, which is being used for generating the
75+
* checksum.
76+
* {@em Note}: The checksum will only reflect the data, which has been read so far.
77+
* This is probably not, what you expect. Make sure, that the complete data has been
78+
* read, if that is what you want. The easiest way to do so is by invoking
79+
* {@link #consume()}.
80+
*/
81+
public MessageDigest getMessageDigest() {
82+
return messageDigest;
83+
}
84+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
* http://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+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.security.MessageDigest;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
26+
/**
27+
* The {@link ObservableInputStream} allows, that an InputStream may be consumed
28+
* by other receivers, apart from the thread, which is reading it.
29+
* The other consumers are implemented as instances of {@link Observer}. A
30+
* typical application may be the generation of a {@link MessageDigest} on the
31+
* fly.
32+
* {@code Note}: The {@link ObservableInputStream} is <em>not</em> thread safe,
33+
* as instances of InputStream usually aren't.
34+
* If you must access the stream from multiple threads, then synchronization, locking,
35+
* or a similar means must be used.
36+
* @see MessageDigestCalculatingInputStream
37+
*/
38+
public class ObservableInputStream extends ProxyInputStream {
39+
public static abstract class Observer {
40+
/** Called to indicate, that {@link InputStream#read()} has been invoked
41+
* on the {@link ObservableInputStream}, and will return a value.
42+
* @param pByte The value, which is being returned. This will never be -1 (EOF),
43+
* because, in that case, {link #finished()} will be invoked instead.
44+
*/
45+
void data(int pByte) throws IOException {}
46+
/** Called to indicate, that {@link InputStream#read(byte[])}, or
47+
* {@link InputStream#read(byte[], int, int)} have been called, and are about to
48+
* invoke data.
49+
* @param pBuffer The byte array, which has been passed to the read call, and where
50+
* data has been stored.
51+
* @param pOffset The offset within the byte array, where data has been stored.
52+
* @param pLength The number of bytes, which have been stored in the byte array.
53+
*/
54+
void data(byte[] pBuffer, int pOffset, int pLength) throws IOException {}
55+
/** Called to indicate, that EOF has been seen on the underlying stream.
56+
* This method may be called multiple times, if the reader keeps invoking
57+
* either of the read methods, and they will consequently keep returning
58+
* EOF.
59+
*/
60+
void finished() throws IOException {}
61+
/** Called to indicate, that the {@link ObservableInputStream} has been closed.
62+
*/
63+
void closed() throws IOException {}
64+
/**
65+
* Called to indicate, that an error occurred on the underlying stream.
66+
*/
67+
void error(IOException pException) throws IOException { throw pException; }
68+
}
69+
70+
private final List<Observer> observers = new ArrayList<Observer>();
71+
72+
public ObservableInputStream(InputStream pProxy) {
73+
super(pProxy);
74+
}
75+
76+
public void add(Observer pObserver) {
77+
observers.add(pObserver);
78+
}
79+
80+
public void remove(Observer pObserver) {
81+
observers.remove(pObserver);
82+
}
83+
84+
public void removeAllObservers() {
85+
observers.clear();
86+
}
87+
88+
@Override
89+
public int read() throws IOException {
90+
int result = 0;
91+
IOException ioe = null;
92+
try {
93+
result = super.read();
94+
} catch (IOException pException) {
95+
ioe = pException;
96+
}
97+
if (ioe != null) {
98+
noteError(ioe);
99+
} else if (result == -1) {
100+
noteFinished();
101+
} else {
102+
noteDataByte(result);
103+
}
104+
return result;
105+
}
106+
107+
@Override
108+
public int read(byte[] pBuffer) throws IOException {
109+
int result = 0;
110+
IOException ioe = null;
111+
try {
112+
result = super.read(pBuffer);
113+
} catch (IOException pException) {
114+
ioe = pException;
115+
}
116+
if (ioe != null) {
117+
noteError(ioe);
118+
} else if (result == -1) {
119+
noteFinished();
120+
} else if (result > 0) {
121+
noteDataBytes(pBuffer, 0, result);
122+
}
123+
return result;
124+
}
125+
126+
@Override
127+
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
128+
int result = 0;
129+
IOException ioe = null;
130+
try {
131+
result = super.read(pBuffer, pOffset, pLength);
132+
} catch (IOException pException) {
133+
ioe = pException;
134+
}
135+
if (ioe != null) {
136+
noteError(ioe);
137+
} else if (result == -1) {
138+
noteFinished();
139+
} else if (result > 0) {
140+
noteDataBytes(pBuffer, pOffset, result);
141+
}
142+
return result;
143+
}
144+
145+
/** Notifies the observers by invoking {@link Observer#data(byte[],int,int)}
146+
* with the given arguments.
147+
* @param pBuffer Passed to the observers.
148+
* @param pOffset Passed to the observers.
149+
* @param pLength Passed to the observers.
150+
* @throws IOException Some observer has thrown an exception, which is being
151+
* passed down.
152+
*/
153+
protected void noteDataBytes(byte[] pBuffer, int pOffset, int pLength) throws IOException {
154+
for (Observer observer : getObservers()) {
155+
observer.data(pBuffer, pOffset, pLength);
156+
}
157+
}
158+
159+
/** Notifies the observers by invoking {@link Observer#finished()}.
160+
* @throws IOException Some observer has thrown an exception, which is being
161+
* passed down.
162+
*/
163+
protected void noteFinished() throws IOException {
164+
for (Observer observer : getObservers()) {
165+
observer.finished();
166+
}
167+
}
168+
169+
/** Notifies the observers by invoking {@link Observer#data(int)}
170+
* with the given arguments.
171+
* @param pDataByte Passed to the observers.
172+
* @throws IOException Some observer has thrown an exception, which is being
173+
* passed down.
174+
*/
175+
protected void noteDataByte(int pDataByte) throws IOException {
176+
for (Observer observer : getObservers()) {
177+
observer.data(pDataByte);
178+
}
179+
}
180+
181+
/** Notifies the observers by invoking {@link Observer#error(IOException)}
182+
* with the given argument.
183+
* @param pException Passed to the observers.
184+
* @throws IOException Some observer has thrown an exception, which is being
185+
* passed down. This may be the same exception, which has been passed as an
186+
* argument.
187+
*/
188+
protected void noteError(IOException pException) throws IOException {
189+
for (Observer observer : getObservers()) {
190+
observer.error(pException);
191+
}
192+
}
193+
194+
/** Notifies the observers by invoking {@link Observer#finished()}.
195+
* @throws IOException Some observer has thrown an exception, which is being
196+
* passed down.
197+
*/
198+
protected void noteClosed() throws IOException {
199+
for (Observer observer : getObservers()) {
200+
observer.closed();
201+
}
202+
}
203+
204+
protected List<Observer> getObservers() {
205+
return observers;
206+
}
207+
208+
@Override
209+
public void close() throws IOException {
210+
IOException ioe = null;
211+
try {
212+
super.close();
213+
} catch (IOException e) {
214+
ioe = e;
215+
}
216+
if (ioe == null) {
217+
noteClosed();
218+
} else {
219+
noteError(ioe);
220+
}
221+
}
222+
223+
/** Reads all data from the underlying {@link InputStream}, while notifying the
224+
* observers.
225+
* @throws IOException The underlying {@link InputStream}, or either of the
226+
* observers has thrown an exception.
227+
*/
228+
public void consume() throws IOException {
229+
final byte[] buffer = new byte[8192];
230+
for (;;) {
231+
final int res = read(buffer);
232+
if (res == -1) {
233+
return;
234+
}
235+
}
236+
}
237+
238+
}

src/main/java/org/apache/commons/io/input/TeeInputStream.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
*
3636
* @version $Id$
3737
* @since 1.4
38+
* @see ObservableInputStream
3839
*/
3940
public class TeeInputStream extends ProxyInputStream {
4041

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
* http://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+
package org.apache.commons.io.input;
18+
19+
import static org.junit.Assert.*;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.security.MessageDigest;
23+
import java.util.Random;
24+
25+
import org.junit.Test;
26+
27+
public class MessageDigestCalculatingInputStreamTest {
28+
public static byte[] generateRandomByteStream(int pSize) {
29+
final byte[] buffer = new byte[pSize];
30+
final Random rnd = new Random();
31+
rnd.nextBytes(buffer);
32+
return buffer;
33+
}
34+
35+
@Test
36+
public void test() throws Exception {
37+
for (int i = 256; i < 8192; i = i*2) {
38+
final byte[] buffer = generateRandomByteStream(i);
39+
final MessageDigest md5Sum = MessageDigest.getInstance("MD5");
40+
final byte[] expect = md5Sum.digest(buffer);
41+
final MessageDigestCalculatingInputStream md5InputStream = new MessageDigestCalculatingInputStream(new ByteArrayInputStream(buffer));
42+
md5InputStream.consume();
43+
final byte[] got = md5InputStream.getMessageDigest().digest();
44+
assertArrayEquals(expect, got);
45+
}
46+
}
47+
48+
}

0 commit comments

Comments
 (0)