From 1f0fef0dbaf6bc21966fa3dc0aefc9b76c25c365 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 21 Feb 2022 11:56:27 +0100 Subject: [PATCH 1/8] Provides a CachingOuptutStream and a CachingWriter --- .../plexus/util/io/CachingOutputStream.java | 66 ++++++++++++++++++ .../plexus/util/io/CachingWriter.java | 69 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java create mode 100644 src/main/java/org/codehaus/plexus/util/io/CachingWriter.java diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java new file mode 100644 index 00000000..45b8c5cb --- /dev/null +++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java @@ -0,0 +1,66 @@ +package org.codehaus.plexus.util.io; + +/* + * Copyright The Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +/** + * Caching OutputStream to avoid overwriting a file with + * the same content. + */ +public class CachingOutputStream extends ByteArrayOutputStream +{ + private final Path path; + private boolean modified; + + public CachingOutputStream( File path ) + { + this( Objects.requireNonNull( path ).toPath() ); + } + + public CachingOutputStream( Path path ) + { + this.path = Objects.requireNonNull( path ); + } + + @Override + public void close() throws IOException + { + byte[] data = toByteArray(); + if ( Files.exists( path ) && Files.size( path ) == data.length ) + { + byte[] old = Files.readAllBytes( path ); + if ( Arrays.equals( old, data ) ) + { + return; + } + } + Files.write( path, data ); + modified = true; + } + + public boolean isModified() + { + return modified; + } +} diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java b/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java new file mode 100644 index 00000000..63c9df2a --- /dev/null +++ b/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java @@ -0,0 +1,69 @@ +package org.codehaus.plexus.util.io; + +/* + * Copyright The Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +/** + * Caching Writer to avoid overwriting a file with + * the same content. + */ +public class CachingWriter extends StringWriter +{ + private final Path path; + private final Charset charset; + private boolean modified; + + public CachingWriter( File path, Charset charset ) + { + this( Objects.requireNonNull( path ).toPath(), charset ); + } + + public CachingWriter( Path path, Charset charset ) + { + this.path = Objects.requireNonNull( path ); + this.charset = Objects.requireNonNull( charset ); + } + + @Override + public void close() throws IOException + { + byte[] data = getBuffer().toString().getBytes( charset ); + if ( Files.exists( path ) && Files.size( path ) == data.length ) + { + byte[] ba = Files.readAllBytes( path ); + if ( Arrays.equals( data, ba ) ) + { + return; + } + } + Files.write( path, data ); + modified = true; + } + + public boolean isModified() + { + return modified; + } +} From 8ac7811ba2c26b70cfbb0581e949abbb7f446922 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 09:36:26 +0100 Subject: [PATCH 2/8] Better implementation of the caching outputstream / writer --- .../plexus/util/io/CachingOutputStream.java | 128 ++++++++++++++++-- .../plexus/util/io/CachingWriter.java | 37 ++--- .../util/io/CachingOutputStreamTest.java | 119 ++++++++++++++++ .../plexus/util/io/CachingWriterTest.java | 119 ++++++++++++++++ 4 files changed, 367 insertions(+), 36 deletions(-) create mode 100644 src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java create mode 100644 src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java index 45b8c5cb..01e54b6c 100644 --- a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java +++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java @@ -16,47 +16,147 @@ * limitations under the License. */ -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.nio.file.Files; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.file.Path; -import java.util.Arrays; +import java.nio.file.StandardOpenOption; import java.util.Objects; /** * Caching OutputStream to avoid overwriting a file with * the same content. */ -public class CachingOutputStream extends ByteArrayOutputStream +public class CachingOutputStream extends OutputStream { private final Path path; + private FileChannel channel; + private ByteBuffer readBuffer; + private ByteBuffer writeBuffer; private boolean modified; - public CachingOutputStream( File path ) + public CachingOutputStream( File path ) throws IOException { this( Objects.requireNonNull( path ).toPath() ); } - public CachingOutputStream( Path path ) + public CachingOutputStream( Path path ) throws IOException + { + this( path, 32 * 1024 ); + } + + public CachingOutputStream( Path path, int bufferSize ) throws IOException { this.path = Objects.requireNonNull( path ); + this.channel = FileChannel.open( path, + StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE ); + this.readBuffer = ByteBuffer.allocate( bufferSize ); + this.writeBuffer = ByteBuffer.allocate( bufferSize ); } @Override - public void close() throws IOException + public void write( int b ) throws IOException + { + if ( writeBuffer.remaining() < 1 ) + { + writeBuffer.flip(); + flushBuffer( writeBuffer ); + writeBuffer.clear(); + } + writeBuffer.put( ( byte ) b ); + } + + @Override + public void write( byte[] b ) throws IOException + { + write( b, 0, b.length ); + } + + @Override + public void write( byte[] b, int off, int len ) throws IOException + { + if ( writeBuffer.remaining() < len ) + { + writeBuffer.flip(); + flushBuffer( writeBuffer ); + writeBuffer.clear(); + } + int capacity = writeBuffer.capacity(); + while ( len >= capacity ) + { + flushBuffer( ByteBuffer.wrap( b, off, capacity ) ); + off += capacity; + len -= capacity; + } + if ( len > 0 ) + { + writeBuffer.put( b, off, len ); + } + } + + @Override + public void flush() throws IOException + { + writeBuffer.flip(); + flushBuffer( writeBuffer ); + writeBuffer.clear(); + super.flush(); + } + + private void flushBuffer( ByteBuffer writeBuffer ) throws IOException { - byte[] data = toByteArray(); - if ( Files.exists( path ) && Files.size( path ) == data.length ) + if ( modified ) { - byte[] old = Files.readAllBytes( path ); - if ( Arrays.equals( old, data ) ) + channel.write( writeBuffer ); + } + else + { + int len = writeBuffer.remaining(); + ByteBuffer readBuffer; + if ( this.readBuffer.capacity() >= len ) { - return; + readBuffer = this.readBuffer; + readBuffer.clear(); } + else + { + readBuffer = ByteBuffer.allocate( len ); + } + while ( len > 0 ) + { + int read = channel.read( readBuffer ); + if ( read <= 0 ) + { + modified = true; + channel.position( channel.position() - readBuffer.position() ); + channel.write( writeBuffer ); + return; + } + len -= read; + } + readBuffer.flip(); + if ( readBuffer.compareTo( writeBuffer ) != 0 ) + { + modified = true; + channel.position( channel.position() - readBuffer.remaining() ); + channel.write( writeBuffer ); + } + } + } + + @Override + public void close() throws IOException + { + flush(); + long position = channel.position(); + if ( position != channel.size() ) + { + modified = true; + channel.truncate( position ); } - Files.write( path, data ); - modified = true; + channel.close(); } public boolean isModified() diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java b/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java index 63c9df2a..23cc4411 100644 --- a/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java +++ b/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.file.Files; @@ -29,41 +30,33 @@ * Caching Writer to avoid overwriting a file with * the same content. */ -public class CachingWriter extends StringWriter +public class CachingWriter extends OutputStreamWriter { - private final Path path; - private final Charset charset; - private boolean modified; + private final CachingOutputStream cos; - public CachingWriter( File path, Charset charset ) + public CachingWriter( File path, Charset charset ) throws IOException { this( Objects.requireNonNull( path ).toPath(), charset ); } - public CachingWriter( Path path, Charset charset ) + public CachingWriter( Path path, Charset charset ) throws IOException { - this.path = Objects.requireNonNull( path ); - this.charset = Objects.requireNonNull( charset ); + this( path, charset, 32 * 1024 ); } - @Override - public void close() throws IOException + public CachingWriter( Path path, Charset charset, int bufferSize ) throws IOException { - byte[] data = getBuffer().toString().getBytes( charset ); - if ( Files.exists( path ) && Files.size( path ) == data.length ) - { - byte[] ba = Files.readAllBytes( path ); - if ( Arrays.equals( data, ba ) ) - { - return; - } - } - Files.write( path, data ); - modified = true; + this( new CachingOutputStream( path, bufferSize ), charset ); + } + + private CachingWriter( CachingOutputStream outputStream, Charset charset ) throws IOException + { + super( outputStream, charset ); + this.cos = outputStream; } public boolean isModified() { - return modified; + return cos.isModified(); } } diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java new file mode 100644 index 00000000..24a3984b --- /dev/null +++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java @@ -0,0 +1,119 @@ +package org.codehaus.plexus.util.io; + +/* + * Copyright The Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class CachingOutputStreamTest +{ + + Path tempDir; + + @Before + public void setup() throws IOException + { + Path dir = Paths.get( "target/io" ); + Files.createDirectories( dir ); + tempDir = Files.createTempDirectory( dir, "temp-" ); + } + + @Test + public void testWriteNoExistingFile() throws IOException, InterruptedException + { + byte[] data = "Hello world!".getBytes( StandardCharsets.UTF_8 ); + Path path = tempDir.resolve( "file.txt" ); + assertFalse( Files.exists( path ) ); + + try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + byte[] read = Files.readAllBytes( path ); + assertArrayEquals( data, read ); + FileTime modified = Files.getLastModifiedTime( path ); + + Thread.sleep( 100 ); + + try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = Files.readAllBytes( path ); + assertArrayEquals( data, read ); + FileTime newModified = Files.getLastModifiedTime( path ); + assertEquals( modified, newModified ); + + Thread.sleep( 100 ); + + // write longer data + data = "Good morning!".getBytes( StandardCharsets.UTF_8 ); + try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = Files.readAllBytes( path ); + assertArrayEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + + Thread.sleep( 100 ); + + // different data same size + data = "Good mornong!".getBytes( StandardCharsets.UTF_8 ); + try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = Files.readAllBytes( path ); + assertArrayEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + + // same data but shorter + data = "Good mornon".getBytes( StandardCharsets.UTF_8 ); + try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = Files.readAllBytes( path ); + assertArrayEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + } +} diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java new file mode 100644 index 00000000..b6e5257c --- /dev/null +++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java @@ -0,0 +1,119 @@ +package org.codehaus.plexus.util.io; + +/* + * Copyright The Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class CachingWriterTest +{ + + Path tempDir; + + @Before + public void setup() throws IOException + { + Path dir = Paths.get( "target/io" ); + Files.createDirectories( dir ); + tempDir = Files.createTempDirectory( dir, "temp-" ); + } + + @Test + public void testWriteNoExistingFile() throws IOException, InterruptedException + { + String data = "Hello world!"; + Path path = tempDir.resolve( "file.txt" ); + assertFalse( Files.exists( path ) ); + + try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + String read = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 ); + assertEquals( data, read ); + FileTime modified = Files.getLastModifiedTime( path ); + + Thread.sleep( 100 ); + + try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 ); + assertEquals( data, read ); + FileTime newModified = Files.getLastModifiedTime( path ); + assertEquals( modified, newModified ); + + Thread.sleep( 100 ); + + // write longer data + data = "Good morning!"; + try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 ); + assertEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + + Thread.sleep( 100 ); + + // different data same size + data = "Good mornong!"; + try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 ); + assertEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + + // same data but shorter + data = "Good mornon"; + try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) + { + cos.write( data ); + } + assertTrue( Files.exists( path ) ); + read = new String( Files.readAllBytes( path ), StandardCharsets.UTF_8 ); + assertEquals( data, read ); + newModified = Files.getLastModifiedTime( path ); + assertNotEquals( modified, newModified ); + modified = newModified; + } +} From f70e38b85d12ec7e1bc3557170cf44634cb863c0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 09:41:57 +0100 Subject: [PATCH 3/8] Fix problem with jdk8 / jdk9 signatures --- .../plexus/util/io/CachingOutputStream.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java index 01e54b6c..1241873a 100644 --- a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java +++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; @@ -61,9 +62,9 @@ public void write( int b ) throws IOException { if ( writeBuffer.remaining() < 1 ) { - writeBuffer.flip(); + ( ( Buffer ) writeBuffer ).flip(); flushBuffer( writeBuffer ); - writeBuffer.clear(); + ( ( Buffer ) writeBuffer ).clear(); } writeBuffer.put( ( byte ) b ); } @@ -79,9 +80,9 @@ public void write( byte[] b, int off, int len ) throws IOException { if ( writeBuffer.remaining() < len ) { - writeBuffer.flip(); + ( ( Buffer ) writeBuffer ).flip(); flushBuffer( writeBuffer ); - writeBuffer.clear(); + ( ( Buffer ) writeBuffer ).clear(); } int capacity = writeBuffer.capacity(); while ( len >= capacity ) @@ -99,9 +100,9 @@ public void write( byte[] b, int off, int len ) throws IOException @Override public void flush() throws IOException { - writeBuffer.flip(); + ( ( Buffer ) writeBuffer ).flip(); flushBuffer( writeBuffer ); - writeBuffer.clear(); + ( ( Buffer ) writeBuffer ).clear(); super.flush(); } @@ -118,7 +119,7 @@ private void flushBuffer( ByteBuffer writeBuffer ) throws IOException if ( this.readBuffer.capacity() >= len ) { readBuffer = this.readBuffer; - readBuffer.clear(); + ( ( Buffer ) readBuffer ).clear(); } else { @@ -136,7 +137,7 @@ private void flushBuffer( ByteBuffer writeBuffer ) throws IOException } len -= read; } - readBuffer.flip(); + ( ( Buffer ) readBuffer ).flip(); if ( readBuffer.compareTo( writeBuffer ) != 0 ) { modified = true; From 566c4f4ecfd713119d725d994abe27b022046eed Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 09:43:15 +0100 Subject: [PATCH 4/8] Fix test --- .../org/codehaus/plexus/util/io/CachingOutputStreamTest.java | 2 ++ .../java/org/codehaus/plexus/util/io/CachingWriterTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java index 24a3984b..b0c03de3 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java @@ -103,6 +103,8 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; + Thread.sleep( 100 ); + // same data but shorter data = "Good mornon".getBytes( StandardCharsets.UTF_8 ); try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java index b6e5257c..d5bb7c9e 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java @@ -103,6 +103,8 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; + Thread.sleep( 100 ); + // same data but shorter data = "Good mornon"; try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) From 0751e0fe77e94e7283d17e44b484e968f831ceb2 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 09:49:45 +0100 Subject: [PATCH 5/8] Raise the sleep time to make sure tests do pass --- .../codehaus/plexus/util/io/CachingOutputStreamTest.java | 8 ++++---- .../org/codehaus/plexus/util/io/CachingWriterTest.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java index b0c03de3..6ffdf15a 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java @@ -61,7 +61,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertArrayEquals( data, read ); FileTime modified = Files.getLastModifiedTime( path ); - Thread.sleep( 100 ); + Thread.sleep( 250 ); try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) { @@ -73,7 +73,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException FileTime newModified = Files.getLastModifiedTime( path ); assertEquals( modified, newModified ); - Thread.sleep( 100 ); + Thread.sleep( 250 ); // write longer data data = "Good morning!".getBytes( StandardCharsets.UTF_8 ); @@ -88,7 +88,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 100 ); + Thread.sleep( 250 ); // different data same size data = "Good mornong!".getBytes( StandardCharsets.UTF_8 ); @@ -103,7 +103,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 100 ); + Thread.sleep( 250 ); // same data but shorter data = "Good mornon".getBytes( StandardCharsets.UTF_8 ); diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java index d5bb7c9e..3f6ee7b4 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java @@ -61,7 +61,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertEquals( data, read ); FileTime modified = Files.getLastModifiedTime( path ); - Thread.sleep( 100 ); + Thread.sleep( 250 ); try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) { @@ -73,7 +73,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException FileTime newModified = Files.getLastModifiedTime( path ); assertEquals( modified, newModified ); - Thread.sleep( 100 ); + Thread.sleep( 250 ); // write longer data data = "Good morning!"; @@ -88,7 +88,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 100 ); + Thread.sleep( 250 ); // different data same size data = "Good mornong!"; @@ -103,7 +103,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 100 ); + Thread.sleep( 250 ); // same data but shorter data = "Good mornon"; From 6cc30847554f9cfd944c32c4979ef46aa750c6ff Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 10:06:33 +0100 Subject: [PATCH 6/8] Fix tests --- .../org/codehaus/plexus/util/io/CachingOutputStreamTest.java | 1 + src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java index 6ffdf15a..6153e9e9 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java @@ -72,6 +72,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertArrayEquals( data, read ); FileTime newModified = Files.getLastModifiedTime( path ); assertEquals( modified, newModified ); + modified = newModified; Thread.sleep( 250 ); diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java index 3f6ee7b4..8ca76a6b 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java @@ -72,6 +72,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertEquals( data, read ); FileTime newModified = Files.getLastModifiedTime( path ); assertEquals( modified, newModified ); + modified = newModified; Thread.sleep( 250 ); From 4473d2494d1022032ddf75d8dcaa13020238174c Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 22 Feb 2022 11:17:10 +0100 Subject: [PATCH 7/8] Improve tests --- .../util/io/CachingOutputStreamTest.java | 31 ++++++++++++++++--- .../plexus/util/io/CachingWriterTest.java | 29 ++++++++++++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java index 6153e9e9..3c329ea9 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java @@ -17,11 +17,13 @@ */ import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; +import java.util.Objects; import org.junit.Before; import org.junit.Test; @@ -36,6 +38,8 @@ public class CachingOutputStreamTest { Path tempDir; + Path checkLastModified; + FileTime lm; @Before public void setup() throws IOException @@ -43,6 +47,24 @@ public void setup() throws IOException Path dir = Paths.get( "target/io" ); Files.createDirectories( dir ); tempDir = Files.createTempDirectory( dir, "temp-" ); + checkLastModified = tempDir.resolve( ".check" ); + Files.newOutputStream( checkLastModified ).close(); + lm = Files.getLastModifiedTime( checkLastModified ); + } + + private void waitLastModified() throws IOException, InterruptedException + { + while ( true ) + { + Files.newOutputStream( checkLastModified ).close(); + FileTime nlm = Files.getLastModifiedTime( checkLastModified ); + if ( !Objects.equals( nlm, lm ) ) + { + lm = nlm; + break; + } + Thread.sleep( 10 ); + } } @Test @@ -61,7 +83,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertArrayEquals( data, read ); FileTime modified = Files.getLastModifiedTime( path ); - Thread.sleep( 250 ); + waitLastModified(); try ( CachingOutputStream cos = new CachingOutputStream( path, 4 ) ) { @@ -74,7 +96,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // write longer data data = "Good morning!".getBytes( StandardCharsets.UTF_8 ); @@ -89,7 +111,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // different data same size data = "Good mornong!".getBytes( StandardCharsets.UTF_8 ); @@ -104,7 +126,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // same data but shorter data = "Good mornon".getBytes( StandardCharsets.UTF_8 ); @@ -119,4 +141,5 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; } + } diff --git a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java index 8ca76a6b..f5b95903 100644 --- a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java +++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileTime; +import java.util.Objects; import org.junit.Before; import org.junit.Test; @@ -36,6 +37,8 @@ public class CachingWriterTest { Path tempDir; + Path checkLastModified; + FileTime lm; @Before public void setup() throws IOException @@ -43,6 +46,24 @@ public void setup() throws IOException Path dir = Paths.get( "target/io" ); Files.createDirectories( dir ); tempDir = Files.createTempDirectory( dir, "temp-" ); + checkLastModified = tempDir.resolve( ".check" ); + Files.newOutputStream( checkLastModified ).close(); + lm = Files.getLastModifiedTime( checkLastModified ); + } + + private void waitLastModified() throws IOException, InterruptedException + { + while ( true ) + { + Files.newOutputStream( checkLastModified ).close(); + FileTime nlm = Files.getLastModifiedTime( checkLastModified ); + if ( !Objects.equals( nlm, lm ) ) + { + lm = nlm; + break; + } + Thread.sleep( 10 ); + } } @Test @@ -61,7 +82,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertEquals( data, read ); FileTime modified = Files.getLastModifiedTime( path ); - Thread.sleep( 250 ); + waitLastModified(); try ( CachingWriter cos = new CachingWriter( path, StandardCharsets.UTF_8, 4 ) ) { @@ -74,7 +95,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // write longer data data = "Good morning!"; @@ -89,7 +110,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // different data same size data = "Good mornong!"; @@ -104,7 +125,7 @@ public void testWriteNoExistingFile() throws IOException, InterruptedException assertNotEquals( modified, newModified ); modified = newModified; - Thread.sleep( 250 ); + waitLastModified(); // same data but shorter data = "Good mornon"; From 0c03b4620ce7774adb8c4c2f5bf144d09fb063a8 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 25 Apr 2022 15:18:24 +0200 Subject: [PATCH 8/8] Fix FileChannel#truncate not updating the last modified time --- .../codehaus/plexus/util/io/CachingOutputStream.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java index 1241873a..521d5373 100644 --- a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java +++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java @@ -22,8 +22,11 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; import java.util.Objects; /** @@ -154,7 +157,12 @@ public void close() throws IOException long position = channel.position(); if ( position != channel.size() ) { - modified = true; + if ( !modified ) + { + FileTime now = FileTime.from( Instant.now() ); + Files.setLastModifiedTime( path, now ); + modified = true; + } channel.truncate( position ); } channel.close();