From fd4f0b147c5bb8f6d41d8d22f1bcc8405ea27b14 Mon Sep 17 00:00:00 2001 From: Andrzej Jarmoniuk Date: Wed, 7 Sep 2022 07:53:09 +0200 Subject: [PATCH] #342: Adding the possibility of adding new scm tags --- .../codehaus/mojo/versions/SetScmTagMojo.java | 22 ++- .../codehaus/mojo/versions/api/PomHelper.java | 163 ++++++++++++++---- .../mojo/versions/SetScmTagMojoTest.java | 57 ++++++ .../mojo/versions/api/PomHelperTest.java | 82 ++++++++- .../utils/ModifiedPomXMLEventReaderUtils.java | 62 +++++++ .../mojo/set-scm-tag/issue-342-pom.xml | 24 +++ .../org/codehaus/mojo/set-scm-tag/pom.xml | 24 +++ 7 files changed, 392 insertions(+), 42 deletions(-) create mode 100644 src/test/java/org/codehaus/mojo/versions/SetScmTagMojoTest.java create mode 100644 src/test/java/org/codehaus/mojo/versions/utils/ModifiedPomXMLEventReaderUtils.java create mode 100644 src/test/resources/org/codehaus/mojo/set-scm-tag/issue-342-pom.xml create mode 100644 src/test/resources/org/codehaus/mojo/set-scm-tag/pom.xml diff --git a/src/main/java/org/codehaus/mojo/versions/SetScmTagMojo.java b/src/main/java/org/codehaus/mojo/versions/SetScmTagMojo.java index b7bbc2b66a..46d5cbdd0e 100644 --- a/src/main/java/org/codehaus/mojo/versions/SetScmTagMojo.java +++ b/src/main/java/org/codehaus/mojo/versions/SetScmTagMojo.java @@ -93,33 +93,39 @@ protected void update( ModifiedPomXMLEventReader pom ) List failures = new ArrayList<>(); if ( !isBlank( newTag ) ) { - getLog().info( "Updating tag: " + scm.getTag() + " -> " + newTag ); - if ( !PomHelper.setProjectValue( pom, "/project/scm/tag", newTag ) ) + getLog().info( "Updating tag: " + ( scm != null && scm.getTag() != null + ? scm.getTag() : "(empty)" ) + " -> " + newTag ); + if ( !PomHelper.setElementValue( pom, "/project/scm", "tag", newTag ) ) { failures.add( "tag: " + newTag ); } } if ( !isBlank( connection ) ) { - getLog().info( "Updating connection: " + scm.getConnection() + " -> " + connection ); - if ( !PomHelper.setProjectValue( pom, "/project/scm/connection", connection ) ) + getLog().info( "Updating connection: " + ( scm != null && scm.getConnection() != null + ? scm.getConnection() : "(empty)" ) + " -> " + connection ); + if ( !PomHelper.setElementValue( pom, "/project/scm", "connection", connection ) ) { failures.add( "connection: " + connection ); } } if ( !isBlank( developerConnection ) ) { - getLog().info( "Updating developerConnection: " + scm.getDeveloperConnection() + " -> " + getLog().info( "Updating developerConnection: " + + ( scm != null && scm.getDeveloperConnection() != null + ? scm.getDeveloperConnection() : "(empty)" ) + " -> " + developerConnection ); - if ( !PomHelper.setProjectValue( pom, "/project/scm/developerConnection", developerConnection ) ) + if ( !PomHelper.setElementValue( pom, "/project/scm", "developerConnection", + developerConnection ) ) { failures.add( "developerConnection: " + developerConnection ); } } if ( !isBlank( url ) ) { - getLog().info( "Updating url: " + scm.getUrl() + " -> " + url ); - if ( !PomHelper.setProjectValue( pom, "/project/scm/url", url ) ) + getLog().info( "Updating url: " + ( scm != null && scm.getUrl() != null + ? scm.getUrl() : "(empty)" ) + " -> " + url ); + if ( !PomHelper.setElementValue( pom, "/project/scm", "url", url ) ) { failures.add( "url: " + url ); } diff --git a/src/main/java/org/codehaus/mojo/versions/api/PomHelper.java b/src/main/java/org/codehaus/mojo/versions/api/PomHelper.java index 0df81259c5..b7dde0e88b 100644 --- a/src/main/java/org/codehaus/mojo/versions/api/PomHelper.java +++ b/src/main/java/org/codehaus/mojo/versions/api/PomHelper.java @@ -71,6 +71,8 @@ import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import static java.util.stream.IntStream.range; + /** * Helper class for modifying pom files. * @@ -232,59 +234,156 @@ else if ( matchScopeRegex.matcher( path ).matches() ) public static boolean setProjectVersion( final ModifiedPomXMLEventReader pom, final String value ) throws XMLStreamException { - return setProjectValue( pom, "/project/version", value ); + return setElementValue( pom, "/project", "version", value, false ); } /** - * Searches the pom re-defining a project value using the given pattern. + * Sets the value of the given element given its parent element path. + * Will only consider the first found occurrence of the parent element. + * If the element is not found in the parent element, the method will create the element. * - * @param pom The pom to modify. - * @param pattern The pattern to look for. - * @param value The new value of the property. - * @return true if a replacement was made. - * @throws XMLStreamException if something went wrong. + * @param pom pom to modify + * @param parentPath path of the parent element + * @param elementName name of the element to set or create + * @param value the new value of the element + * @return {@code true} if the element was created or replaced + * @throws XMLStreamException if something went wrong */ - public static boolean setProjectValue( final ModifiedPomXMLEventReader pom, String pattern, final String value ) - throws XMLStreamException + public static boolean setElementValue( ModifiedPomXMLEventReader pom, String parentPath, + String elementName, String value ) + throws XMLStreamException { - Stack stack = new Stack<>(); - String path = ""; - final Pattern matchScopeRegex; - boolean madeReplacement = false; - matchScopeRegex = Pattern.compile( pattern ); - pom.rewind(); + return setElementValue( pom, parentPath, elementName, value, true ); + } - while ( pom.hasNext() ) + /** + * Sets the value of the given element given its parent element path. + * Will only consider the first found occurrence of the parent element. + * If the element is not found in the parent element, the method will create the element + * if {@code shouldCreate} is {@code true}. + * + * @param pom pom to modify + * @param parentPath path of the parent element + * @param elementName name of the element to set or create + * @param value the new value of the element + * @param shouldCreate should the element be created if it's not found in the first encountered parent element + * matching the parentPath + * @return {@code true} if the element was created or replaced + * @throws XMLStreamException if something went wrong + */ + public static boolean setElementValue( ModifiedPomXMLEventReader pom, String parentPath, + String elementName, String value, boolean shouldCreate ) + throws XMLStreamException + { + class ElementValueInternal { - XMLEvent event = pom.nextEvent(); - if ( event.isStartElement() ) + private final String parentName; + private final String superParentPath; + + private static final int MARK_CHILD_BEGIN = 0; + private static final int MARK_OPTION = 1; + private static final int PARENT_BEGIN = 2; + + ElementValueInternal() { - stack.push( path ); - path = path + "/" + event.asStartElement().getName().getLocalPart(); + int lastDelimeterIndex = parentPath.lastIndexOf( '/' ); + parentName = parentPath.substring( lastDelimeterIndex + 1 ); + superParentPath = parentPath.substring( 0, lastDelimeterIndex ); + } - if ( matchScopeRegex.matcher( path ).matches() ) + boolean process( String currentPath ) throws XMLStreamException + { + boolean replacementMade = false; + while ( !replacementMade && pom.hasNext() ) { - pom.mark( 0 ); + XMLEvent event = pom.nextEvent(); + if ( event.isStartElement() ) + { + String currentElementName = event.asStartElement().getName().getLocalPart(); + + // here, we will only mark the beginning of the child or the parent element + if ( currentPath.equals( parentPath ) && elementName.equals( currentElementName ) ) + { + pom.mark( MARK_CHILD_BEGIN ); + } + else if ( currentPath.equals( superParentPath ) && currentElementName.equals( parentName ) ) + { + pom.mark( PARENT_BEGIN ); + } + // process child element + replacementMade = process( currentPath + "/" + currentElementName ); + } + else if ( event.isEndElement() ) + { + // here we're doing the replacement + if ( currentPath.equals( parentPath + "/" + elementName ) ) + { + // end of the child + replaceValueInChild(); + replacementMade = true; + } + else if ( shouldCreate && currentPath.equals( parentPath ) ) + { + // end of the parent + replaceValueInParent(); + replacementMade = true; + } + else + { + return false; + } + } } + return replacementMade; } - if ( event.isEndElement() ) + + private void replaceValueInChild() { - if ( matchScopeRegex.matcher( path ).matches() ) + pom.mark( MARK_OPTION ); + if ( pom.getBetween( MARK_CHILD_BEGIN, MARK_OPTION ).length() > 0 ) { - pom.mark( 1 ); - if ( pom.hasMark( 0 ) && pom.hasMark( 1 ) ) + pom.replaceBetween( 0, 1, value ); + } + else + { + pom.replace( String.format( "<%1$s>%2$s", elementName, value ) ); + } + } + + private void replaceValueInParent() + { + pom.mark( MARK_OPTION ); + if ( pom.hasMark( PARENT_BEGIN ) ) + { + if ( pom.getBetween( PARENT_BEGIN, MARK_OPTION ).length() > 0 ) { - pom.replaceBetween( 0, 1, value ); - madeReplacement = true; + pom.replace( String.format( "<%2$s>%3$s", + parentName, elementName, value ) ); + } + else + { + pom.replace( String.format( "<%1$s><%2$s>%3$s", + parentName, elementName, value ) ); } - pom.clearMark( 0 ); - pom.clearMark( 1 ); } - path = stack.pop(); + else + { + pom.replace( String.format( "<%1$s><%2$s>%3$s", + parentName, elementName, value ) ); + } } } - return madeReplacement; + + try + { + pom.rewind(); + return new ElementValueInternal().process( "" ); + } + finally + { + range( 0, 3 ).forEach( pom::clearMark ); + } } /** diff --git a/src/test/java/org/codehaus/mojo/versions/SetScmTagMojoTest.java b/src/test/java/org/codehaus/mojo/versions/SetScmTagMojoTest.java new file mode 100644 index 0000000000..db8fae6aef --- /dev/null +++ b/src/test/java/org/codehaus/mojo/versions/SetScmTagMojoTest.java @@ -0,0 +1,57 @@ +package org.codehaus.mojo.versions; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.codehaus.mojo.versions.utils.BaseMojoTestCase; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.matchesPattern; + +/** + * Basic tests for {@linkplain SetPropertyMojoTest}. + * + * @author Andrzej Jarmoniuk + */ +public class SetScmTagMojoTest extends BaseMojoTestCase +{ + @Test + public void testNewScmValues() throws Exception + { + Path pomFile = Paths.get( "target/test-classes/org/codehaus/mojo/set-scm-tag/issue-342-pom.xml" ); + createMojo( "set-scm-tag", pomFile.toString() ) + .execute(); + String output = String.join( "", Files.readAllLines( pomFile ) ) + .replaceAll( "\\s*", "" ); + assertThat( output, allOf( + matchesPattern( ".*.*\\s*newTag\\s*.*.*" ), + matchesPattern( ".*.*\\s*url\\s*.*.*" ), + matchesPattern( ".*.*\\s*connection\\s*.*.*" ), + matchesPattern( ".*.*\\s*" + + "developerConnection\\s*.*.*" ) + ) + ); + } +} diff --git a/src/test/java/org/codehaus/mojo/versions/api/PomHelperTest.java b/src/test/java/org/codehaus/mojo/versions/api/PomHelperTest.java index 653dc658d3..e447355b39 100644 --- a/src/test/java/org/codehaus/mojo/versions/api/PomHelperTest.java +++ b/src/test/java/org/codehaus/mojo/versions/api/PomHelperTest.java @@ -1,6 +1,7 @@ package org.codehaus.mojo.versions.api; import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.StringReader; @@ -12,8 +13,13 @@ import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader; import org.codehaus.stax2.XMLInputFactory2; +import org.junit.BeforeClass; import org.junit.Test; +import static org.codehaus.mojo.versions.utils.ModifiedPomXMLEventReaderUtils.matches; +import static org.codehaus.stax2.XMLInputFactory2.P_PRESERVE_LOCATION; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -25,6 +31,14 @@ */ public class PomHelperTest { + private static final XMLInputFactory INPUT_FACTORY = XMLInputFactory2.newInstance(); + + @BeforeClass + public static void setUp() + { + INPUT_FACTORY.setProperty( P_PRESERVE_LOCATION, Boolean.TRUE ); + } + /** * Tests if imported POMs are properly read from dependency management section. Such logic is required to resolve * bug #134 @@ -36,11 +50,12 @@ public void testImportedPOMsRetrievedFromDependencyManagement() throws Exception { URL url = getClass().getResource( "PomHelperTest.dependencyManagementBOMs.pom.xml" ); + assert url != null; File file = new File( url.getPath() ); StringBuilder input = PomHelper.readXmlFile( file ); XMLInputFactory inputFactory = XMLInputFactory2.newInstance(); - inputFactory.setProperty( XMLInputFactory2.P_PRESERVE_LOCATION, Boolean.TRUE ); + inputFactory.setProperty( P_PRESERVE_LOCATION, Boolean.TRUE ); ModifiedPomXMLEventReader pom = new ModifiedPomXMLEventReader( input, inputFactory, file.getAbsolutePath() ); @@ -68,11 +83,12 @@ public void testLongProperties() throws Exception { URL url = getClass().getResource( "PomHelperTest.testLongProperties.pom.xml" ); + assert url != null; File file = new File( url.getPath() ); StringBuilder input = PomHelper.readXmlFile( file ); XMLInputFactory inputFactory = XMLInputFactory2.newInstance(); - inputFactory.setProperty( XMLInputFactory2.P_PRESERVE_LOCATION, Boolean.TRUE ); + inputFactory.setProperty( P_PRESERVE_LOCATION, Boolean.TRUE ); ModifiedPomXMLEventReader pom = new ModifiedPomXMLEventReader( input, inputFactory, file.getAbsolutePath() ); @@ -92,6 +108,7 @@ public void testGroupIdNotOnChildPom() throws Exception { URL url = getClass().getResource( "PomHelperTest.noGroupIdOnChild.pom.xml" ); + assert url != null; StringBuilder input = PomHelper.readXmlFile( new File( url.getPath() ) ); MavenXpp3Reader reader = new MavenXpp3Reader(); Model model = reader.read( new StringReader( input.toString() ) ); @@ -187,4 +204,65 @@ public void testRangeVersionIntersect() } + @Test + public void testSetElementValueExistingValue() throws XMLStreamException + { + ModifiedPomXMLEventReader xmlEventReader = new ModifiedPomXMLEventReader( + new StringBuilder( "test" ), + INPUT_FACTORY, null ); + + assertThat( PomHelper.setElementValue( xmlEventReader, "/super-parent/parent", + "child", "value" ), is( true ) ); + assertThat( xmlEventReader, + matches( "value" ) ); + } + + @Test + public void testSetElementValueEmptyChild() throws XMLStreamException + { + ModifiedPomXMLEventReader xmlEventReader = new ModifiedPomXMLEventReader( + new StringBuilder( "" ), INPUT_FACTORY, null ); + + assertThat( PomHelper.setElementValue( xmlEventReader, "/super-parent/parent", + "child", "value" ), is( true ) ); + assertThat( xmlEventReader, + matches( "value" ) ); + } + + @Test + public void testSetElementValueNewValueEmptyParent() throws XMLStreamException + { + ModifiedPomXMLEventReader xmlEventReader = new ModifiedPomXMLEventReader( + new StringBuilder( "" ), INPUT_FACTORY, null ); + + assertThat( PomHelper.setElementValue( xmlEventReader, "/super-parent/parent", + "child", "value" ), is( true ) ); + assertThat( xmlEventReader, + matches( "value" ) ); + } + + @Test + public void testSetElementValueNewValueNoChild() throws XMLStreamException + { + ModifiedPomXMLEventReader xmlEventReader = new ModifiedPomXMLEventReader( + new StringBuilder( "" ), INPUT_FACTORY, null ); + + assertThat( PomHelper.setElementValue( xmlEventReader, "/super-parent/parent", + "child", "value" ), is( true ) ); + assertThat( xmlEventReader, + matches( "value" ) ); + } + + @Test + public void testSetProjectValueNewValueNonEmptyParent() throws XMLStreamException + { + ModifiedPomXMLEventReader xmlEventReader = new ModifiedPomXMLEventReader( + new StringBuilder( "test" ), INPUT_FACTORY, + null ); + + assertThat( PomHelper.setElementValue( xmlEventReader, "/super-parent/parent", + "child", "value" ), is( true ) ); + assertThat( xmlEventReader, + matches( "value" ) ); + } } diff --git a/src/test/java/org/codehaus/mojo/versions/utils/ModifiedPomXMLEventReaderUtils.java b/src/test/java/org/codehaus/mojo/versions/utils/ModifiedPomXMLEventReaderUtils.java new file mode 100644 index 0000000000..fb8a37a3b3 --- /dev/null +++ b/src/test/java/org/codehaus/mojo/versions/utils/ModifiedPomXMLEventReaderUtils.java @@ -0,0 +1,62 @@ +package org.codehaus.mojo.versions.utils; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + *

