Skip to content

Commit 49773f1

Browse files
authored
Provides a CachingOuptutStream and a CachingWriter (#184)
Provides a CachingOuptutStream and a CachingWriter
1 parent 0808fe3 commit 49773f1

File tree

4 files changed

+525
-0
lines changed

4 files changed

+525
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
packageorg.codehaus.plexus.util.io;
2+
3+
/*
4+
* Copyright The Codehaus Foundation.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
importjava.io.File;
20+
importjava.io.IOException;
21+
importjava.io.OutputStream;
22+
importjava.nio.Buffer;
23+
importjava.nio.ByteBuffer;
24+
importjava.nio.channels.FileChannel;
25+
importjava.nio.file.Files;
26+
importjava.nio.file.Path;
27+
importjava.nio.file.StandardOpenOption;
28+
importjava.nio.file.attribute.FileTime;
29+
importjava.time.Instant;
30+
importjava.util.Objects;
31+
32+
/**
33+
* Caching OutputStream to avoid overwriting a file with
34+
* the same content.
35+
*/
36+
publicclassCachingOutputStreamextendsOutputStream
37+
{
38+
privatefinalPathpath;
39+
privateFileChannelchannel;
40+
privateByteBufferreadBuffer;
41+
privateByteBufferwriteBuffer;
42+
privatebooleanmodified;
43+
44+
publicCachingOutputStream( Filepath ) throwsIOException
45+
{
46+
this( Objects.requireNonNull( path ).toPath() );
47+
}
48+
49+
publicCachingOutputStream( Pathpath ) throwsIOException
50+
{
51+
this( path, 32 * 1024 );
52+
}
53+
54+
publicCachingOutputStream( Pathpath, intbufferSize ) throwsIOException
55+
{
56+
this.path = Objects.requireNonNull( path );
57+
this.channel = FileChannel.open( path,
58+
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE );
59+
this.readBuffer = ByteBuffer.allocate( bufferSize );
60+
this.writeBuffer = ByteBuffer.allocate( bufferSize );
61+
}
62+
63+
@Override
64+
publicvoidwrite( intb ) throwsIOException
65+
{
66+
if ( writeBuffer.remaining() < 1 )
67+
{
68+
( ( Buffer ) writeBuffer ).flip();
69+
flushBuffer( writeBuffer );
70+
( ( Buffer ) writeBuffer ).clear();
71+
}
72+
writeBuffer.put( ( byte ) b );
73+
}
74+
75+
@Override
76+
publicvoidwrite( byte[] b ) throwsIOException
77+
{
78+
write( b, 0, b.length );
79+
}
80+
81+
@Override
82+
publicvoidwrite( byte[] b, intoff, intlen ) throwsIOException
83+
{
84+
if ( writeBuffer.remaining() < len )
85+
{
86+
( ( Buffer ) writeBuffer ).flip();
87+
flushBuffer( writeBuffer );
88+
( ( Buffer ) writeBuffer ).clear();
89+
}
90+
intcapacity = writeBuffer.capacity();
91+
while ( len >= capacity )
92+
{
93+
flushBuffer( ByteBuffer.wrap( b, off, capacity ) );
94+
off += capacity;
95+
len -= capacity;
96+
}
97+
if ( len > 0 )
98+
{
99+
writeBuffer.put( b, off, len );
100+
}
101+
}
102+
103+
@Override
104+
publicvoidflush() throwsIOException
105+
{
106+
( ( Buffer ) writeBuffer ).flip();
107+
flushBuffer( writeBuffer );
108+
( ( Buffer ) writeBuffer ).clear();
109+
super.flush();
110+
}
111+
112+
privatevoidflushBuffer( ByteBufferwriteBuffer ) throwsIOException
113+
{
114+
if ( modified )
115+
{
116+
channel.write( writeBuffer );
117+
}
118+
else
119+
{
120+
intlen = writeBuffer.remaining();
121+
ByteBufferreadBuffer;
122+
if ( this.readBuffer.capacity() >= len )
123+
{
124+
readBuffer = this.readBuffer;
125+
( ( Buffer ) readBuffer ).clear();
126+
}
127+
else
128+
{
129+
readBuffer = ByteBuffer.allocate( len );
130+
}
131+
while ( len > 0 )
132+
{
133+
intread = channel.read( readBuffer );
134+
if ( read <= 0 )
135+
{
136+
modified = true;
137+
channel.position( channel.position() - readBuffer.position() );
138+
channel.write( writeBuffer );
139+
return;
140+
}
141+
len -= read;
142+
}
143+
( ( Buffer ) readBuffer ).flip();
144+
if ( readBuffer.compareTo( writeBuffer ) != 0 )
145+
{
146+
modified = true;
147+
channel.position( channel.position() - readBuffer.remaining() );
148+
channel.write( writeBuffer );
149+
}
150+
}
151+
}
152+
153+
@Override
154+
publicvoidclose() throwsIOException
155+
{
156+
flush();
157+
longposition = channel.position();
158+
if ( position != channel.size() )
159+
{
160+
if ( !modified )
161+
{
162+
FileTimenow = FileTime.from( Instant.now() );
163+
Files.setLastModifiedTime( path, now );
164+
modified = true;
165+
}
166+
channel.truncate( position );
167+
}
168+
channel.close();
169+
}
170+
171+
publicbooleanisModified()
172+
{
173+
returnmodified;
174+
}
175+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
packageorg.codehaus.plexus.util.io;
2+
3+
/*
4+
* Copyright The Codehaus Foundation.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
importjava.io.File;
20+
importjava.io.IOException;
21+
importjava.io.OutputStreamWriter;
22+
importjava.io.StringWriter;
23+
importjava.nio.charset.Charset;
24+
importjava.nio.file.Files;
25+
importjava.nio.file.Path;
26+
importjava.util.Arrays;
27+
importjava.util.Objects;
28+
29+
/**
30+
* Caching Writer to avoid overwriting a file with
31+
* the same content.
32+
*/
33+
publicclassCachingWriterextendsOutputStreamWriter
34+
{
35+
privatefinalCachingOutputStreamcos;
36+
37+
publicCachingWriter( Filepath, Charsetcharset ) throwsIOException
38+
{
39+
this( Objects.requireNonNull( path ).toPath(), charset );
40+
}
41+
42+
publicCachingWriter( Pathpath, Charsetcharset ) throwsIOException
43+
{
44+
this( path, charset, 32 * 1024 );
45+
}
46+
47+
publicCachingWriter( Pathpath, Charsetcharset, intbufferSize ) throwsIOException
48+
{
49+
this( newCachingOutputStream( path, bufferSize ), charset );
50+
}
51+
52+
privateCachingWriter( CachingOutputStreamoutputStream, Charsetcharset ) throwsIOException
53+
{
54+
super( outputStream, charset );
55+
this.cos = outputStream;
56+
}
57+
58+
publicbooleanisModified()
59+
{
60+
returncos.isModified();
61+
}
62+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
packageorg.codehaus.plexus.util.io;
2+
3+
/*
4+
* Copyright The Codehaus Foundation.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
importjava.io.IOException;
20+
importjava.io.OutputStream;
21+
importjava.nio.charset.StandardCharsets;
22+
importjava.nio.file.Files;
23+
importjava.nio.file.Path;
24+
importjava.nio.file.Paths;
25+
importjava.nio.file.attribute.FileTime;
26+
importjava.util.Objects;
27+
28+
importorg.junit.Before;
29+
importorg.junit.Test;
30+
31+
importstaticorg.junit.Assert.assertArrayEquals;
32+
importstaticorg.junit.Assert.assertEquals;
33+
importstaticorg.junit.Assert.assertFalse;
34+
importstaticorg.junit.Assert.assertNotEquals;
35+
importstaticorg.junit.Assert.assertTrue;
36+
37+
publicclassCachingOutputStreamTest
38+
{
39+
40+
PathtempDir;
41+
PathcheckLastModified;
42+
FileTimelm;
43+
44+
@Before
45+
publicvoidsetup() throwsIOException
46+
{
47+
Pathdir = Paths.get( "target/io" );
48+
Files.createDirectories( dir );
49+
tempDir = Files.createTempDirectory( dir, "temp-" );
50+
checkLastModified = tempDir.resolve( ".check" );
51+
Files.newOutputStream( checkLastModified ).close();
52+
lm = Files.getLastModifiedTime( checkLastModified );
53+
}
54+
55+
privatevoidwaitLastModified() throwsIOException, InterruptedException
56+
{
57+
while ( true )
58+
{
59+
Files.newOutputStream( checkLastModified ).close();
60+
FileTimenlm = Files.getLastModifiedTime( checkLastModified );
61+
if ( !Objects.equals( nlm, lm ) )
62+
{
63+
lm = nlm;
64+
break;
65+
}
66+
Thread.sleep( 10 );
67+
}
68+
}
69+
70+
@Test
71+
publicvoidtestWriteNoExistingFile() throwsIOException, InterruptedException
72+
{
73+
byte[] data = "Hello world!".getBytes( StandardCharsets.UTF_8 );
74+
Pathpath = tempDir.resolve( "file.txt" );
75+
assertFalse( Files.exists( path ) );
76+
77+
try ( CachingOutputStreamcos = newCachingOutputStream( path, 4 ) )
78+
{
79+
cos.write( data );
80+
}
81+
assertTrue( Files.exists( path ) );
82+
byte[] read = Files.readAllBytes( path );
83+
assertArrayEquals( data, read );
84+
FileTimemodified = Files.getLastModifiedTime( path );
85+
86+
waitLastModified();
87+
88+
try ( CachingOutputStreamcos = newCachingOutputStream( path, 4 ) )
89+
{
90+
cos.write( data );
91+
}
92+
assertTrue( Files.exists( path ) );
93+
read = Files.readAllBytes( path );
94+
assertArrayEquals( data, read );
95+
FileTimenewModified = Files.getLastModifiedTime( path );
96+
assertEquals( modified, newModified );
97+
modified = newModified;
98+
99+
waitLastModified();
100+
101+
// write longer data
102+
data = "Good morning!".getBytes( StandardCharsets.UTF_8 );
103+
try ( CachingOutputStreamcos = newCachingOutputStream( path, 4 ) )
104+
{
105+
cos.write( data );
106+
}
107+
assertTrue( Files.exists( path ) );
108+
read = Files.readAllBytes( path );
109+
assertArrayEquals( data, read );
110+
newModified = Files.getLastModifiedTime( path );
111+
assertNotEquals( modified, newModified );
112+
modified = newModified;
113+
114+
waitLastModified();
115+
116+
// different data same size
117+
data = "Good mornong!".getBytes( StandardCharsets.UTF_8 );
118+
try ( CachingOutputStreamcos = newCachingOutputStream( path, 4 ) )
119+
{
120+
cos.write( data );
121+
}
122+
assertTrue( Files.exists( path ) );
123+
read = Files.readAllBytes( path );
124+
assertArrayEquals( data, read );
125+
newModified = Files.getLastModifiedTime( path );
126+
assertNotEquals( modified, newModified );
127+
modified = newModified;
128+
129+
waitLastModified();
130+
131+
// same data but shorter
132+
data = "Good mornon".getBytes( StandardCharsets.UTF_8 );
133+
try ( CachingOutputStreamcos = newCachingOutputStream( path, 4 ) )
134+
{
135+
cos.write( data );
136+
}
137+
assertTrue( Files.exists( path ) );
138+
read = Files.readAllBytes( path );
139+
assertArrayEquals( data, read );
140+
newModified = Files.getLastModifiedTime( path );
141+
assertNotEquals( modified, newModified );
142+
modified = newModified;
143+
}
144+
145+
}

0 commit comments

Comments
(0)