Skip to content

Commit 4d3ad0a

Browse files
committed
Add a caching output stream
1 parent b4b1fb6 commit 4d3ad0a

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.sonatype.plexus.build.incremental;
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+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.nio.Buffer;
23+
import java.nio.ByteBuffer;
24+
import java.nio.channels.FileChannel;
25+
import java.nio.file.Path;
26+
import java.nio.file.StandardOpenOption;
27+
28+
/**
29+
* Caching OutputStream to avoid overwriting a file with
30+
* the same content.
31+
*/
32+
class CachingOutputStream extends OutputStream {
33+
private final Path path;
34+
private FileChannel channel;
35+
private ByteBuffer readBuffer;
36+
private ByteBuffer writeBuffer;
37+
private boolean modified;
38+
39+
public CachingOutputStream(File path) throws IOException {
40+
this(requireNonNull(path).toPath());
41+
}
42+
43+
public CachingOutputStream(Path path) throws IOException {
44+
this(path, 32 * 1024);
45+
}
46+
47+
public CachingOutputStream(Path path, int bufferSize) throws IOException {
48+
this.path = requireNonNull(path);
49+
this.channel =
50+
FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
51+
this.readBuffer = ByteBuffer.allocate(bufferSize);
52+
this.writeBuffer = ByteBuffer.allocate(bufferSize);
53+
}
54+
55+
@Override
56+
public void write(int b) throws IOException {
57+
if (writeBuffer.remaining() < 1) {
58+
((Buffer) writeBuffer).flip();
59+
flushBuffer(writeBuffer);
60+
((Buffer) writeBuffer).clear();
61+
}
62+
writeBuffer.put((byte) b);
63+
}
64+
65+
@Override
66+
public void write(byte[] b) throws IOException {
67+
write(b, 0, b.length);
68+
}
69+
70+
@Override
71+
public void write(byte[] b, int off, int len) throws IOException {
72+
if (writeBuffer.remaining() < len) {
73+
((Buffer) writeBuffer).flip();
74+
flushBuffer(writeBuffer);
75+
((Buffer) writeBuffer).clear();
76+
}
77+
int capacity = writeBuffer.capacity();
78+
while (len >= capacity) {
79+
flushBuffer(ByteBuffer.wrap(b, off, capacity));
80+
off += capacity;
81+
len -= capacity;
82+
}
83+
if (len > 0) {
84+
writeBuffer.put(b, off, len);
85+
}
86+
}
87+
88+
@Override
89+
public void flush() throws IOException {
90+
((Buffer) writeBuffer).flip();
91+
flushBuffer(writeBuffer);
92+
((Buffer) writeBuffer).clear();
93+
super.flush();
94+
}
95+
96+
private void flushBuffer(ByteBuffer writeBuffer) throws IOException {
97+
if (modified) {
98+
channel.write(writeBuffer);
99+
} else {
100+
int len = writeBuffer.remaining();
101+
ByteBuffer readBuffer;
102+
if (this.readBuffer.capacity() >= len) {
103+
readBuffer = this.readBuffer;
104+
((Buffer) readBuffer).clear();
105+
readBuffer.limit(len);
106+
} else {
107+
readBuffer = ByteBuffer.allocate(len);
108+
}
109+
while (len > 0) {
110+
int read = channel.read(readBuffer);
111+
if (read <= 0) {
112+
modified = true;
113+
channel.position(channel.position() - readBuffer.position());
114+
channel.write(writeBuffer);
115+
return;
116+
}
117+
len -= read;
118+
}
119+
((Buffer) readBuffer).flip();
120+
if (readBuffer.compareTo(writeBuffer) != 0) {
121+
modified = true;
122+
channel.position(channel.position() - readBuffer.remaining());
123+
channel.write(writeBuffer);
124+
}
125+
}
126+
}
127+
128+
@Override
129+
public void close() throws IOException {
130+
if (channel.isOpen()) {
131+
flush();
132+
long position = channel.position();
133+
if (position != channel.size()) {
134+
modified = true;
135+
channel.truncate(position);
136+
}
137+
channel.close();
138+
}
139+
}
140+
141+
public boolean isModified() {
142+
return modified;
143+
}
144+
145+
private static <T> T requireNonNull(T t) {
146+
if (t == null) {
147+
throw new NullPointerException();
148+
}
149+
return t;
150+
}
151+
}

src/main/java/org/sonatype/plexus/build/incremental/DefaultBuildContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public boolean hasDelta(List relpaths) {
5151
}
5252

5353
public OutputStream newFileOutputStream(File file) throws IOException {
54-
return new FileOutputStream(file);
54+
return new CachingOutputStream(file);
5555
}
5656

5757
public Scanner newScanner(File basedir) {

0 commit comments

Comments
 (0)