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.Files;
26  import java.nio.file.Path;
27  import java.nio.file.StandardOpenOption;
28  import java.nio.file.attribute.FileTime;
29  import java.time.Instant;
30  import java.util.Objects;
31  
32  /**
33   * Caching OutputStream to avoid overwriting a file with
34   * the same content.
35   */
36  public class CachingOutputStream extends OutputStream
37  {
38      private final Path path;
39      private FileChannel channel;
40      private ByteBuffer readBuffer;
41      private ByteBuffer writeBuffer;
42      private boolean modified;
43  
44      public CachingOutputStream( File path ) throws IOException
45      {
46          this( Objects.requireNonNull( path ).toPath() );
47      }
48  
49      public CachingOutputStream( Path path ) throws IOException
50      {
51          this( path, 32 * 1024 );
52      }
53  
54      public CachingOutputStream( Path path, int bufferSize ) throws IOException
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      public void write( int b ) throws IOException
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      public void write( byte[] b ) throws IOException
77      {
78          write( b, 0, b.length );
79      }
80  
81      @Override
82      public void write( byte[] b, int off, int len ) throws IOException
83      {
84          if ( writeBuffer.remaining() < len )
85          {
86              ( ( Buffer ) writeBuffer ).flip();
87              flushBuffer( writeBuffer );
88              ( ( Buffer ) writeBuffer ).clear();
89          }
90          int capacity = 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     public void flush() throws IOException
105     {
106         ( ( Buffer ) writeBuffer ).flip();
107         flushBuffer( writeBuffer );
108         ( ( Buffer ) writeBuffer ).clear();
109         super.flush();
110     }
111 
112     private void flushBuffer( ByteBuffer writeBuffer ) throws IOException
113     {
114         if ( modified )
115         {
116             channel.write( writeBuffer );
117         }
118         else
119         {
120             int len = writeBuffer.remaining();
121             ByteBuffer readBuffer;
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                 int read = 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     public void close() throws IOException
155     {
156         if ( channel.isOpen() )
157         {
158             flush();
159             long position = channel.position();
160             if ( position != channel.size() )
161             {
162                 modified = true;
163                 channel.truncate( position );
164             }
165             channel.close();
166         }
167     }
168 
169     public boolean isModified()
170     {
171         return modified;
172     }
173 }