Utilities for the {@link ModifiedPomXMLEventReader} class

+ * + * @author Andrzej Jarmoniuk + */ +public class ModifiedPomXMLEventReaderUtils +{ + public static

Matcher

matches( String pattern ) + { + return new TypeSafeMatcher

() + { + @Override + public void describeTo( Description description ) + { + description.appendText( pattern ); + } + + @Override + protected void describeMismatchSafely( P pom, Description description ) + { + description.appendText( asString( pom ) ); + } + + @Override + protected boolean matchesSafely( P pom ) + { + return pattern.matches( asString( pom ) ); + } + + private String asString( P pom ) + { + return pom.asStringBuilder().toString().replaceAll( "\\s", "" ); + } + }; + } +} diff --git a/src/test/resources/org/codehaus/mojo/set-scm-tag/issue-342-pom.xml b/src/test/resources/org/codehaus/mojo/set-scm-tag/issue-342-pom.xml new file mode 100644 index 0000000000..681851f29a --- /dev/null +++ b/src/test/resources/org/codehaus/mojo/set-scm-tag/issue-342-pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + default-group + default-artifact + 1.0 + pom + + + + + + + versions-maven-plugin + + newTag + connection + developerConnection + url + + + + + \ No newline at end of file diff --git a/src/test/resources/org/codehaus/mojo/set-scm-tag/pom.xml b/src/test/resources/org/codehaus/mojo/set-scm-tag/pom.xml new file mode 100644 index 0000000000..681851f29a --- /dev/null +++ b/src/test/resources/org/codehaus/mojo/set-scm-tag/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + default-group + default-artifact + 1.0 + pom + + + + + + + versions-maven-plugin + + newTag + connection + developerConnection + url + + + + + \ No newline at end of file