From 3896620b4fbf031b1b02b4d46b328c5839a231da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotrek=20=C5=BBygie=C5=82o?=
Date: Thu, 24 Feb 2022 12:28:56 +0100
Subject: [PATCH 01/12] Use (already) precalculated value
---
.../org/codehaus/plexus/util/xml/pull/MXSerializer.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/main/java/org/codehaus/plexus/util/xml/pull/MXSerializer.java b/src/main/java/org/codehaus/plexus/util/xml/pull/MXSerializer.java
index cd1edc59..c69db3f4 100644
--- a/src/main/java/org/codehaus/plexus/util/xml/pull/MXSerializer.java
+++ b/src/main/java/org/codehaus/plexus/util/xml/pull/MXSerializer.java
@@ -460,15 +460,15 @@ public void startDocument( String encoding, Boolean standalone )
if ( encoding != null )
{
out.write( " encoding=" );
- out.write( attributeUseApostrophe ? '\'' : '"' );
+ out.write( apos );
out.write( encoding );
- out.write( attributeUseApostrophe ? '\'' : '"' );
+ out.write( apos );
// out.write('\'');
}
if ( standalone != null )
{
out.write( " standalone=" );
- out.write( attributeUseApostrophe ? '\'' : '"' );
+ out.write( apos );
if ( standalone )
{
out.write( "yes" );
@@ -477,7 +477,7 @@ public void startDocument( String encoding, Boolean standalone )
{
out.write( "no" );
}
- out.write( attributeUseApostrophe ? '\'' : '"' );
+ out.write( apos );
// if(standalone.booleanValue()) {
// out.write(" standalone='yes'");
// } else {
From b99f7c0e46d8b08d63afa4c2c7d274ebf5ebcc94 Mon Sep 17 00:00:00 2001
From: Gabriel Belingueres
Date: Sat, 2 Apr 2022 12:31:39 -0300
Subject: [PATCH 02/12] Fixed regressions:
* #163 - new case: Don't assume UTF8 as default, to allow parsing from String.
* #194 - Incorrect getText() after parsing the DOCDECL section.
* Added tests exercising other regressions exposed while fixing this issues.
---
.../plexus/util/xml/pull/MXParser.java | 157 ++++--
.../plexus/util/xml/pull/MXParserTest.java | 495 ++++++++++++++++++
src/test/resources/xml/test-entities-dos.xml | 6 +
.../xml/test-entities-in-attr-dos.xml | 9 +
.../resources/xml/test-entities-in-attr.xml | 9 +
src/test/resources/xml/test-entities.xml | 6 +
6 files changed, 637 insertions(+), 45 deletions(-)
create mode 100644 src/test/resources/xml/test-entities-dos.xml
create mode 100644 src/test/resources/xml/test-entities-in-attr-dos.xml
create mode 100644 src/test/resources/xml/test-entities-in-attr.xml
create mode 100644 src/test/resources/xml/test-entities.xml
diff --git a/src/main/java/org/codehaus/plexus/util/xml/pull/MXParser.java b/src/main/java/org/codehaus/plexus/util/xml/pull/MXParser.java
index 3874f572..e21b66cb 100644
--- a/src/main/java/org/codehaus/plexus/util/xml/pull/MXParser.java
+++ b/src/main/java/org/codehaus/plexus/util/xml/pull/MXParser.java
@@ -124,7 +124,7 @@ private String newStringIntern( char[] cbuf, int off, int len )
// private String elValue[];
private int elNamespaceCount[];
- private String fileEncoding = "UTF8";
+ private String fileEncoding = null;
/**
* Make sure that we have enough space to keep element stack if passed size. It will always create one additional
@@ -587,8 +587,8 @@ else if ( FEATURE_XML_ROUNDTRIP.equals( name ) )
}
}
- /**
- * Unknown properties are always returned as false
+ /**
+ * Unknown properties are always returned as false
*/
@Override
public boolean getFeature( String name )
@@ -1596,11 +1596,11 @@ else if ( ch == '&' )
}
final int oldStart = posStart + bufAbsoluteStart;
final int oldEnd = posEnd + bufAbsoluteStart;
- final char[] resolvedEntity = parseEntityRef();
+ parseEntityRef();
if ( tokenize )
return eventType = ENTITY_REF;
// check if replacement text can be resolved !!!
- if ( resolvedEntity == null )
+ if ( resolvedEntityRefCharBuf == BUF_NOT_RESOLVED )
{
if ( entityRefName == null )
{
@@ -1628,7 +1628,7 @@ else if ( ch == '&' )
}
// assert usePC == true;
// write into PC replacement text - do merge for replacement text!!!!
- for ( char aResolvedEntity : resolvedEntity )
+ for ( char aResolvedEntity : resolvedEntityRefCharBuf )
{
if ( pcEnd >= pc.length )
{
@@ -2675,9 +2675,28 @@ else if ( ch == '\t' || ch == '\n' || ch == '\r' )
return ch;
}
- private char[] charRefOneCharBuf = new char[1];
+ // state representing that no entity ref have been resolved
+ private static final char[] BUF_NOT_RESOLVED = new char[0];
+
+ // predefined entity refs
+ private static final char[] BUF_LT = new char[] { '<' };
+ private static final char[] BUF_AMP = new char[] { '&' };
+ private static final char[] BUF_GT = new char[] { '>' };
+ private static final char[] BUF_APO = new char[] { '\'' };
+ private static final char[] BUF_QUOT = new char[] { '"' };
- private char[] parseEntityRef()
+ private char[] resolvedEntityRefCharBuf = BUF_NOT_RESOLVED;
+
+ /**
+ * parse Entity Ref, either a character entity or one of the predefined name entities.
+ *
+ * @return the length of the valid found character reference, which may be one of the predefined character reference
+ * names (resolvedEntityRefCharBuf contains the replaced chars). Returns the length of the not found entity
+ * name, otherwise.
+ * @throws XmlPullParserException if invalid XML is detected.
+ * @throws IOException if an I/O error is found.
+ */
+ private int parseCharOrPredefinedEntityRef()
throws XmlPullParserException, IOException
{
// entity reference http://www.w3.org/TR/2000/REC-xml-20001006#NT-Reference
@@ -2686,6 +2705,8 @@ private char[] parseEntityRef()
// ASSUMPTION just after &
entityRefName = null;
posStart = pos;
+ int len = 0;
+ resolvedEntityRefCharBuf = BUF_NOT_RESOLVED;
char ch = more();
if ( ch == '#' )
{
@@ -2750,7 +2771,6 @@ else if ( ch >= 'A' && ch <= 'F' )
ch = more();
}
}
- posEnd = pos - 1;
boolean isValidCodePoint = true;
try
@@ -2759,7 +2779,7 @@ else if ( ch >= 'A' && ch <= 'F' )
isValidCodePoint = isValidCodePoint( codePoint );
if ( isValidCodePoint )
{
- charRefOneCharBuf = Character.toChars( codePoint );
+ resolvedEntityRefCharBuf = Character.toChars( codePoint );
}
}
catch ( IllegalArgumentException e )
@@ -2775,14 +2795,14 @@ else if ( ch >= 'A' && ch <= 'F' )
if ( tokenize )
{
- text = newString( charRefOneCharBuf, 0, charRefOneCharBuf.length );
+ text = newString( resolvedEntityRefCharBuf, 0, resolvedEntityRefCharBuf.length );
}
- return charRefOneCharBuf;
+ len = resolvedEntityRefCharBuf.length;
}
else
{
// [68] EntityRef ::= '&' Name ';'
- // scan anem until ;
+ // scan name until ;
if ( !isNameStartChar( ch ) )
{
throw new XmlPullParserException( "entity reference names can not start with character '"
@@ -2801,17 +2821,15 @@ else if ( ch >= 'A' && ch <= 'F' )
+ printable( ch ) + "'", this, null );
}
}
- posEnd = pos - 1;
// determine what name maps to
- final int len = posEnd - posStart;
+ len = ( pos - 1 ) - posStart;
if ( len == 2 && buf[posStart] == 'l' && buf[posStart + 1] == 't' )
{
if ( tokenize )
{
text = "<";
}
- charRefOneCharBuf[0] = '<';
- return charRefOneCharBuf;
+ resolvedEntityRefCharBuf = BUF_LT;
// if(paramPC || isParserTokenizing) {
// if(pcEnd >= pc.length) ensurePC();
// pc[pcEnd++] = '<';
@@ -2823,8 +2841,7 @@ else if ( len == 3 && buf[posStart] == 'a' && buf[posStart + 1] == 'm' && buf[po
{
text = "&";
}
- charRefOneCharBuf[0] = '&';
- return charRefOneCharBuf;
+ resolvedEntityRefCharBuf = BUF_AMP;
}
else if ( len == 2 && buf[posStart] == 'g' && buf[posStart + 1] == 't' )
{
@@ -2832,8 +2849,7 @@ else if ( len == 2 && buf[posStart] == 'g' && buf[posStart + 1] == 't' )
{
text = ">";
}
- charRefOneCharBuf[0] = '>';
- return charRefOneCharBuf;
+ resolvedEntityRefCharBuf = BUF_GT;
}
else if ( len == 4 && buf[posStart] == 'a' && buf[posStart + 1] == 'p' && buf[posStart + 2] == 'o'
&& buf[posStart + 3] == 's' )
@@ -2842,8 +2858,7 @@ else if ( len == 4 && buf[posStart] == 'a' && buf[posStart + 1] == 'p' && buf[po
{
text = "'";
}
- charRefOneCharBuf[0] = '\'';
- return charRefOneCharBuf;
+ resolvedEntityRefCharBuf = BUF_APO;
}
else if ( len == 4 && buf[posStart] == 'q' && buf[posStart + 1] == 'u' && buf[posStart + 2] == 'o'
&& buf[posStart + 3] == 't' )
@@ -2852,25 +2867,65 @@ else if ( len == 4 && buf[posStart] == 'q' && buf[posStart + 1] == 'u' && buf[po
{
text = "\"";
}
- charRefOneCharBuf[0] = '"';
- return charRefOneCharBuf;
- }
- else
- {
- final char[] result = lookuEntityReplacement( len );
- if ( result != null )
- {
- return result;
- }
+ resolvedEntityRefCharBuf = BUF_QUOT;
}
- if ( tokenize )
- text = null;
- return null;
}
+
+ posEnd = pos;
+
+ return len;
+ }
+
+ /**
+ * Parse an entity reference inside the DOCDECL section.
+ *
+ * @throws XmlPullParserException if invalid XML is detected.
+ * @throws IOException if an I/O error is found.
+ */
+ private void parseEntityRefInDocDecl()
+ throws XmlPullParserException, IOException
+ {
+ parseCharOrPredefinedEntityRef();
+ if (usePC) {
+ posStart--; // include in PC the starting '&' of the entity
+ joinPC();
+ }
+
+ if ( resolvedEntityRefCharBuf != BUF_NOT_RESOLVED )
+ return;
+ if ( tokenize )
+ text = null;
+ }
+
+ /**
+ * Parse an entity reference inside a tag or attribute.
+ *
+ * @throws XmlPullParserException if invalid XML is detected.
+ * @throws IOException if an I/O error is found.
+ */
+ private void parseEntityRef()
+ throws XmlPullParserException, IOException
+ {
+ final int len = parseCharOrPredefinedEntityRef();
+
+ posEnd--; // don't involve the final ';' from the entity in the search
+
+ if ( resolvedEntityRefCharBuf != BUF_NOT_RESOLVED ) {
+ return;
+ }
+
+ resolvedEntityRefCharBuf = lookuEntityReplacement( len );
+ if ( resolvedEntityRefCharBuf != BUF_NOT_RESOLVED )
+ {
+ return;
+ }
+ if ( tokenize )
+ text = null;
}
/**
- * Check if the provided parameter is a valid Char, according to: {@link https://www.w3.org/TR/REC-xml/#NT-Char}
+ * Check if the provided parameter is a valid Char. According to
+ * https://www.w3.org/TR/REC-xml/#NT-Char
*
* @param codePoint the numeric value to check
* @return true if it is a valid numeric character reference. False otherwise.
@@ -2883,8 +2938,6 @@ private static boolean isValidCodePoint( int codePoint )
}
private char[] lookuEntityReplacement( int entityNameLen )
- throws XmlPullParserException, IOException
-
{
if ( !allStringsInterned )
{
@@ -2919,7 +2972,7 @@ private char[] lookuEntityReplacement( int entityNameLen )
}
}
}
- return null;
+ return BUF_NOT_RESOLVED;
}
private void parseComment()
@@ -2977,7 +3030,7 @@ else if (isValidCodePoint( ch ))
}
else
{
- throw new XmlPullParserException( "Illegal character 0x" + Integer.toHexString(((int) ch)) + " found in comment", this, null );
+ throw new XmlPullParserException( "Illegal character 0x" + Integer.toHexString(ch) + " found in comment", this, null );
}
if ( normalizeIgnorableWS )
{
@@ -3484,7 +3537,8 @@ else if ( ch == '>' && bracketLevel == 0 )
break;
else if ( ch == '&' )
{
- extractEntityRef();
+ extractEntityRefInDocDecl();
+ continue;
}
if ( normalizeIgnorableWS )
{
@@ -3536,6 +3590,19 @@ else if ( ch == '\n' )
}
posEnd = pos - 1;
+ text = null;
+ }
+
+ private void extractEntityRefInDocDecl()
+ throws XmlPullParserException, IOException
+ {
+ // extractEntityRef
+ posEnd = pos - 1;
+
+ int prevPosStart = posStart;
+ parseEntityRefInDocDecl();
+
+ posStart = prevPosStart;
}
private void extractEntityRef()
@@ -3559,9 +3626,9 @@ private void extractEntityRef()
}
// assert usePC == true;
- final char[] resolvedEntity = parseEntityRef();
+ parseEntityRef();
// check if replacement text can be resolved !!!
- if ( resolvedEntity == null )
+ if ( resolvedEntityRefCharBuf == BUF_NOT_RESOLVED )
{
if ( entityRefName == null )
{
@@ -3571,7 +3638,7 @@ private void extractEntityRef()
+ "'", this, null );
}
// write into PC replacement text - do merge for replacement text!!!!
- for ( char aResolvedEntity : resolvedEntity )
+ for ( char aResolvedEntity : resolvedEntityRefCharBuf )
{
if ( pcEnd >= pc.length )
{
diff --git a/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java b/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
index e0be666a..6fc6e9c6 100644
--- a/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
+++ b/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
@@ -17,6 +17,7 @@
*/
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -29,6 +30,7 @@
import java.nio.file.Files;
import java.nio.file.Paths;
+import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.junit.Test;
@@ -898,4 +900,497 @@ public void testEncodingISO_8859_1_setInputStream()
}
}
+ /**
+ * Issue 163: https://github.com/codehaus-plexus/plexus-utils/issues/163
+ *
+ * Another case of bug #163: File encoding information is lost after the input file is copied to a String.
+ *
+ * @throws IOException if IO error.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testEncodingISO_8859_1setStringReader()
+ throws IOException
+ {
+ try ( Reader reader =
+ ReaderFactory.newXmlReader( new File( "src/test/resources/xml", "test-encoding-ISO-8859-1.xml" ) ) )
+ {
+ MXParser parser = new MXParser();
+ String xmlFileContents = IOUtil.toString( reader );
+ parser.setInput( new StringReader( xmlFileContents ) );
+ while ( parser.nextToken() != XmlPullParser.END_DOCUMENT )
+ ;
+ assertTrue( true );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not raise exception: " + e );
+ }
+ }
+
+ /**
+ *
+ * Test custom Entity not found.
+ *
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws java.lang.Exception if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testCustomEntityNotFoundInText()
+ throws Exception
+ {
+ MXParser parser = new MXParser();
+
+ String input = "&otherentity;";
+ parser.setInput( new StringReader( input ) );
+ parser.defineEntityReplacementText( "myentity", "replacement" );
+
+ try
+ {
+ assertEquals( XmlPullParser.START_TAG, parser.next() );
+ assertEquals( XmlPullParser.TEXT, parser.next() );
+ fail( "should raise exception" );
+ }
+ catch ( XmlPullParserException e )
+ {
+ assertTrue( e.getMessage().contains( "could not resolve entity named 'otherentity' (position: START_TAG seen &otherentity;... @1:19)" ) );
+ assertEquals( XmlPullParser.START_TAG, parser.getEventType() ); // not an ENTITY_REF
+ assertEquals( "otherentity", parser.getText() );
+ }
+ }
+
+ /**
+ *
+ * Test custom Entity not found, with tokenize.
+ *
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws java.lang.Exception if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testCustomEntityNotFoundInTextTokenize()
+ throws Exception
+ {
+ MXParser parser = new MXParser();
+
+ String input = "&otherentity;";
+ parser.setInput( new StringReader( input ) );
+ parser.defineEntityReplacementText( "myentity", "replacement" );
+
+ try
+ {
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertNull( parser.getText() );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not throw exception if tokenize" );
+ }
+ }
+
+ /**
+ *
+ * Test custom Entity not found in attribute.
+ *
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws java.lang.Exception if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testCustomEntityNotFoundInAttr()
+ throws Exception
+ {
+ MXParser parser = new MXParser();
+
+ String input = "sometext";
+ parser.setInput( new StringReader( input ) );
+ parser.defineEntityReplacementText( "myentity", "replacement" );
+
+ try
+ {
+ assertEquals( XmlPullParser.START_TAG, parser.next() );
+ fail( "should raise exception" );
+ }
+ catch ( XmlPullParserException e )
+ {
+ assertTrue( e.getMessage().contains( "could not resolve entity named 'otherentity' (position: START_DOCUMENT seen
+ * Test custom Entity not found in attribute, with tokenize.
+ *
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ * @throws XmlPullParserException
+ *
+ * @throws Exception if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testCustomEntityNotFoundInAttrTokenize() throws Exception
+ {
+ MXParser parser = new MXParser();
+
+ String input = "Issue #194: Incorrect getText() after parsing the DOCDECL section>
+ *
+ * test DOCDECL text with myCustomEntity that cannot be resolved, Unix line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testDocdeclTextWithEntitiesUnix()
+ throws IOException
+ {
+ testDocdeclTextWithEntities( "test-entities.xml" );
+ }
+
+ /**
+ * Issue #194: Incorrect getText() after parsing the DOCDECL section>
+ *
+ *
test DOCDECL text with myCustomEntity that cannot be resolved, DOS line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testDocdeclTextWithEntitiesDOS()
+ throws IOException
+ {
+ testDocdeclTextWithEntities( "test-entities-dos.xml" );
+ }
+
+ private void testDocdeclTextWithEntities( String filename )
+ throws IOException
+ {
+ try ( Reader reader = ReaderFactory.newXmlReader( new File( "src/test/resources/xml", filename ) ) )
+ {
+ MXParser parser = new MXParser();
+ parser.setInput( reader );
+ assertEquals( XmlPullParser.PROCESSING_INSTRUCTION, parser.nextToken() );
+ assertEquals( XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken() );
+ assertEquals( XmlPullParser.DOCDECL, parser.nextToken() );
+ assertEquals( " document [\n"
+ + "\n"
+ + "\n"
+ + "]", parser.getText() );
+ assertEquals( XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken() );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "document", parser.getName() );
+ assertEquals( XmlPullParser.TEXT, parser.next() );
+
+ fail( "should fail to resolve 'myCustomEntity' entity");
+ }
+ catch ( XmlPullParserException e )
+ {
+ assertTrue( e.getMessage().contains( "could not resolve entity named 'myCustomEntity'" ));
+ }
+ }
+
+ /**
+ * Issue #194: Incorrect getText() after parsing the DOCDECL section>
+ *
+ *
test DOCDECL text with entities appearing in attributes, Unix line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testDocdeclTextWithEntitiesInAttributesUnix()
+ throws IOException
+ {
+ testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr.xml" );
+ }
+
+ /**
+ * Issue #194: Incorrect getText() after parsing the DOCDECL section>
+ *
+ *
test DOCDECL text with entities appearing in attributes, DOS line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testDocdeclTextWithEntitiesInAttributesDOS()
+ throws IOException
+ {
+ testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr-dos.xml" );
+ }
+
+ private void testDocdeclTextWithEntitiesInAttributes( String filename )
+ throws IOException
+ {
+ try ( Reader reader = ReaderFactory.newXmlReader( new File( "src/test/resources/xml", filename ) ) )
+ {
+ MXParser parser = new MXParser();
+ parser.setInput( reader );
+ parser.defineEntityReplacementText( "nbsp", " " );
+ parser.defineEntityReplacementText( "Alpha", "Α" );
+ parser.defineEntityReplacementText( "tritPos", "𝟭" );
+ parser.defineEntityReplacementText( "flo", "ř" );
+ parser.defineEntityReplacementText( "myCustomEntity", "&flo;" );
+ assertEquals( XmlPullParser.PROCESSING_INSTRUCTION, parser.nextToken() );
+ assertEquals( XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken() );
+ assertEquals( XmlPullParser.DOCDECL, parser.nextToken() );
+ assertEquals( " document [\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "\n"
+ + "\n"
+ + "]", parser.getText() );
+ assertEquals( XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken() );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "document", parser.getName() );
+ assertEquals( 1, parser.getAttributeCount() );
+ assertEquals( "name", parser.getAttributeName( 0 ) );
+ assertEquals( "section name with entities: '&' 'Α' '<' ' ' '>' '𝟭' ''' 'ř' '\"'",
+ parser.getAttributeValue( 0 ) );
+
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "myCustomEntity", parser.getName() );
+ assertEquals( "ř", parser.getText() );
+
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( XmlPullParser.END_DOCUMENT, parser.nextToken() );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not raise exception: " + e );
+ }
+ }
+
+ /**
+ * test entity ref with entities appearing in tags, Unix line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testEntityRefTextUnix()
+ throws IOException
+ {
+ testEntityRefText( "\n" );
+ }
+
+ /**
+ * test entity ref with entities appearing in tags, DOS line separator.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testEntityRefTextDOS()
+ throws IOException
+ {
+ testEntityRefText( "\r\n" );
+ }
+
+ private void testEntityRefText( String newLine )
+ throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( "" ).append( newLine );
+ sb.append( "" ).append( newLine );
+ sb.append( "" ).append( newLine );
+ sb.append( "" ).append( newLine );
+ sb.append( "]>" ).append( newLine );
+ sb.append( "&foo;&foo1;&foo2;&tritPos;" );
+
+ try
+ {
+ MXParser parser = new MXParser();
+ parser.setInput( new StringReader( sb.toString() ) );
+ parser.defineEntityReplacementText( "foo", "ř" );
+ parser.defineEntityReplacementText( "nbsp", " " );
+ parser.defineEntityReplacementText( "foo1", " " );
+ parser.defineEntityReplacementText( "foo2", "š" );
+ parser.defineEntityReplacementText( "tritPos", "𝟭" );
+
+ assertEquals( XmlPullParser.DOCDECL, parser.nextToken() );
+ assertEquals( " test [\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "\n"
+ + "]", parser.getText() );
+ assertEquals( XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken() );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "b", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "ř", parser.getText() );
+ assertEquals( "foo", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( " ", parser.getText() );
+ assertEquals( "foo1", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "š", parser.getText() );
+ assertEquals( "foo2", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "𝟭", parser.getText() );
+ assertEquals( "tritPos", parser.getName() );
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( "b", parser.getName() );
+ assertEquals( XmlPullParser.END_DOCUMENT, parser.nextToken() );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not raise exception: " + e );
+ }
+ }
+
+ /**
+ * Ensures that entity ref getText() and getName() return the correct value.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testEntityReplacement() throws IOException {
+ String input = "
";
+
+ try
+ {
+ MXParser parser = new MXParser();
+ parser.setInput( new StringReader( input ) );
+ parser.defineEntityReplacementText( "nbsp", " " );
+
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "p", parser.getName() );
+ assertEquals( XmlPullParser.COMMENT, parser.nextToken() );
+ assertEquals( " a pagebreak: ", parser.getText() );
+ assertEquals( XmlPullParser.COMMENT, parser.nextToken() );
+ assertEquals( " PB ", parser.getText() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "\u00A0", parser.getText() );
+ assertEquals( "#160", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( " ", parser.getText() );
+ assertEquals( "nbsp", parser.getName() );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "unknown", parser.getName() );
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( "unknown", parser.getName() );
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( "p", parser.getName() );
+ assertEquals( XmlPullParser.END_DOCUMENT, parser.nextToken() );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not raise exception: " + e );
+ }
+ }
+
+ /**
+ * Ensures correct replacements inside the internal PC array when the new copied array size is shorter than
+ * previous ones.
+ *
+ * Regression test: assure same behavior of MXParser from plexus-utils 3.3.0.
+ *
+ * @throws IOException if any.
+ *
+ * @since 3.4.2
+ */
+ @Test
+ public void testReplacementInPCArrayWithShorterCharArray()
+ throws IOException
+ {
+ String input = "]>"
+ + "";
+
+ try
+ {
+ MXParser parser = new MXParser();
+ parser.setInput( new StringReader( new String(input.getBytes(), "ISO-8859-1" ) ) );
+ parser.defineEntityReplacementText( "foo", "ř" );
+ parser.defineEntityReplacementText( "tritPos", "𝟭" );
+
+ assertEquals( XmlPullParser.DOCDECL, parser.nextToken() );
+ assertEquals( " test []", parser.getText() );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "section", parser.getName() );
+ assertEquals( 1, parser.getAttributeCount() );
+ assertEquals( "name" , parser.getAttributeName( 0 ) );
+ assertEquals( "&ř𝟭" , parser.getAttributeValue( 0 ) );
+ assertEquals( XmlPullParser.START_TAG, parser.nextToken() );
+ assertEquals( "", parser.getText() );
+ assertEquals( "p", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "&", parser.getText() );
+ assertEquals( "amp", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "ř", parser.getText() );
+ assertEquals( "foo", parser.getName() );
+ assertEquals( XmlPullParser.ENTITY_REF, parser.nextToken() );
+ assertEquals( "𝟭", parser.getText() );
+ assertEquals( "tritPos", parser.getName() );
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( "p", parser.getName() );
+ assertEquals( XmlPullParser.END_TAG, parser.nextToken() );
+ assertEquals( "section", parser.getName() );
+ assertEquals( XmlPullParser.END_DOCUMENT, parser.nextToken() );
+ }
+ catch ( XmlPullParserException e )
+ {
+ fail( "should not raise exception: " + e );
+ }
+ }
}
diff --git a/src/test/resources/xml/test-entities-dos.xml b/src/test/resources/xml/test-entities-dos.xml
new file mode 100644
index 00000000..e1d6d17a
--- /dev/null
+++ b/src/test/resources/xml/test-entities-dos.xml
@@ -0,0 +1,6 @@
+
+
+
+]>
+&myCustomEntity;
\ No newline at end of file
diff --git a/src/test/resources/xml/test-entities-in-attr-dos.xml b/src/test/resources/xml/test-entities-in-attr-dos.xml
new file mode 100644
index 00000000..a423c995
--- /dev/null
+++ b/src/test/resources/xml/test-entities-in-attr-dos.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+]>
+&myCustomEntity;
\ No newline at end of file
diff --git a/src/test/resources/xml/test-entities-in-attr.xml b/src/test/resources/xml/test-entities-in-attr.xml
new file mode 100644
index 00000000..a423c995
--- /dev/null
+++ b/src/test/resources/xml/test-entities-in-attr.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+]>
+&myCustomEntity;
\ No newline at end of file
diff --git a/src/test/resources/xml/test-entities.xml b/src/test/resources/xml/test-entities.xml
new file mode 100644
index 00000000..e1d6d17a
--- /dev/null
+++ b/src/test/resources/xml/test-entities.xml
@@ -0,0 +1,6 @@
+
+
+
+]>
+&myCustomEntity;
\ No newline at end of file
From 298fd8d626a7dd1271dcc4ae8e2029a2eba2c1ad Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 15 Apr 2022 02:03:30 +0000
Subject: [PATCH 03/12] Bump actions/checkout from 2.4.0 to 3.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3.0.1)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
.github/workflows/codeql-analysis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 3f39b7e2..9935c621 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2.4.0
+ uses: actions/checkout@v3.0.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
From 52d64eadb7d4f8a543d43cc49194cdf74b93f944 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 29 Mar 2022 02:02:37 +0000
Subject: [PATCH 04/12] Bump jmh-core from 1.34 to 1.35
Bumps jmh-core from 1.34 to 1.35.
---
updated-dependencies:
- dependency-name: org.openjdk.jmh:jmh-core
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index a758c676..8a9018e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,7 @@ limitations under the License.
org.openjdk.jmh
jmh-core
- 1.34
+ 1.35
test
From 11be3b5bf2a8b9853ab2e7f2e1245233220ca46a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 29 Mar 2022 02:02:35 +0000
Subject: [PATCH 05/12] Bump jmh-generator-annprocess from 1.34 to 1.35
Bumps jmh-generator-annprocess from 1.34 to 1.35.
---
updated-dependencies:
- dependency-name: org.openjdk.jmh:jmh-generator-annprocess
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8a9018e9..beda14b7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,7 +70,7 @@ limitations under the License.
org.openjdk.jmh
jmh-generator-annprocess
- 1.34
+ 1.35
test
From 10f729542509b039a748fc7085826e43137bba35 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 8 Mar 2022 02:02:38 +0000
Subject: [PATCH 06/12] Bump release-drafter/release-drafter from 5.18.1 to
5.19.0
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 5.18.1 to 5.19.0.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](https://github.com/release-drafter/release-drafter/compare/v5.18.1...v5.19.0)
---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
.github/workflows/release-drafter.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index 4ece1c1a..96f54b5d 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -7,6 +7,6 @@ jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- - uses: release-drafter/release-drafter@v5.18.1
+ - uses: release-drafter/release-drafter@v5.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
From 0d384e75ba5f950d389281226e56515be195b604 Mon Sep 17 00:00:00 2001
From: Gabriel Belingueres
Date: Sun, 17 Apr 2022 04:01:59 -0300
Subject: [PATCH 07/12] Fix some testing XML files checkout with incorrect eol
---
.gitattributes | 4 ++++
.../org/codehaus/plexus/util/xml/pull/MXParserTest.java | 8 ++++----
.../xml/{test-entities-dos.xml => test-entities-DOS.xml} | 0
.../xml/{test-entities.xml => test-entities-UNIX.xml} | 0
...ties-in-attr-dos.xml => test-entities-in-attr-DOS.xml} | 0
...ntities-in-attr.xml => test-entities-in-attr-UNIX.xml} | 0
6 files changed, 8 insertions(+), 4 deletions(-)
rename src/test/resources/xml/{test-entities-dos.xml => test-entities-DOS.xml} (100%)
rename src/test/resources/xml/{test-entities.xml => test-entities-UNIX.xml} (100%)
rename src/test/resources/xml/{test-entities-in-attr-dos.xml => test-entities-in-attr-DOS.xml} (100%)
rename src/test/resources/xml/{test-entities-in-attr.xml => test-entities-in-attr-UNIX.xml} (100%)
diff --git a/.gitattributes b/.gitattributes
index 3bb3b5ea..ccd9f864 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,3 +6,7 @@
*.css text
*.js text
*.sql text
+
+# some files require the correct eol for proper testing
+*-DOS.xml text eol=crlf
+*-UNIX.xml text eol=lf
diff --git a/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java b/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
index 6fc6e9c6..27ba5f8c 100644
--- a/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
+++ b/src/test/java/org/codehaus/plexus/util/xml/pull/MXParserTest.java
@@ -1081,7 +1081,7 @@ public void testCustomEntityNotFoundInAttrTokenize() throws Exception
public void testDocdeclTextWithEntitiesUnix()
throws IOException
{
- testDocdeclTextWithEntities( "test-entities.xml" );
+ testDocdeclTextWithEntities( "test-entities-UNIX.xml" );
}
/**
@@ -1099,7 +1099,7 @@ public void testDocdeclTextWithEntitiesUnix()
public void testDocdeclTextWithEntitiesDOS()
throws IOException
{
- testDocdeclTextWithEntities( "test-entities-dos.xml" );
+ testDocdeclTextWithEntities( "test-entities-DOS.xml" );
}
private void testDocdeclTextWithEntities( String filename )
@@ -1144,7 +1144,7 @@ private void testDocdeclTextWithEntities( String filename )
public void testDocdeclTextWithEntitiesInAttributesUnix()
throws IOException
{
- testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr.xml" );
+ testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr-UNIX.xml" );
}
/**
@@ -1162,7 +1162,7 @@ public void testDocdeclTextWithEntitiesInAttributesUnix()
public void testDocdeclTextWithEntitiesInAttributesDOS()
throws IOException
{
- testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr-dos.xml" );
+ testDocdeclTextWithEntitiesInAttributes( "test-entities-in-attr-DOS.xml" );
}
private void testDocdeclTextWithEntitiesInAttributes( String filename )
diff --git a/src/test/resources/xml/test-entities-dos.xml b/src/test/resources/xml/test-entities-DOS.xml
similarity index 100%
rename from src/test/resources/xml/test-entities-dos.xml
rename to src/test/resources/xml/test-entities-DOS.xml
diff --git a/src/test/resources/xml/test-entities.xml b/src/test/resources/xml/test-entities-UNIX.xml
similarity index 100%
rename from src/test/resources/xml/test-entities.xml
rename to src/test/resources/xml/test-entities-UNIX.xml
diff --git a/src/test/resources/xml/test-entities-in-attr-dos.xml b/src/test/resources/xml/test-entities-in-attr-DOS.xml
similarity index 100%
rename from src/test/resources/xml/test-entities-in-attr-dos.xml
rename to src/test/resources/xml/test-entities-in-attr-DOS.xml
diff --git a/src/test/resources/xml/test-entities-in-attr.xml b/src/test/resources/xml/test-entities-in-attr-UNIX.xml
similarity index 100%
rename from src/test/resources/xml/test-entities-in-attr.xml
rename to src/test/resources/xml/test-entities-in-attr-UNIX.xml
From d3823dc54686cbd268b0cd8f7c6ef6058d163817 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 22 Apr 2022 02:02:31 +0000
Subject: [PATCH 08/12] Bump actions/checkout from 3.0.1 to 3.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.0.1...v3.0.2)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.github/workflows/codeql-analysis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9935c621..6240fce9 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3.0.1
+ uses: actions/checkout@v3.0.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
From 2f68e930054aae96ca8f694774b07fd63741301c Mon Sep 17 00:00:00 2001
From: Sylwester Lachiewicz
Date: Sat, 23 Apr 2022 19:43:51 +0200
Subject: [PATCH 09/12] Fix regression and deprecate: FileUtils.fileAppend
should create file if not exist.
---
src/main/java/org/codehaus/plexus/util/FileUtils.java | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/codehaus/plexus/util/FileUtils.java b/src/main/java/org/codehaus/plexus/util/FileUtils.java
index cac82ba3..4dca8b3c 100644
--- a/src/main/java/org/codehaus/plexus/util/FileUtils.java
+++ b/src/main/java/org/codehaus/plexus/util/FileUtils.java
@@ -389,6 +389,8 @@ private static InputStreamReader getInputStreamReader( File file, String encodin
* @param fileName The path of the file to write.
* @param data The content to write to the file.
* @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
+ * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
*/
public static void fileAppend( String fileName, String data )
throws IOException
@@ -403,11 +405,14 @@ public static void fileAppend( String fileName, String data )
* @param encoding The encoding of the file.
* @param data The content to write to the file.
* @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
+ * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
*/
public static void fileAppend( String fileName, String encoding, String data )
throws IOException
{
- try ( OutputStream out = Files.newOutputStream( Paths.get(fileName), StandardOpenOption.APPEND ) )
+ try ( OutputStream out = Files.newOutputStream( Paths.get(fileName),
+ StandardOpenOption.APPEND, StandardOpenOption.CREATE ) )
{
if ( encoding != null )
{
From 0808fe36dde238e6824043d5ca597b7cec1bd5c7 Mon Sep 17 00:00:00 2001
From: Sylwester Lachiewicz
Date: Sat, 23 Apr 2022 22:33:48 +0200
Subject: [PATCH 10/12] Added Licence file
Closes #78
---
LICENSE.txt | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 201 insertions(+)
create mode 100644 LICENSE.txt
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..261eeb9e
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
From 49773f19e43af7d39f441aac06adbc6a4052a063 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Tue, 26 Apr 2022 15:42:15 +0200
Subject: [PATCH 11/12] Provides a CachingOuptutStream and a CachingWriter
(#184)
Provides a CachingOuptutStream and a CachingWriter
---
.../plexus/util/io/CachingOutputStream.java | 175 ++++++++++++++++++
.../plexus/util/io/CachingWriter.java | 62 +++++++
.../util/io/CachingOutputStreamTest.java | 145 +++++++++++++++
.../plexus/util/io/CachingWriterTest.java | 143 ++++++++++++++
4 files changed, 525 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
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
new file mode 100644
index 00000000..521d5373
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java
@@ -0,0 +1,175 @@
+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.OutputStream;
+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;
+
+/**
+ * Caching OutputStream to avoid overwriting a file with
+ * the same content.
+ */
+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 ) throws IOException
+ {
+ this( Objects.requireNonNull( path ).toPath() );
+ }
+
+ 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 write( int b ) throws IOException
+ {
+ if ( writeBuffer.remaining() < 1 )
+ {
+ ( ( Buffer ) writeBuffer ).flip();
+ flushBuffer( writeBuffer );
+ ( ( Buffer ) 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 )
+ {
+ ( ( Buffer ) writeBuffer ).flip();
+ flushBuffer( writeBuffer );
+ ( ( Buffer ) 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
+ {
+ ( ( Buffer ) writeBuffer ).flip();
+ flushBuffer( writeBuffer );
+ ( ( Buffer ) writeBuffer ).clear();
+ super.flush();
+ }
+
+ private void flushBuffer( ByteBuffer writeBuffer ) throws IOException
+ {
+ if ( modified )
+ {
+ channel.write( writeBuffer );
+ }
+ else
+ {
+ int len = writeBuffer.remaining();
+ ByteBuffer readBuffer;
+ if ( this.readBuffer.capacity() >= len )
+ {
+ readBuffer = this.readBuffer;
+ ( ( Buffer ) 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;
+ }
+ ( ( Buffer ) 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() )
+ {
+ if ( !modified )
+ {
+ FileTime now = FileTime.from( Instant.now() );
+ Files.setLastModifiedTime( path, now );
+ modified = true;
+ }
+ channel.truncate( position );
+ }
+ channel.close();
+ }
+
+ 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..23cc4411
--- /dev/null
+++ b/src/main/java/org/codehaus/plexus/util/io/CachingWriter.java
@@ -0,0 +1,62 @@
+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.OutputStreamWriter;
+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 OutputStreamWriter
+{
+ private final CachingOutputStream cos;
+
+ public CachingWriter( File path, Charset charset ) throws IOException
+ {
+ this( Objects.requireNonNull( path ).toPath(), charset );
+ }
+
+ public CachingWriter( Path path, Charset charset ) throws IOException
+ {
+ this( path, charset, 32 * 1024 );
+ }
+
+ public CachingWriter( Path path, Charset charset, int bufferSize ) throws IOException
+ {
+ this( new CachingOutputStream( path, bufferSize ), charset );
+ }
+
+ private CachingWriter( CachingOutputStream outputStream, Charset charset ) throws IOException
+ {
+ super( outputStream, charset );
+ this.cos = outputStream;
+ }
+
+ public boolean isModified()
+ {
+ 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..3c329ea9
--- /dev/null
+++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java
@@ -0,0 +1,145 @@
+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.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;
+
+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;
+ Path checkLastModified;
+ FileTime lm;
+
+ @Before
+ 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
+ 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 );
+
+ waitLastModified();
+
+ 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 );
+ modified = newModified;
+
+ waitLastModified();
+
+ // 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;
+
+ waitLastModified();
+
+ // 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;
+
+ waitLastModified();
+
+ // 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..f5b95903
--- /dev/null
+++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java
@@ -0,0 +1,143 @@
+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 java.util.Objects;
+
+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;
+ Path checkLastModified;
+ FileTime lm;
+
+ @Before
+ 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
+ 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 );
+
+ waitLastModified();
+
+ 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 );
+ modified = newModified;
+
+ waitLastModified();
+
+ // 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;
+
+ waitLastModified();
+
+ // 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;
+
+ waitLastModified();
+
+ // 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 ee5fe41b032f95f9b39f6ef21256800a6608dfd6 Mon Sep 17 00:00:00 2001
From: Guillaume Nodet
Date: Tue, 26 Apr 2022 17:44:51 +0200
Subject: [PATCH 12/12] Fix last modified time not being updated on linux
(#203)
Fix test, clean things
---
.../plexus/util/io/CachingOutputStream.java | 14 ++++++--------
.../plexus/util/io/CachingOutputStreamTest.java | 7 ++-----
.../codehaus/plexus/util/io/CachingWriterTest.java | 7 ++-----
3 files changed, 10 insertions(+), 18 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 521d5373..744e6f06 100644
--- a/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java
+++ b/src/main/java/org/codehaus/plexus/util/io/CachingOutputStream.java
@@ -153,19 +153,17 @@ private void flushBuffer( ByteBuffer writeBuffer ) throws IOException
@Override
public void close() throws IOException
{
- flush();
- long position = channel.position();
- if ( position != channel.size() )
+ if ( channel.isOpen() )
{
- if ( !modified )
+ flush();
+ long position = channel.position();
+ if ( position != channel.size() )
{
- FileTime now = FileTime.from( Instant.now() );
- Files.setLastModifiedTime( path, now );
modified = true;
+ channel.truncate( position );
}
- channel.truncate( position );
+ channel.close();
}
- channel.close();
}
public boolean 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
index 3c329ea9..e7888ad4 100644
--- a/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java
+++ b/src/test/java/org/codehaus/plexus/util/io/CachingOutputStreamTest.java
@@ -17,7 +17,6 @@
*/
import java.io.IOException;
-import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -39,7 +38,6 @@ public class CachingOutputStreamTest
Path tempDir;
Path checkLastModified;
- FileTime lm;
@Before
public void setup() throws IOException
@@ -48,19 +46,18 @@ public void setup() throws IOException
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
{
+ Files.newOutputStream( checkLastModified ).close();
+ FileTime lm = Files.getLastModifiedTime( checkLastModified );
while ( true )
{
Files.newOutputStream( checkLastModified ).close();
FileTime nlm = Files.getLastModifiedTime( checkLastModified );
if ( !Objects.equals( nlm, lm ) )
{
- lm = nlm;
break;
}
Thread.sleep( 10 );
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 f5b95903..a4ffec91 100644
--- a/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java
+++ b/src/test/java/org/codehaus/plexus/util/io/CachingWriterTest.java
@@ -27,7 +27,6 @@
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;
@@ -38,7 +37,6 @@ public class CachingWriterTest
Path tempDir;
Path checkLastModified;
- FileTime lm;
@Before
public void setup() throws IOException
@@ -47,19 +45,18 @@ public void setup() throws IOException
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
{
+ Files.newOutputStream( checkLastModified ).close();
+ FileTime lm = Files.getLastModifiedTime( checkLastModified );
while ( true )
{
Files.newOutputStream( checkLastModified ).close();
FileTime nlm = Files.getLastModifiedTime( checkLastModified );
if ( !Objects.equals( nlm, lm ) )
{
- lm = nlm;
break;
}
Thread.sleep( 10 );