blob: 49df5abb89ed24a178d5b7e7967c0e2019361e14 [file] [log] [blame]
package org.apache.maven.shared.release.transform.jdom;
/*
* 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.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.model.Model;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.release.ReleaseExecutionException;
import org.apache.maven.shared.release.config.ReleaseDescriptor;
import org.apache.maven.shared.release.transform.ModelETL;
import org.apache.maven.shared.release.util.ReleaseUtil;
import org.codehaus.plexus.util.WriterFactory;
import org.jdom.CDATA;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.filter.ContentFilter;
import org.jdom.filter.ElementFilter;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
/**
* JDom implementation for extracting, transform, loading the Model (pom.xml)
*
* @author Robert Scholte
* @since 3.0
*/
public class JDomModelETL implements ModelETL
{
private ReleaseDescriptor releaseDescriptor;
private MavenProject project;
private Document document;
private String intro = null;
private String outtro = null;
private String ls = ReleaseUtil.LS;
public void setLs( String ls )
{
this.ls = ls;
}
public void setReleaseDescriptor( ReleaseDescriptor releaseDescriptor )
{
this.releaseDescriptor = releaseDescriptor;
}
public void setProject( MavenProject project )
{
this.project = project;
}
@Override
public void extract( File pomFile ) throws ReleaseExecutionException
{
try
{
String content = ReleaseUtil.readXmlFile( pomFile, ls );
// we need to eliminate any extra whitespace inside elements, as JDOM will nuke it
content = content.replaceAll( "<([^!][^>]*?)\\s{2,}([^>]*?)>", "<$1 $2>" );
content = content.replaceAll( "(\\s{2,})/>", "$1 />" );
SAXBuilder builder = new SAXBuilder();
document = builder.build( new StringReader( content ) );
// Normalize line endings to platform's style (XML processors like JDOM normalize line endings to "\n" as
// per section 2.11 of the XML spec)
normaliseLineEndings( document );
// rewrite DOM as a string to find differences, since text outside the root element is not tracked
StringWriter w = new StringWriter();
Format format = Format.getRawFormat();
format.setLineSeparator( ls );
XMLOutputter out = new XMLOutputter( format );
out.output( document.getRootElement(), w );
int index = content.indexOf( w.toString() );
if ( index >= 0 )
{
intro = content.substring( 0, index );
outtro = content.substring( index + w.toString().length() );
}
else
{
/*
* NOTE: Due to whitespace, attribute reordering or entity expansion the above indexOf test can easily
* fail. So let's try harder. Maybe some day, when JDOM offers a StaxBuilder and this builder employes
* XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE, this whole mess can be avoided.
*/
// CHECKSTYLE_OFF: LocalFinalVariableName
final String SPACE = "\\s++";
final String XML = "<\\?(?:(?:[^\"'>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+>";
final String INTSUB = "\\[(?:(?:[^\"'\\]]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+\\]";
final String DOCTYPE =
"<!DOCTYPE(?:(?:[^\"'\\[>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:" + INTSUB + "))*+>";
final String PI = XML;
final String COMMENT = "<!--(?:[^-]|(?:-[^-]))*+-->";
final String INTRO =
"(?:(?:" + SPACE + ")|(?:" + XML + ")|(?:" + DOCTYPE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
final String OUTRO = "(?:(?:" + SPACE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
final String POM = "(?s)(" + INTRO + ")(.*?)(" + OUTRO + ")";
// CHECKSTYLE_ON: LocalFinalVariableName
Matcher matcher = Pattern.compile( POM ).matcher( content );
if ( matcher.matches() )
{
intro = matcher.group( 1 );
outtro = matcher.group( matcher.groupCount() );
}
}
}
catch ( JDOMException | IOException e )
{
throw new ReleaseExecutionException( "Error reading POM: " + e.getMessage(), e );
}
}
@Override
public void transform()
{
}
@Override
public void load( File targetFile ) throws ReleaseExecutionException
{
writePom( targetFile, document, releaseDescriptor, project.getModelVersion(), intro, outtro );
}
@Override
public Model getModel()
{
return new JDomModel( document );
}
private void normaliseLineEndings( Document document )
{
for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.COMMENT ) ); i.hasNext(); )
{
Comment c = (Comment) i.next();
c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
}
for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.CDATA ) ); i.hasNext(); )
{
CDATA c = (CDATA) i.next();
c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
}
}
private void writePom( File pomFile, Document document, ReleaseDescriptor releaseDescriptor, String modelVersion,
String intro, String outtro )
throws ReleaseExecutionException
{
Element rootElement = document.getRootElement();
if ( releaseDescriptor.isAddSchema() )
{
Namespace pomNamespace = Namespace.getNamespace( "", "http://maven.apache.org/POM/" + modelVersion );
rootElement.setNamespace( pomNamespace );
Namespace xsiNamespace = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
rootElement.addNamespaceDeclaration( xsiNamespace );
if ( rootElement.getAttribute( "schemaLocation", xsiNamespace ) == null )
{
rootElement.setAttribute( "schemaLocation", "http://maven.apache.org/POM/" + modelVersion
+ " https://maven.apache.org/xsd/maven-" + modelVersion + ".xsd", xsiNamespace );
}
// the empty namespace is considered equal to the POM namespace, so match them up to avoid extra xmlns=""
ElementFilter elementFilter = new ElementFilter( Namespace.getNamespace( "" ) );
for ( Iterator<?> i = rootElement.getDescendants( elementFilter ); i.hasNext(); )
{
Element e = (Element) i.next();
e.setNamespace( pomNamespace );
}
}
try ( Writer writer = WriterFactory.newXmlWriter( pomFile ) )
{
if ( intro != null )
{
writer.write( intro );
}
Format format = Format.getRawFormat();
format.setLineSeparator( ls );
XMLOutputter out = new XMLOutputter( format );
out.output( document.getRootElement(), writer );
if ( outtro != null )
{
writer.write( outtro );
}
}
catch ( IOException e )
{
throw new ReleaseExecutionException( "Error writing POM: " + e.getMessage(), e );
}
}
}