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