[MNG-7182] Use the MX xpp parser instead of a STAX transformation (#486)

This commit is contained in:
Guillaume Nodet 2021-11-29 13:31:55 +01:00 committed by GitHub
parent 98a6c4f14c
commit ae8aebea19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1300 additions and 2613 deletions

View File

@ -23,91 +23,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import org.apache.maven.model.building.AbstractModelSourceTransformer;
import org.apache.maven.model.building.DefaultBuildPomXMLFilterFactory;
import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.xml.internal.DefaultConsumerPomXMLFilterFactory;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.apache.maven.model.transform.RawToConsumerPomXMLFilterFactory;
import org.apache.maven.model.transform.pull.XmlUtils;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
import org.codehaus.plexus.util.xml.pull.MXParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
class ConsumerModelSourceTransformer extends AbstractModelSourceTransformer
class ConsumerModelSourceTransformer
{
@Override
protected AbstractSAXFilter getSAXFilter( Path pomFile,
TransformerContext context,
Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerConfigurationException, SAXException, ParserConfigurationException
public InputStream transform( Path pomFile, TransformerContext context )
throws IOException, XmlPullParserException
{
return new DefaultConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context,
lexicalHandlerConsumer, true ) ).get( pomFile );
}
XmlStreamReader reader = ReaderFactory.newXmlReader( Files.newInputStream( pomFile ) );
XmlPullParser parser = new MXParser( EntityReplacementMap.defaultEntityReplacementMap );
parser.setInput( reader );
parser = new RawToConsumerPomXMLFilterFactory( new DefaultBuildPomXMLFilterFactory( context, true ) )
.get( parser, pomFile );
/**
* This transformer will ensure that encoding and version are kept.
* However, it cannot prevent:
* <ul>
* <li>attributes will be on one line</li>
* <li>Unnecessary whitespace before the rootelement will be removed</li>
* </ul>
*/
@Override
protected TransformerHandler getTransformerHandler( Path pomFile )
throws IOException, org.apache.maven.model.building.TransformerException
{
final TransformerHandler transformerHandler;
final SAXTransformerFactory transformerFactory = getTransformerFactory();
// Keep same encoding+version
try ( InputStream input = Files.newInputStream( pomFile ) )
{
XMLStreamReader streamReader =
XMLInputFactory.newFactory().createXMLStreamReader( input );
transformerHandler = transformerFactory.newTransformerHandler();
final String encoding = streamReader.getCharacterEncodingScheme();
final String version = streamReader.getVersion();
Transformer transformer = transformerHandler.getTransformer();
transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
if ( encoding == null && version == null )
{
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
}
else
{
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
if ( encoding != null )
{
transformer.setOutputProperty( OutputKeys.ENCODING, encoding );
}
if ( version != null )
{
transformer.setOutputProperty( OutputKeys.VERSION, version );
}
}
}
catch ( XMLStreamException | TransformerConfigurationException e )
{
throw new org.apache.maven.model.building.TransformerException(
"Failed to detect XML encoding and version", e );
}
return transformerHandler;
return XmlUtils.writeDocument( reader, parser );
}
}

View File

@ -19,6 +19,19 @@ package org.apache.maven.internal.aether;
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
import org.apache.maven.bridge.MavenRepositorySystem;
@ -26,7 +39,6 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.feature.Features;
import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.building.TransformerException;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy;
@ -37,6 +49,7 @@ import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
@ -59,19 +72,6 @@ import org.eclipse.sisu.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* @since 3.3.0
*/
@ -310,7 +310,7 @@ public class DefaultRepositorySystemSessionFactory
{
return new ConsumerModelSourceTransformer().transform( pomFile.toPath(), context );
}
catch ( TransformerException e )
catch ( XmlPullParserException e )
{
throw new TransformException( e );
}

View File

@ -19,35 +19,6 @@ package org.apache.maven.model.building;
* under the License.
*/
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.sax.CommentRenormalizer;
import org.apache.maven.model.transform.sax.Factories;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.LexicalHandler;
/**
* Offers a transformation implementation based on PipelineStreams.
* Subclasses are responsible for providing the right SAXFilter.
@ -58,181 +29,5 @@ import org.xml.sax.ext.LexicalHandler;
public abstract class AbstractModelSourceTransformer
implements ModelSourceTransformer
{
private static final AtomicInteger TRANSFORM_THREAD_COUNTER = new AtomicInteger();
private final TransformerFactory transformerFactory = Factories.newTransformerFactory();
protected abstract AbstractSAXFilter getSAXFilter( Path pomFile,
TransformerContext context,
Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerConfigurationException, SAXException, ParserConfigurationException;
protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
{
return outputStream;
}
public SAXTransformerFactory getTransformerFactory()
{
return ( SAXTransformerFactory ) transformerFactory;
}
protected TransformerHandler getTransformerHandler( Path pomFile )
throws IOException, org.apache.maven.model.building.TransformerException
{
return null;
}
@Override
public final InputStream transform( Path pomFile, TransformerContext context )
throws IOException, org.apache.maven.model.building.TransformerException
{
final TransformerHandler transformerHandler = getTransformerHandler( pomFile );
final PipedOutputStream pout = new PipedOutputStream();
OutputStream out = filterOutputStream( pout, pomFile );
final javax.xml.transform.Result result;
final Consumer<LexicalHandler> lexConsumer;
if ( transformerHandler == null )
{
result = new StreamResult( out );
lexConsumer = null;
}
else
{
result = new SAXResult( transformerHandler );
lexConsumer = l -> ( (SAXResult) result ).setLexicalHandler( new CommentRenormalizer( l ) );
transformerHandler.setResult( new StreamResult( out ) );
}
final AbstractSAXFilter filter;
try
{
filter = getSAXFilter( pomFile, context, lexConsumer );
filter.setLexicalHandler( transformerHandler );
// By default errors are written to stderr.
// Hence set custom errorHandler to reduce noice
filter.setErrorHandler( new ErrorHandler()
{
@Override
public void warning( SAXParseException exception )
throws SAXException
{
throw exception;
}
@Override
public void fatalError( SAXParseException exception )
throws SAXException
{
throw exception;
}
@Override
public void error( SAXParseException exception )
throws SAXException
{
throw exception;
}
} );
}
catch ( TransformerConfigurationException | SAXException | ParserConfigurationException e )
{
throw new org.apache.maven.model.building.TransformerException( e );
}
final SAXSource transformSource =
new SAXSource( filter, new org.xml.sax.InputSource( Files.newInputStream( pomFile ) ) );
IOExceptionHandler eh = new IOExceptionHandler();
// Ensure pipedStreams are connected before the transformThread starts!!
final PipedInputStream pipedInputStream = new PipedInputStream( pout );
Thread transformThread = new Thread( () ->
{
try ( PipedOutputStream pos = pout )
{
transformerFactory.newTransformer().transform( transformSource, result );
}
catch ( TransformerException | IOException e )
{
eh.uncaughtException( Thread.currentThread(), e );
}
}, "TransformThread-" + TRANSFORM_THREAD_COUNTER.incrementAndGet() );
transformThread.setUncaughtExceptionHandler( eh );
transformThread.setDaemon( true );
transformThread.start();
return new ThreadAwareInputStream( pipedInputStream, eh );
}
private static class IOExceptionHandler
implements Thread.UncaughtExceptionHandler, AutoCloseable
{
private volatile Throwable cause;
@Override
public void uncaughtException( Thread t, Throwable e )
{
try
{
throw e;
}
catch ( TransformerException | IOException | RuntimeException | Error allGood )
{
// all good
this.cause = e;
}
catch ( Throwable notGood )
{
throw new AssertionError( "Unexpected Exception", e );
}
}
@Override
public void close()
throws IOException
{
if ( cause != null )
{
try
{
throw cause;
}
catch ( IOException | RuntimeException | Error e )
{
throw e;
}
catch ( Throwable t )
{
// Any checked exception
throw new RuntimeException( "Failed to transform pom", t );
}
}
}
}
private class ThreadAwareInputStream
extends FilterInputStream
{
final IOExceptionHandler h;
protected ThreadAwareInputStream( InputStream in, IOExceptionHandler h )
{
super( in );
this.h = h;
}
@Override
public void close()
throws IOException
{
try ( IOExceptionHandler eh = h )
{
super.close();
}
}
}
}

View File

@ -19,24 +19,14 @@ package org.apache.maven.model.building;
* under the License.
*/
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.apache.maven.model.transform.BuildToRawPomXMLFilterFactory;
import org.apache.maven.model.transform.BuildToRawPomXMLFilterListener;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.eclipse.sisu.Nullable;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* ModelSourceTransformer for the build pom
@ -46,44 +36,15 @@ import org.xml.sax.ext.LexicalHandler;
*/
@Named
@Singleton
class BuildModelSourceTransformer extends AbstractModelSourceTransformer
class BuildModelSourceTransformer implements ModelSourceTransformer
{
@Inject
@Nullable
private BuildToRawPomXMLFilterListener xmlFilterListener;
protected AbstractSAXFilter getSAXFilter( Path pomFile,
TransformerContext context,
Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerConfigurationException, SAXException, ParserConfigurationException
@Override
public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
throws IOException, TransformerException
{
BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory =
new DefaultBuildPomXMLFilterFactory( context, lexicalHandlerConsumer, false );
new DefaultBuildPomXMLFilterFactory( context, false );
return buildPomXMLFilterFactory.get( pomFile );
}
@Override
protected OutputStream filterOutputStream( OutputStream outputStream, Path pomFile )
{
OutputStream out;
if ( xmlFilterListener != null )
{
out = new FilterOutputStream( outputStream )
{
@Override
public void write( byte[] b, int off, int len )
throws IOException
{
super.write( b, off, len );
xmlFilterListener.write( pomFile, b, off, len );
}
};
}
else
{
out = outputStream;
}
return out;
return buildPomXMLFilterFactory.get( parser, pomFile );
}
}

View File

@ -23,13 +23,11 @@ package org.apache.maven.model.building;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.maven.model.Model;
import org.apache.maven.model.transform.BuildToRawPomXMLFilterFactory;
import org.apache.maven.model.transform.RelativeProject;
import org.xml.sax.ext.LexicalHandler;
/**
* A BuildPomXMLFilterFactory which is context aware
@ -44,14 +42,12 @@ public class DefaultBuildPomXMLFilterFactory extends BuildToRawPomXMLFilterFacto
/**
*
* @param context a set of data to extract values from as required for the build pom
* @param lexicalHandlerConsumer the lexical handler consumer
* @param consume {@code true} if this factory is being used for creating the consumer pom, otherwise {@code false}
*/
public DefaultBuildPomXMLFilterFactory( TransformerContext context,
Consumer<LexicalHandler> lexicalHandlerConsumer,
boolean consume )
{
super( lexicalHandlerConsumer, consume );
super( consume );
this.context = context;
}

View File

@ -20,10 +20,10 @@ package org.apache.maven.model.building;
*/
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Default ModelSourceTransformer, provides pomFile as inputStream and ignores the context
*
@ -34,10 +34,10 @@ public class DefaultModelSourceTransformer implements ModelSourceTransformer
{
@Override
public InputStream transform( Path pomFile, TransformerContext context )
throws IOException, TransformerException
public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
throws IOException, TransformerException
{
return Files.newInputStream( pomFile );
return parser;
}
}

View File

@ -20,16 +20,18 @@ package org.apache.maven.model.building;
*/
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* The ModelSourceTransformer is a way to transform the local pom while streaming the input.
*
* The {@link #transform(Path, TransformerContext)} method uses a Path on purpose, to ensure the
* The {@link #transform(XmlPullParser, Path, TransformerContext)} method uses a Path on purpose, to ensure the
* local pom is the the original source.
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
public interface ModelSourceTransformer
@ -42,6 +44,6 @@ public interface ModelSourceTransformer
* @throws IOException if an I/O error occurs
* @throws TransformerException if the transformation fails
*/
InputStream transform( Path pomFile, TransformerContext context )
XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
throws IOException, TransformerException;
}

View File

@ -20,10 +20,12 @@ package org.apache.maven.model.io;
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
@ -35,11 +37,13 @@ import org.apache.maven.model.InputSource;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.ModelSourceTransformer;
import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.building.TransformerException;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.model.io.xpp3.MavenXpp3ReaderEx;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.pull.EntityReplacementMap;
import org.codehaus.plexus.util.xml.pull.MXParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
@ -54,6 +58,10 @@ public class DefaultModelReader
{
private final ModelSourceTransformer transformer;
private Method readMethod;
private Method readMethodEx;
@Inject
public DefaultModelReader( ModelSourceTransformer transformer )
{
@ -66,28 +74,9 @@ public class DefaultModelReader
{
Objects.requireNonNull( input, "input cannot be null" );
TransformerContext context = getTransformerContext( options );
final InputStream is;
if ( context == null )
try ( XmlStreamReader in = ReaderFactory.newXmlReader( input ) )
{
is = new FileInputStream( input );
}
else
{
try
{
is = transformer.transform( input.toPath(), context );
}
catch ( TransformerException e )
{
throw new IOException( "Failed to transform " + input, e );
}
}
try ( InputStream in = is )
{
Model model = read( is, options );
Model model = read( in, input.toPath(), options );
model.setPomFile( input );
@ -103,7 +92,7 @@ public class DefaultModelReader
try ( Reader in = input )
{
return read( in, isStrict( options ), getSource( options ) );
return read( in, null, options );
}
}
@ -115,7 +104,7 @@ public class DefaultModelReader
try ( XmlStreamReader in = ReaderFactory.newXmlReader( input ) )
{
return read( in, isStrict( options ), getSource( options ) );
return read( in, null, options );
}
}
@ -137,24 +126,80 @@ public class DefaultModelReader
return (TransformerContext) value;
}
private Model read( Reader reader, boolean strict, InputSource source )
private Model read( Reader reader, Path pomFile, Map<String, ?> options )
throws IOException
{
try
{
if ( source != null )
XmlPullParser parser = new MXParser( EntityReplacementMap.defaultEntityReplacementMap );
parser.setInput( reader );
TransformerContext context = getTransformerContext( options );
XmlPullParser transformingParser = context != null
? transformer.transform( parser, pomFile, context ) : parser;
InputSource source = getSource( options );
boolean strict = isStrict( options );
try
{
return new MavenXpp3ReaderEx().read( reader, strict, source );
if ( source != null )
{
return readModelEx( transformingParser, source, strict );
}
else
{
return readModel( transformingParser, strict );
}
}
else
catch ( InvocationTargetException e )
{
return new MavenXpp3Reader().read( reader, strict );
Throwable cause = e.getCause();
if ( cause instanceof Exception )
{
throw ( Exception ) cause;
}
throw e;
}
}
catch ( XmlPullParserException e )
{
throw new ModelParseException( e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e );
}
catch ( IOException e )
{
throw e;
}
catch ( Exception e )
{
throw new IOException( "Unable to transform pom", e );
}
}
private Model readModel( XmlPullParser parser, boolean strict )
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
if ( readMethod == null )
{
readMethod = MavenXpp3Reader.class.getDeclaredMethod( "read", XmlPullParser.class, boolean.class );
readMethod.setAccessible( true );
}
MavenXpp3Reader mr = new MavenXpp3Reader();
Object model = readMethod.invoke( mr, parser, strict );
return ( Model ) model;
}
private Model readModelEx( XmlPullParser parser, InputSource source, boolean strict )
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
if ( readMethodEx == null )
{
readMethodEx = MavenXpp3ReaderEx.class.getDeclaredMethod( "read",
XmlPullParser.class, boolean.class, InputSource.class );
readMethodEx.setAccessible( true );
}
MavenXpp3ReaderEx mr = new MavenXpp3ReaderEx();
Object model = readMethodEx.invoke( mr, parser, strict, source );
return ( Model ) model;
}
}

View File

@ -22,22 +22,16 @@ package org.apache.maven.model.inheritance;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.AbstractModelSourceTransformer;
import org.apache.maven.model.building.SimpleProblemCollector;
import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.building.TransformerException;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.model.io.DefaultModelWriter;
import org.apache.maven.model.io.ModelWriter;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xmlunit.matchers.CompareMatcher;
@ -64,9 +58,8 @@ public class DefaultInheritanceAssemblerTest
reader = new DefaultModelReader( new AbstractModelSourceTransformer()
{
@Override
protected AbstractSAXFilter getSAXFilter( Path pomFile, TransformerContext context,
Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerConfigurationException, SAXException, ParserConfigurationException
public XmlPullParser transform( XmlPullParser parser, Path pomFile, TransformerContext context )
throws IOException, TransformerException
{
return null;
}

View File

@ -32,6 +32,10 @@ under the License.
<name>Maven Model XML Transform</name>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>

View File

@ -1,285 +0,0 @@
package org.apache.maven.model.transform;
/*
* 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.sax.SAXEvent;
import org.apache.maven.model.transform.sax.SAXEventFactory;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
/**
* Builds up a list of SAXEvents, which will be executed with {@link #executeEvents()}
*
* @author Robert Scholte
* @since 4.0.0
*/
abstract class AbstractEventXMLFilter extends AbstractSAXFilter
{
private Queue<SAXEvent> saxEvents = new ArrayDeque<>();
private SAXEventFactory eventFactory;
// characters BEFORE startElement must get state of startingElement
// this way removing based on state keeps correct formatting
private List<SAXEvent> charactersSegments = new ArrayList<>();
private boolean lockCharacters = false;
protected abstract boolean isParsing();
protected abstract String getState();
protected boolean acceptEvent( String state )
{
return true;
}
AbstractEventXMLFilter()
{
super();
}
AbstractEventXMLFilter( AbstractSAXFilter parent )
{
super( parent );
}
private SAXEventFactory getEventFactory()
{
if ( eventFactory == null )
{
eventFactory = SAXEventFactory.newInstance( getContentHandler(), getLexicalHandler() );
}
return eventFactory;
}
private void processEvent( final SAXEvent event )
throws SAXException
{
if ( isParsing() )
{
final String eventState = getState();
if ( !lockCharacters )
{
charactersSegments.forEach( e ->
saxEvents.add( () ->
{
if ( acceptEvent( eventState ) )
{
e.execute();
}
} ) );
charactersSegments.clear();
}
saxEvents.add( () ->
{
if ( acceptEvent( eventState ) )
{
event.execute();
}
} );
}
else
{
event.execute();
}
}
/**
* Should be used to include extra events before a closing element.
* This is a lightweight solution to keep the correct indentation.
*/
protected Includer include()
{
this.lockCharacters = true;
return () -> lockCharacters = false;
}
protected final void executeEvents() throws SAXException
{
final String eventState = getState();
charactersSegments.forEach( e ->
saxEvents.add( () ->
{
if ( acceptEvent( eventState ) )
{
e.execute();
}
} ) );
charactersSegments.clear();
// not with streams due to checked SAXException
while ( !saxEvents.isEmpty() )
{
saxEvents.poll().execute();
}
}
@Override
public void setDocumentLocator( Locator locator )
{
try
{
processEvent( getEventFactory().setDocumentLocator( locator ) );
}
catch ( SAXException e )
{
// noop, setDocumentLocator can never throw a SAXException
}
}
@Override
public void startDocument() throws SAXException
{
processEvent( getEventFactory().startDocument() );
}
@Override
public void endDocument() throws SAXException
{
processEvent( getEventFactory().endDocument() );
}
@Override
public void startPrefixMapping( String prefix, String uri ) throws SAXException
{
processEvent( getEventFactory().startPrefixMapping( prefix, uri ) );
}
@Override
public void endPrefixMapping( String prefix ) throws SAXException
{
processEvent( getEventFactory().endPrefixMapping( prefix ) );
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts ) throws SAXException
{
processEvent( getEventFactory().startElement( uri, localName, qName, atts ) );
}
@Override
public void endElement( String uri, String localName, String qName ) throws SAXException
{
processEvent( getEventFactory().endElement( uri, localName, qName ) );
}
@Override
public void characters( char[] ch, int start, int length ) throws SAXException
{
if ( lockCharacters )
{
processEvent( getEventFactory().characters( ch, start, length ) );
}
else if ( isParsing() )
{
this.charactersSegments.add( getEventFactory().characters( ch, start, length ) );
}
else
{
super.characters( ch, start, length );
}
}
@Override
public void ignorableWhitespace( char[] ch, int start, int length ) throws SAXException
{
processEvent( getEventFactory().ignorableWhitespace( ch, start, length ) );
}
@Override
public void processingInstruction( String target, String data ) throws SAXException
{
processEvent( getEventFactory().processingInstruction( target, data ) );
}
@Override
public void skippedEntity( String name ) throws SAXException
{
processEvent( getEventFactory().skippedEntity( name ) );
}
@Override
public void startDTD( String name, String publicId, String systemId ) throws SAXException
{
processEvent( getEventFactory().startCDATA() );
}
@Override
public void endDTD() throws SAXException
{
processEvent( getEventFactory().endDTD() );
}
@Override
public void startEntity( String name ) throws SAXException
{
processEvent( getEventFactory().startEntity( name ) );
}
@Override
public void endEntity( String name ) throws SAXException
{
processEvent( getEventFactory().endEntity( name ) );
}
@Override
public void startCDATA()
throws SAXException
{
processEvent( getEventFactory().startCDATA() );
}
@Override
public void endCDATA()
throws SAXException
{
processEvent( getEventFactory().endCDATA() );
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
processEvent( getEventFactory().comment( ch, start, length ) );
}
/**
* AutoCloseable with a close method that doesn't throw an exception
*
* @author Robert Scholte
*
*/
@FunctionalInterface
protected interface Includer extends AutoCloseable
{
void close();
}
}

View File

@ -1,58 +0,0 @@
package org.apache.maven.model.transform;
/*
* 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.xml.sax.XMLReader;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
/**
* Filter to adjust pom on filesystem before being processed for effective pom.
* There should only be 1 BuildToRawPomXMLFilter, so the same is being used by both
* org.apache.maven.model.building.DefaultModelBuilder.transformData(InputStream) and
* org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory.newFileTransformerManager()
*
* @author Robert Scholte
* @since 4.0.0
*/
public class BuildToRawPomXMLFilter extends AbstractSAXFilter
{
BuildToRawPomXMLFilter()
{
super();
}
BuildToRawPomXMLFilter( AbstractSAXFilter parent )
{
super( parent );
}
/**
* Don't allow overwriting parent
*/
@Override
public final void setParent( XMLReader parent )
{
if ( getParent() == null )
{
super.setParent( parent );
}
}
}

View File

@ -22,17 +22,9 @@ package org.apache.maven.model.transform;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.sax.Factories;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Base implementation for providing the BuildToRawPomXML.
@ -44,74 +36,42 @@ public class BuildToRawPomXMLFilterFactory
{
private final boolean consume;
private final Consumer<LexicalHandler> lexicalHandlerConsumer;
public BuildToRawPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer )
public BuildToRawPomXMLFilterFactory()
{
this( lexicalHandlerConsumer, false );
this( false );
}
public BuildToRawPomXMLFilterFactory( Consumer<LexicalHandler> lexicalHandlerConsumer, boolean consume )
public BuildToRawPomXMLFilterFactory( boolean consume )
{
this.lexicalHandlerConsumer = lexicalHandlerConsumer;
this.consume = consume;
}
/**
*
* @param projectFile will be used by ConsumerPomXMLFilter to get the right filter
* @throws SAXException
* @throws ParserConfigurationException
* @throws TransformerConfigurationException
*/
public final BuildToRawPomXMLFilter get( Path projectFile )
throws SAXException, ParserConfigurationException, TransformerConfigurationException
public final XmlPullParser get( XmlPullParser orgParser, Path projectFile )
{
AbstractSAXFilter parent = new AbstractSAXFilter();
parent.setParent( getXMLReader() );
if ( lexicalHandlerConsumer != null )
{
lexicalHandlerConsumer.accept( parent );
}
XmlPullParser parser = orgParser;
if ( getDependencyKeyToVersionMapper() != null )
{
ReactorDependencyXMLFilter reactorDependencyXMLFilter =
new ReactorDependencyXMLFilter( getDependencyKeyToVersionMapper() );
reactorDependencyXMLFilter.setParent( parent );
parent.setLexicalHandler( reactorDependencyXMLFilter );
parent = reactorDependencyXMLFilter;
parser = new ReactorDependencyXMLFilter( parser, getDependencyKeyToVersionMapper() );
}
if ( getRelativePathMapper() != null )
{
ParentXMLFilter parentFilter = new ParentXMLFilter( getRelativePathMapper() );
parentFilter.setProjectPath( projectFile.getParent() );
parentFilter.setParent( parent );
parent.setLexicalHandler( parentFilter );
parent = parentFilter;
parser = new ParentXMLFilter( parser, getRelativePathMapper(), projectFile.getParent() );
}
CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( consume );
CiFriendlyXMLFilter ciFriendlyFilter = new CiFriendlyXMLFilter( parser, consume );
getChangelist().ifPresent( ciFriendlyFilter::setChangelist );
getRevision().ifPresent( ciFriendlyFilter::setRevision );
getSha1().ifPresent( ciFriendlyFilter::setSha1 );
parser = ciFriendlyFilter;
if ( ciFriendlyFilter.isSet() )
{
ciFriendlyFilter.setParent( parent );
parent.setLexicalHandler( ciFriendlyFilter );
parent = ciFriendlyFilter;
}
return new BuildToRawPomXMLFilter( parent );
}
private XMLReader getXMLReader() throws SAXException, ParserConfigurationException
{
XMLReader xmlReader = Factories.newXMLReader();
xmlReader.setFeature( "http://xml.org/sax/features/namespaces", true );
return xmlReader;
return parser;
}
/**

View File

@ -1,42 +0,0 @@
package org.apache.maven.model.transform;
/*
* 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.Path;
/**
* Listener can be used to capture the result of the transformation of build to raw POM.
*
* @author Robert Scholte
* @since 4.0.0
*/
@FunctionalInterface
public interface BuildToRawPomXMLFilterListener
{
/**
* Captures the result of the XML transformation
*
* @param pomFile the original to being transformed
* @param b the byte array
* @param off the offset
* @param len the length
*/
void write( Path pomFile, byte[] b, int off, int len );
}

View File

@ -19,37 +19,29 @@ package org.apache.maven.model.transform;
* under the License.
*/
import java.util.List;
import java.util.function.Function;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.apache.maven.model.transform.pull.NodeBufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Resolves all ci-friendly properties occurrences between version-tags
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
class CiFriendlyXMLFilter
extends AbstractSAXFilter
extends NodeBufferingParser
{
private final boolean replace;
private Function<String, String> replaceChain = Function.identity();
private String characters;
private boolean parseVersion;
CiFriendlyXMLFilter( boolean replace )
CiFriendlyXMLFilter( XmlPullParser xmlPullParser, boolean replace )
{
this.replace = replace;
}
CiFriendlyXMLFilter( AbstractSAXFilter parent, boolean replace )
{
super( parent );
super( xmlPullParser, "version" );
this.replace = replace;
}
@ -80,52 +72,16 @@ class CiFriendlyXMLFilter
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
protected void process( List<Event> buffer )
{
if ( parseVersion )
for ( Event event : buffer )
{
this.characters = nullSafeAppend( characters, new String( ch, start, length ) );
}
else
{
super.characters( ch, start, length );
}
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts )
throws SAXException
{
if ( !parseVersion && "version".equals( localName ) )
{
parseVersion = true;
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
if ( parseVersion )
{
// assuming this has the best performance
if ( replace && characters != null && characters.contains( "${" ) )
if ( event.event == TEXT && replace && event.text.contains( "${" ) )
{
char[] ch = replaceChain.apply( characters ).toCharArray();
super.characters( ch, 0, ch.length );
event.text = replaceChain.apply( event.text );
}
else
{
char[] ch = characters.toCharArray();
super.characters( ch, 0, ch.length );
}
characters = null;
parseVersion = false;
pushEvent( event );
}
super.endElement( uri, localName, qName );
}
}

View File

@ -1,87 +0,0 @@
package org.apache.maven.model.transform;
/*
* 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.util.Objects;
/**
*
* @author Robert Scholte
* @since 4.0.0
*/
public class DependencyKey
{
private final String groupId;
private final String artifactId;
private final int hashCode;
public DependencyKey( String groupId, String artifactId )
{
this.groupId = groupId;
this.artifactId = artifactId;
this.hashCode = Objects.hash( artifactId, groupId );
}
public String getGroupId()
{
return groupId;
}
public String getArtifactId()
{
return artifactId;
}
@Override
public int hashCode()
{
return hashCode;
}
@Override
public boolean equals( Object obj )
{
if ( this == obj )
{
return true;
}
if ( obj == null )
{
return false;
}
if ( getClass() != obj.getClass() )
{
return false;
}
DependencyKey other = (DependencyKey) obj;
if ( !Objects.equals( artifactId, other.artifactId ) )
{
return false;
}
return Objects.equals( groupId, other.groupId );
}
}

View File

@ -19,23 +19,23 @@ package org.apache.maven.model.transform;
* under the License.
*/
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLFilter;
import org.apache.maven.model.transform.pull.BufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* This filter will skip all following filters and write directly to the output.
* Should be used in case of a DOM that should not be effected by other filters, even though the elements match
* Should be used in case of a DOM that should not be effected by other filters, even though the elements match.
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
class FastForwardFilter extends AbstractSAXFilter
class FastForwardFilter extends BufferingParser
{
/**
* DOM elements of pom
@ -53,75 +53,51 @@ class FastForwardFilter extends AbstractSAXFilter
private int domDepth = 0;
private ContentHandler originalHandler;
FastForwardFilter()
FastForwardFilter( XmlPullParser xmlPullParser )
{
super();
}
FastForwardFilter( AbstractSAXFilter parent )
{
super( parent );
super( xmlPullParser );
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts )
throws SAXException
protected boolean accept() throws XmlPullParserException, IOException
{
super.startElement( uri, localName, qName, atts );
if ( domDepth > 0 )
if ( xmlPullParser.getEventType() == START_TAG )
{
domDepth++;
}
else
{
final String key = state.peek() + '.' + localName;
switch ( key )
String localName = xmlPullParser.getName();
if ( domDepth > 0 )
{
case "execution.configuration":
case "plugin.configuration":
case "plugin.goals":
case "profile.reports":
case "project.reports":
case "reportSet.configuration":
domDepth++;
originalHandler = getContentHandler();
ContentHandler outputContentHandler = getContentHandler();
while ( outputContentHandler instanceof XMLFilter )
{
outputContentHandler = ( (XMLFilter) outputContentHandler ).getContentHandler();
}
setContentHandler( outputContentHandler );
break;
default:
break;
domDepth++;
}
state.push( localName );
else
{
final String key = state.peek() + '/' + localName;
switch ( key )
{
case "execution/configuration":
case "plugin/configuration":
case "plugin/goals":
case "profile/reports":
case "project/reports":
case "reportSet/configuration":
domDepth++;
disable();
break;
default:
break;
}
}
state.add( localName );
}
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
if ( domDepth > 0 )
else if ( xmlPullParser.getEventType() == END_TAG )
{
domDepth--;
if ( domDepth == 0 )
{
setContentHandler( originalHandler );
enable();
}
}
else
{
state.pop();
}
super.endElement( uri, localName, qName );
return true;
}
}

View File

@ -19,93 +19,29 @@ package org.apache.maven.model.transform;
* under the License.
*/
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.util.List;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.pull.NodeBufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Remove all modules, this is just buildtime information
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
class ModulesXMLFilter
extends AbstractEventXMLFilter
extends NodeBufferingParser
{
private boolean parsingModules;
private String state;
ModulesXMLFilter()
ModulesXMLFilter( XmlPullParser xmlPullParser )
{
super();
super( xmlPullParser, "modules" );
}
ModulesXMLFilter( AbstractSAXFilter parent )
protected void process( List<Event> buffer )
{
super( parent );
// Do nothing, as we want to delete those nodes completely
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts )
throws SAXException
{
if ( !parsingModules && "modules".equals( localName ) )
{
parsingModules = true;
}
if ( parsingModules )
{
state = localName;
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
if ( parsingModules )
{
switch ( localName )
{
case "modules":
executeEvents();
parsingModules = false;
break;
default:
super.endElement( uri, localName, qName );
break;
}
}
else
{
super.endElement( uri, localName, qName );
}
// for this simple structure resetting to modules it sufficient
state = "modules";
}
@Override
protected boolean isParsing()
{
return parsingModules;
}
@Override
protected String getState()
{
return state;
}
@Override
protected boolean acceptEvent( String state )
{
return false;
}
}

View File

@ -22,14 +22,13 @@ package org.apache.maven.model.transform;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.apache.maven.model.transform.sax.SAXEventUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.apache.maven.model.transform.pull.NodeBufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* <p>
@ -40,167 +39,117 @@ import org.xml.sax.SAXException;
* </p>
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
class ParentXMLFilter
extends AbstractEventXMLFilter
extends NodeBufferingParser
{
private boolean parsingParent;
// states
private String state;
// whiteSpace after <parent>, to be used to position <version>
private String parentWhitespace = "";
private String groupId;
private String artifactId;
private String relativePath;
private boolean hasVersion;
private boolean hasRelativePath;
private Optional<RelativeProject> resolvedParent;
private final Function<Path, Optional<RelativeProject>> relativePathMapper;
private Path projectPath;
private final Path projectPath;
/**
* @param relativePathMapper
*/
ParentXMLFilter( Function<Path, Optional<RelativeProject>> relativePathMapper )
ParentXMLFilter( XmlPullParser parser,
Function<Path, Optional<RelativeProject>> relativePathMapper, Path projectPath )
{
super( parser, "parent" );
this.relativePathMapper = relativePathMapper;
}
public void setProjectPath( Path projectPath )
{
this.projectPath = projectPath;
}
@Override
protected boolean isParsing()
protected void process( List<Event> buffer )
{
return parsingParent;
}
@Override
protected String getState()
{
return state;
}
@Override
public void startElement( String uri, final String localName, String qName, Attributes atts )
throws SAXException
{
if ( !parsingParent && "parent".equals( localName ) )
String tagName = null;
String groupId = null;
String artifactId = null;
String version = null;
String relativePath = null;
String whitespaceAfterParentStart = "";
boolean hasVersion = false;
boolean hasRelativePath = false;
for ( int i = 0; i < buffer.size(); i++ )
{
parsingParent = true;
}
if ( parsingParent )
{
state = localName;
hasVersion |= "version".equals( localName );
// can be set to empty on purpose to enforce repository download
hasRelativePath |= "relativePath".equals( localName );
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
if ( parsingParent )
{
final String eventState = state;
final String charSegment = new String( ch, start, length );
switch ( eventState )
Event event = buffer.get( i );
if ( event.event == START_TAG )
{
case "parent":
parentWhitespace = nullSafeAppend( parentWhitespace, charSegment );
break;
case "relativePath":
relativePath = nullSafeAppend( relativePath, charSegment );
break;
case "groupId":
groupId = nullSafeAppend( groupId, charSegment );
break;
case "artifactId":
artifactId = nullSafeAppend( artifactId, charSegment );
break;
default:
break;
tagName = event.name;
hasVersion |= "version".equals( tagName );
hasRelativePath |= "relativePath".equals( tagName );
}
else if ( event.event == TEXT )
{
if ( event.text.matches( "\\s+" ) )
{
if ( whitespaceAfterParentStart.isEmpty() )
{
whitespaceAfterParentStart = event.text;
}
}
else if ( "groupId".equals( tagName ) )
{
groupId = nullSafeAppend( groupId, event.text );
}
else if ( "artifactId".equals( tagName ) )
{
artifactId = nullSafeAppend( artifactId, event.text );
}
else if ( "relativePath".equals( tagName ) )
{
relativePath = nullSafeAppend( relativePath, event.text );
}
else if ( "version".equals( tagName ) )
{
version = nullSafeAppend( version, event.text );
}
}
else if ( event.event == END_TAG && "parent".equals( event.name ) )
{
Optional<RelativeProject> resolvedParent;
if ( !hasVersion && ( !hasRelativePath || relativePath != null ) )
{
Path relPath = Paths.get( Objects.toString( relativePath, "../pom.xml" ) );
resolvedParent = resolveRelativePath( relPath, groupId, artifactId );
}
else
{
resolvedParent = Optional.empty();
}
if ( !hasVersion && resolvedParent.isPresent() )
{
int pos = buffer.get( i - 1 ).event == TEXT ? i - 1 : i;
Event e = new Event();
e.event = TEXT;
e.text = whitespaceAfterParentStart;
buffer.add( pos++, e );
e = new Event();
e.event = START_TAG;
e.namespace = buffer.get( 0 ).namespace;
e.prefix = buffer.get( 0 ).prefix;
e.name = "version";
buffer.add( pos++, e );
e = new Event();
e.event = TEXT;
e.text = resolvedParent.get().getVersion();
buffer.add( pos++, e );
e = new Event();
e.event = END_TAG;
e.name = "version";
e.namespace = buffer.get( 0 ).namespace;
e.prefix = buffer.get( 0 ).prefix;
buffer.add( pos++, e );
}
break;
}
}
buffer.forEach( this::pushEvent );
}
super.characters( ch, start, length );
}
@Override
public void endElement( String uri, final String localName, String qName )
throws SAXException
{
if ( parsingParent )
{
switch ( localName )
{
case "parent":
if ( !hasVersion && ( !hasRelativePath || relativePath != null ) )
{
resolvedParent =
resolveRelativePath( Paths.get( Objects.toString( relativePath, "../pom.xml" ) ) );
}
else
{
resolvedParent = Optional.empty();
}
if ( !hasVersion && resolvedParent.isPresent() )
{
try ( Includer i = super.include() )
{
super.characters( parentWhitespace.toCharArray(), 0,
parentWhitespace.length() );
String versionQName = SAXEventUtils.renameQName( qName, "version" );
super.startElement( uri, "version", versionQName, null );
String resolvedParentVersion = resolvedParent.get().getVersion();
super.characters( resolvedParentVersion.toCharArray(), 0,
resolvedParentVersion.length() );
super.endElement( uri, "version", versionQName );
}
}
super.executeEvents();
parsingParent = false;
break;
default:
// marker?
break;
}
}
super.endElement( uri, localName, qName );
state = "";
}
protected Optional<RelativeProject> resolveRelativePath( Path relativePath )
protected Optional<RelativeProject> resolveRelativePath( Path relativePath, String groupId, String artifactId )
{
Path pomPath = projectPath.resolve( relativePath );
if ( Files.isDirectory( pomPath ) )
@ -222,4 +171,5 @@ class ParentXMLFilter
}
return Optional.empty();
}
}

View File

@ -1,62 +0,0 @@
package org.apache.maven.model.transform;
/*
* 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.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
/**
* XML Filter to transform pom.xml to consumer pom.
* This often means stripping of build-specific information.
* When extra information is required during filtering it is probably a member of the BuildPomXMLFilter
*
* This filter is used at one location:
* - org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory when publishing POM files.
*
* @author Robert Scholte
* @since 4.0.0
*/
public class RawToConsumerPomXMLFilter extends AbstractSAXFilter
{
RawToConsumerPomXMLFilter( AbstractSAXFilter filter )
{
super( filter );
}
/**
* Don't allow overwriting parent
*/
@Override
public final void setParent( XMLReader parent )
{
if ( getParent() == null )
{
super.setParent( parent );
}
}
@Override
public LexicalHandler getLexicalHandler()
{
return ( (AbstractSAXFilter) getParent() ).getLexicalHandler();
}
}

View File

@ -21,14 +21,10 @@ package org.apache.maven.model.transform;
import java.nio.file.Path;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.xml.sax.SAXException;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
*
* @author Guillaume Nodet
* @author Robert Scholte
* @since 4.0.0
*/
@ -41,20 +37,20 @@ public class RawToConsumerPomXMLFilterFactory
this.buildPomXMLFilterFactory = buildPomXMLFilterFactory;
}
public final RawToConsumerPomXMLFilter get( Path projectPath )
throws SAXException, ParserConfigurationException, TransformerConfigurationException
public final XmlPullParser get( XmlPullParser orgParser, Path projectPath )
{
BuildToRawPomXMLFilter parent = buildPomXMLFilterFactory.get( projectPath );
XmlPullParser parser = orgParser;
parser = buildPomXMLFilterFactory.get( parser, projectPath );
// Ensure that xs:any elements aren't touched by next filters
AbstractSAXFilter filter = new FastForwardFilter( parent );
parser = new FastForwardFilter( parser );
// Strip modules
filter = new ModulesXMLFilter( filter );
parser = new ModulesXMLFilter( parser );
// Adjust relativePath
filter = new RelativePathXMLFilter( filter );
parser = new RelativePathXMLFilter( parser );
return new RawToConsumerPomXMLFilter( filter );
return parser;
}
}

View File

@ -19,153 +19,95 @@ package org.apache.maven.model.transform;
* under the License.
*/
import java.util.List;
import java.util.function.BiFunction;
import org.apache.maven.model.transform.sax.SAXEventUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.apache.maven.model.transform.pull.NodeBufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Will apply the version if the dependency is part of the reactor
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
public class ReactorDependencyXMLFilter extends AbstractEventXMLFilter
public class ReactorDependencyXMLFilter extends NodeBufferingParser
{
private boolean parsingDependency;
// states
private String state;
// whiteSpace after <dependency>, to be used to position <version>
private String dependencyWhitespace;
private boolean hasVersion;
private String groupId;
private String artifactId;
private final BiFunction<String, String, String> reactorVersionMapper;
public ReactorDependencyXMLFilter( BiFunction<String, String, String> reactorVersionMapper )
public ReactorDependencyXMLFilter( XmlPullParser xmlPullParser,
BiFunction<String, String, String> reactorVersionMapper )
{
super( xmlPullParser, "dependency" );
this.reactorVersionMapper = reactorVersionMapper;
}
@Override
public void startElement( String uri, String localName, String qName, Attributes atts )
throws SAXException
protected void process( List<Event> buffer )
{
if ( !parsingDependency && "dependency".equals( localName ) )
// whiteSpace after <dependency>, to be used to position <version>
String dependencyWhitespace = "";
boolean hasVersion = false;
String groupId = null;
String artifactId = null;
String tagName = null;
for ( int i = 0; i < buffer.size(); i++ )
{
parsingDependency = true;
}
if ( parsingDependency )
{
state = localName;
hasVersion |= "version".equals( localName );
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
if ( parsingDependency )
{
final String eventState = state;
String value = new String( ch, start, length );
switch ( eventState )
Event event = buffer.get( i );
if ( event.event == START_TAG )
{
case "dependency":
dependencyWhitespace = nullSafeAppend( dependencyWhitespace, value );
break;
case "groupId":
groupId = nullSafeAppend( groupId, value );
break;
case "artifactId":
artifactId = nullSafeAppend( artifactId, value );
break;
default:
break;
tagName = event.name;
hasVersion |= "version".equals( tagName );
}
}
super.characters( ch, start, length );
}
@Override
public void endElement( String uri, final String localName, String qName )
throws SAXException
{
if ( parsingDependency )
{
switch ( localName )
else if ( event.event == TEXT )
{
case "dependency":
if ( !hasVersion )
if ( event.text.matches( "\\s+" ) )
{
if ( dependencyWhitespace.isEmpty() )
{
String version = getVersion();
// dependency is not part of reactor, probably it is managed
if ( version != null )
{
try ( Includer i = super.include() )
{
if ( dependencyWhitespace != null )
{
super.characters( dependencyWhitespace.toCharArray(), 0,
dependencyWhitespace.length() );
}
String versionQName = SAXEventUtils.renameQName( qName, "version" );
super.startElement( uri, "version", versionQName, null );
super.characters( version.toCharArray(), 0, version.length() );
super.endElement( uri, "version", versionQName );
}
}
dependencyWhitespace = event.text;
}
super.executeEvents();
parsingDependency = false;
// reset
hasVersion = false;
dependencyWhitespace = null;
groupId = null;
artifactId = null;
break;
default:
break;
}
else if ( "groupId".equals( tagName ) )
{
groupId = nullSafeAppend( groupId, event.text );
}
else if ( "artifactId".equals( tagName ) )
{
artifactId = nullSafeAppend( artifactId, event.text );
}
}
else if ( event.event == END_TAG && "dependency".equals( event.name ) )
{
String version = reactorVersionMapper.apply( groupId, artifactId );
if ( !hasVersion && version != null )
{
int pos = buffer.get( i - 1 ).event == TEXT ? i - 1 : i;
Event e = new Event();
e.event = TEXT;
e.text = dependencyWhitespace;
buffer.add( pos++, e );
e = new Event();
e.event = START_TAG;
e.namespace = buffer.get( 0 ).namespace;
e.prefix = buffer.get( 0 ).prefix;
e.name = "version";
buffer.add( pos++, e );
e = new Event();
e.event = TEXT;
e.text = version;
buffer.add( pos++, e );
e = new Event();
e.event = END_TAG;
e.name = "version";
e.namespace = buffer.get( 0 ).namespace;
e.prefix = buffer.get( 0 ).prefix;
buffer.add( pos++, e );
}
break;
}
}
super.endElement( uri, localName, qName );
state = "";
}
private String getVersion()
{
return reactorVersionMapper.apply( groupId, artifactId );
}
@Override
protected boolean isParsing()
{
return parsingDependency;
}
@Override
protected String getState()
{
return state;
buffer.forEach( this::pushEvent );
}
}

View File

@ -19,90 +19,57 @@ package org.apache.maven.model.transform;
* under the License.
*/
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.util.List;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.pull.NodeBufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
/**
* Remove relativePath element, has no value for consumer pom
*
* @author Robert Scholte
* @author Guillaume Nodet
* @since 4.0.0
*/
class RelativePathXMLFilter
extends AbstractEventXMLFilter
public class RelativePathXMLFilter extends NodeBufferingParser
{
private boolean parsingParent;
private String state;
RelativePathXMLFilter()
public RelativePathXMLFilter( XmlPullParser xmlPullParser )
{
super();
super( xmlPullParser, "parent" );
}
RelativePathXMLFilter( AbstractSAXFilter parent )
protected void process( List<Event> buffer )
{
super( parent );
}
@Override
public void startElement( String uri, final String localName, String qName, Attributes atts )
throws SAXException
{
if ( !parsingParent && "parent".equals( localName ) )
boolean skip = false;
Event prev = null;
for ( Event event : buffer )
{
parsingParent = true;
}
if ( parsingParent )
{
state = localName;
}
super.startElement( uri, localName, qName, atts );
}
@Override
public void endElement( String uri, String localName, String qName )
throws SAXException
{
if ( parsingParent )
{
switch ( localName )
if ( event.event == START_TAG && "relativePath".equals( event.name ) )
{
case "parent":
executeEvents();
parsingParent = false;
break;
default:
break;
skip = true;
if ( prev != null && prev.event == TEXT && prev.text.matches( "\\s+" ) )
{
prev = null;
}
event = null;
}
else if ( event.event == END_TAG && "relativePath".equals( event.name ) )
{
skip = false;
event = null;
}
else if ( skip )
{
event = null;
}
if ( prev != null )
{
pushEvent( prev );
}
prev = event;
}
super.endElement( uri, localName, qName );
// for this simple structure resetting to parent it sufficient
state = "parent";
pushEvent( prev );
}
@Override
protected boolean isParsing()
{
return parsingParent;
}
@Override
protected String getState()
{
return state;
}
@Override
protected boolean acceptEvent( String state )
{
return !"relativePath".equals( state );
}
}

View File

@ -0,0 +1,563 @@
package org.apache.maven.model.transform.pull;
/*
* 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.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* An xml pull parser filter base implementation.
*
* @author Guillaume Nodet
* @since 4.0.0
*/
public class BufferingParser implements XmlPullParser
{
protected XmlPullParser xmlPullParser;
protected Deque<Event> events;
protected Event current;
protected boolean disabled;
@SuppressWarnings( "checkstyle:VisibilityModifier" )
public static class Event
{
public int event;
public String name;
public String prefix;
public String namespace;
public boolean empty;
public String text;
public Attribute[] attributes;
public Namespace[] namespaces;
}
@SuppressWarnings( "checkstyle:VisibilityModifier" )
public static class Namespace
{
public String prefix;
public String uri;
}
@SuppressWarnings( "checkstyle:VisibilityModifier" )
public static class Attribute
{
public String name;
public String prefix;
public String namespace;
public String type;
public String value;
public boolean isDefault;
}
public BufferingParser( XmlPullParser xmlPullParser )
{
this.xmlPullParser = xmlPullParser;
}
@Override
public void setFeature( String name, boolean state ) throws XmlPullParserException
{
xmlPullParser.setFeature( name, state );
}
@Override
public boolean getFeature( String name )
{
return xmlPullParser.getFeature( name );
}
@Override
public void setProperty( String name, Object value ) throws XmlPullParserException
{
xmlPullParser.setProperty( name, value );
}
@Override
public Object getProperty( String name )
{
return xmlPullParser.getProperty( name );
}
@Override
public void setInput( Reader in ) throws XmlPullParserException
{
xmlPullParser.setInput( in );
}
@Override
public void setInput( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
{
xmlPullParser.setInput( inputStream, inputEncoding );
}
@Override
public String getInputEncoding()
{
return xmlPullParser.getInputEncoding();
}
@Override
public void defineEntityReplacementText( String entityName, String replacementText ) throws XmlPullParserException
{
xmlPullParser.defineEntityReplacementText( entityName, replacementText );
}
@Override
public int getNamespaceCount( int depth ) throws XmlPullParserException
{
// TODO: if (current != null) throw new IllegalStateException("Not supported during events replay");
return xmlPullParser.getNamespaceCount( depth );
}
@Override
public String getNamespacePrefix( int pos ) throws XmlPullParserException
{
// TODO: if (current != null) throw new IllegalStateException("Not supported during events replay");
return xmlPullParser.getNamespacePrefix( pos );
}
@Override
public String getNamespaceUri( int pos ) throws XmlPullParserException
{
// TODO: if (current != null) throw new IllegalStateException("Not supported during events replay");
return xmlPullParser.getNamespaceUri( pos );
}
@Override
public String getNamespace( String prefix )
{
// TODO: if (current != null) throw new IllegalStateException("Not supported during events replay");
return xmlPullParser.getNamespace( prefix );
}
@Override
public int getDepth()
{
// TODO: if (current != null) throw new IllegalStateException("Not supported during events replay");
return xmlPullParser.getDepth();
}
@Override
public String getPositionDescription()
{
if ( current != null )
{
throw new IllegalStateException( "Not supported during events replay" );
}
return xmlPullParser.getPositionDescription();
}
@Override
public int getLineNumber()
{
if ( current != null )
{
throw new IllegalStateException( "Not supported during events replay" );
}
return xmlPullParser.getLineNumber();
}
@Override
public int getColumnNumber()
{
if ( current != null )
{
throw new IllegalStateException( "Not supported during events replay" );
}
return xmlPullParser.getColumnNumber();
}
@Override
public boolean isWhitespace() throws XmlPullParserException
{
if ( current != null )
{
if ( current.event == TEXT || current.event == CDSECT )
{
return current.text.matches( "[ \r\t\n]+" );
}
else if ( current.event == IGNORABLE_WHITESPACE )
{
return true;
}
else
{
throw new XmlPullParserException( "no content available to check for whitespaces" );
}
}
return xmlPullParser.isWhitespace();
}
@Override
public String getText()
{
return current != null ? current.text : xmlPullParser.getText();
}
@Override
public char[] getTextCharacters( int[] holderForStartAndLength )
{
if ( current != null )
{
throw new IllegalStateException( "Not supported during events replay" );
}
return xmlPullParser.getTextCharacters( holderForStartAndLength );
}
@Override
public String getNamespace()
{
return current != null ? current.namespace : xmlPullParser.getNamespace();
}
@Override
public String getName()
{
return current != null ? current.name : xmlPullParser.getName();
}
@Override
public String getPrefix()
{
return current != null ? current.prefix : xmlPullParser.getPrefix();
}
@Override
public boolean isEmptyElementTag() throws XmlPullParserException
{
return current != null ? current.empty : xmlPullParser.isEmptyElementTag();
}
@Override
public int getAttributeCount()
{
if ( current != null )
{
return current.attributes != null ? current.attributes.length : 0;
}
else
{
return xmlPullParser.getAttributeCount();
}
}
@Override
public String getAttributeNamespace( int index )
{
if ( current != null )
{
return current.attributes[index].namespace;
}
else
{
return xmlPullParser.getAttributeNamespace( index );
}
}
@Override
public String getAttributeName( int index )
{
if ( current != null )
{
return current.attributes[index].name;
}
else
{
return xmlPullParser.getAttributeName( index );
}
}
@Override
public String getAttributePrefix( int index )
{
if ( current != null )
{
return current.attributes[index].prefix;
}
else
{
return xmlPullParser.getAttributePrefix( index );
}
}
@Override
public String getAttributeType( int index )
{
if ( current != null )
{
return current.attributes[index].type;
}
else
{
return xmlPullParser.getAttributeType( index );
}
}
@Override
public boolean isAttributeDefault( int index )
{
if ( current != null )
{
return current.attributes[index].isDefault;
}
else
{
return xmlPullParser.isAttributeDefault( index );
}
}
@Override
public String getAttributeValue( int index )
{
if ( current != null )
{
return current.attributes[index].value;
}
else
{
return xmlPullParser.getAttributeValue( index );
}
}
@Override
public String getAttributeValue( String namespace, String name )
{
if ( current != null )
{
if ( current.attributes != null )
{
for ( Attribute attr : current.attributes )
{
if ( Objects.equals( namespace, attr.namespace )
&& Objects.equals( name, attr.name ) )
{
return attr.value;
}
}
}
return null;
}
else
{
return xmlPullParser.getAttributeValue( namespace, name );
}
}
@Override
public void require( int type, String namespace, String name ) throws XmlPullParserException, IOException
{
if ( current != null )
{
throw new IllegalStateException( "Not supported during events replay" );
}
xmlPullParser.require( type, namespace, name );
}
@Override
public int getEventType() throws XmlPullParserException
{
return current != null ? current.event : xmlPullParser.getEventType();
}
@Override
public int next() throws XmlPullParserException, IOException
{
while ( true )
{
if ( events != null && !events.isEmpty() )
{
current = events.removeFirst();
return current.event;
}
else
{
current = null;
}
if ( getEventType() == END_DOCUMENT )
{
throw new XmlPullParserException( "already reached end of XML input", this, null );
}
int currentEvent = xmlPullParser.next();
if ( disabled || accept() )
{
return currentEvent;
}
}
}
@Override
public int nextToken() throws XmlPullParserException, IOException
{
while ( true )
{
if ( events != null && !events.isEmpty() )
{
current = events.removeFirst();
return current.event;
}
else
{
current = null;
}
if ( getEventType() == END_DOCUMENT )
{
throw new XmlPullParserException( "already reached end of XML input", this, null );
}
int currentEvent = xmlPullParser.nextToken();
if ( accept() )
{
return currentEvent;
}
}
}
@Override
public int nextTag() throws XmlPullParserException, IOException
{
int eventType = next();
if ( eventType == TEXT && isWhitespace() )
{ // skip whitespace
eventType = next();
}
if ( eventType != START_TAG && eventType != END_TAG )
{
throw new XmlPullParserException( "expected START_TAG or END_TAG not "
+ TYPES[getEventType()], this, null );
}
return eventType;
}
@Override
public String nextText() throws XmlPullParserException, IOException
{
int eventType = getEventType();
if ( eventType != START_TAG )
{
throw new XmlPullParserException( "parser must be on START_TAG to read next text", this, null );
}
eventType = next();
if ( eventType == TEXT )
{
final String result = getText();
eventType = next();
if ( eventType != END_TAG )
{
throw new XmlPullParserException( "TEXT must be immediately followed by END_TAG and not "
+ TYPES[getEventType()], this, null );
}
return result;
}
else if ( eventType == END_TAG )
{
return "";
}
else
{
throw new XmlPullParserException( "parser must be on START_TAG or TEXT to read text", this, null );
}
}
protected Event bufferEvent() throws XmlPullParserException
{
Event event = new Event();
XmlPullParser pp = xmlPullParser;
event.event = xmlPullParser.getEventType();
switch ( event.event )
{
case START_DOCUMENT:
case END_DOCUMENT:
break;
case START_TAG:
event.name = pp.getName();
event.namespace = pp.getNamespace();
event.prefix = pp.getPrefix();
event.empty = pp.isEmptyElementTag();
event.text = pp.getText();
break;
case END_TAG:
event.name = pp.getName();
event.namespace = pp.getNamespace();
event.prefix = pp.getPrefix();
event.text = pp.getText();
break;
case TEXT:
case COMMENT:
case IGNORABLE_WHITESPACE:
event.text = pp.getText();
break;
default:
break;
}
return event;
}
protected void pushEvent( Event event )
{
if ( events == null )
{
events = new ArrayDeque<>();
}
events.add( event );
}
protected boolean accept() throws XmlPullParserException, IOException
{
return true;
}
protected void enable()
{
disabled = false;
}
protected void disable()
{
if ( events != null && !events.isEmpty() )
{
throw new IllegalStateException( "Can not disable filter while processing" );
}
disabled = true;
if ( xmlPullParser instanceof BufferingParser )
{
( ( BufferingParser ) xmlPullParser ).disable();
}
}
protected static String nullSafeAppend( String originalValue, String charSegment )
{
if ( originalValue == null )
{
return charSegment;
}
else
{
return originalValue + charSegment;
}
}
}

View File

@ -0,0 +1,81 @@
package org.apache.maven.model.transform.pull;
/*
* 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.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Buffer events while parsing a given element to allow some post-processing.
*
* @author Guillaume Nodet
* @since 4.0.0
*/
public abstract class NodeBufferingParser extends BufferingParser
{
private final List<Event> buffer = new ArrayList<>();
private final String nodeName;
private boolean buffering;
public NodeBufferingParser( XmlPullParser xmlPullParser, String nodeName )
{
super( xmlPullParser );
this.nodeName = Objects.requireNonNull( nodeName );
}
@Override
protected boolean accept() throws XmlPullParserException, IOException
{
if ( nodeName.equals( xmlPullParser.getName() ) )
{
if ( xmlPullParser.getEventType() == START_TAG && !buffering )
{
buffer.add( bufferEvent() );
buffering = true;
return false;
}
if ( xmlPullParser.getEventType() == END_TAG && buffering )
{
buffer.add( bufferEvent() );
process( buffer );
buffering = false;
buffer.clear();
return false;
}
}
else if ( buffering )
{
buffer.add( bufferEvent() );
return false;
}
return true;
}
protected abstract void process( List<Event> buffer );
}

View File

@ -0,0 +1,132 @@
package org.apache.maven.model.transform.pull;
/*
* 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import org.codehaus.plexus.util.xml.XmlStreamReader;
import org.codehaus.plexus.util.xml.pull.MXSerializer;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.codehaus.plexus.util.xml.pull.XmlSerializer;
public class XmlUtils
{
public static ByteArrayInputStream writeDocument( XmlStreamReader reader, XmlPullParser parser )
throws IOException, XmlPullParserException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Writer writer = newWriter( reader, baos );
writeDocument( parser, writer );
return new ByteArrayInputStream( baos.toByteArray() );
}
public static void writeDocument( XmlPullParser parser, Writer writer )
throws IOException, XmlPullParserException
{
XmlSerializer serializer = new MXSerializer();
serializer.setOutput( writer );
while ( parser.nextToken() != XmlPullParser.END_DOCUMENT )
{
switch ( parser.getEventType() )
{
case XmlPullParser.START_DOCUMENT:
serializer.startDocument( parser.getInputEncoding(), true );
break;
case XmlPullParser.END_DOCUMENT:
serializer.endDocument();
case XmlPullParser.START_TAG:
int nsStart = parser.getNamespaceCount( parser.getDepth() - 1 );
int nsEnd = parser.getNamespaceCount( parser.getDepth() );
for ( int i = nsStart; i < nsEnd; i++ )
{
String prefix = parser.getNamespacePrefix( i );
String ns = parser.getNamespaceUri( i );
serializer.setPrefix( prefix, ns );
}
serializer.startTag( parser.getNamespace(), parser.getName() );
for ( int i = 0; i < parser.getAttributeCount(); i++ )
{
serializer.attribute( parser.getAttributeNamespace( i ),
parser.getAttributeName( i ),
parser.getAttributeValue( i ) );
}
break;
case XmlPullParser.END_TAG:
serializer.endTag( parser.getNamespace(), parser.getName() );
break;
case XmlPullParser.TEXT:
serializer.text( normalize( parser.getText() ) );
break;
case XmlPullParser.CDSECT:
serializer.cdsect( parser.getText() );
break;
case XmlPullParser.ENTITY_REF:
serializer.entityRef( parser.getName() );
break;
case XmlPullParser.IGNORABLE_WHITESPACE:
serializer.ignorableWhitespace( normalize( parser.getText() ) );
break;
case XmlPullParser.PROCESSING_INSTRUCTION:
serializer.processingInstruction( parser.getText() );
break;
case XmlPullParser.COMMENT:
serializer.comment( normalize( parser.getText() ) );
break;
case XmlPullParser.DOCDECL:
serializer.docdecl( normalize( parser.getText() ) );
break;
default:
break;
}
}
serializer.endDocument();
}
private static OutputStreamWriter newWriter( XmlStreamReader reader, ByteArrayOutputStream baos )
throws UnsupportedEncodingException
{
if ( reader.getEncoding() != null )
{
return new OutputStreamWriter( baos, reader.getEncoding() );
}
else
{
return new OutputStreamWriter( baos );
}
}
private static String normalize( String input )
{
if ( input.indexOf( '\n' ) >= 0 && !"\n".equals( System.lineSeparator() ) )
{
return input.replace( "\n", System.lineSeparator() );
}
return input;
}
}

View File

@ -1,143 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.XMLFilterImpl;
/**
* XMLFilter with LexicalHandler.
* Since some filters collect events before processing them, the LexicalHandler events must be collected too.
* Otherwise the LexicalHandler events might end up before all collected XMLReader events.
*
* @author Robert Scholte
* @since 4.0.0
*/
public class AbstractSAXFilter extends XMLFilterImpl implements LexicalHandler
{
private LexicalHandler lexicalHandler;
public AbstractSAXFilter()
{
super();
}
public AbstractSAXFilter( AbstractSAXFilter parent )
{
super( parent );
parent.setLexicalHandler( this );
}
public LexicalHandler getLexicalHandler()
{
return lexicalHandler;
}
public void setLexicalHandler( LexicalHandler lexicalHandler )
{
this.lexicalHandler = lexicalHandler;
}
@Override
public void startDTD( String name, String publicId, String systemId )
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.startDTD( name, publicId, systemId );
}
}
@Override
public void endDTD()
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.endDTD();
}
}
@Override
public void startEntity( String name )
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.startEntity( name );
}
}
@Override
public void endEntity( String name )
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.endEntity( name );
}
}
@Override
public void startCDATA()
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.startCDATA();
}
}
@Override
public void endCDATA()
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.endCDATA();
}
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
if ( lexicalHandler != null )
{
lexicalHandler.comment( ch, start, length );
}
}
protected static String nullSafeAppend( String originalValue, String charSegment )
{
if ( originalValue == null )
{
return charSegment;
}
else
{
return originalValue + charSegment;
}
}
}

View File

@ -1,108 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
* During parsing the line separators are transformed to \n
* Unlike characters(), comments don't use the systems line separator for serialization.
* Hence use this class in the LexicalHandler chain to do so
*
* @author Robert Scholte
* @since 4.0.0
*/
public class CommentRenormalizer implements LexicalHandler
{
private final LexicalHandler lexicalHandler;
private final String lineSeparator;
public CommentRenormalizer( LexicalHandler lexicalHandler )
{
this( lexicalHandler, System.lineSeparator() );
}
// for testing purpose
CommentRenormalizer( LexicalHandler lexicalHandler, String lineSeparator )
{
this.lexicalHandler = lexicalHandler;
this.lineSeparator = lineSeparator;
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
if ( "\n".equals( lineSeparator ) )
{
lexicalHandler.comment( ch, start, length );
}
else
{
char[] ca = new String( ch, start, length ).replaceAll( "\n", lineSeparator ).toCharArray();
lexicalHandler.comment( ca, 0, ca.length );
}
}
@Override
public void startDTD( String name, String publicId, String systemId )
throws SAXException
{
lexicalHandler.startDTD( name, publicId, systemId );
}
@Override
public void endDTD()
throws SAXException
{
lexicalHandler.endDTD();
}
@Override
public void startEntity( String name )
throws SAXException
{
lexicalHandler.startEntity( name );
}
@Override
public void endEntity( String name )
throws SAXException
{
lexicalHandler.endEntity( name );
}
@Override
public void startCDATA()
throws SAXException
{
lexicalHandler.startCDATA();
}
@Override
public void endCDATA()
throws SAXException
{
lexicalHandler.endCDATA();
}
}

View File

@ -1,79 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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 javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* Creates XML related factories with OWASP advices applied
*
* @author Robert Scholte
* @since 4.0.0
*/
public final class Factories
{
private Factories()
{
}
/**
* See
* https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#transformerfactory
*/
public static TransformerFactory newTransformerFactory()
{
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_DTD, "" );
tf.setAttribute( XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
return tf;
}
public static XMLReader newXMLReader() throws SAXException, ParserConfigurationException
{
XMLReader reader = XMLReaderFactory.createXMLReader();
try
{
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// Using the XMLReader's setFeature
reader.setFeature( "http://xml.org/sax/features/external-general-entities", false );
}
catch ( SAXNotRecognizedException e )
{
// Tried an unknown feature.
}
catch ( SAXNotSupportedException e )
{
// Tried a feature known to the parser but unsupported.
}
return reader;
}
}

View File

@ -1,34 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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.xml.sax.SAXException;
/**
* Command pattern to gather events which can be executed later on.
*
* @author Robert Scholte
* @since 4.0.0
*/
@FunctionalInterface
public interface SAXEvent
{
void execute() throws SAXException;
}

View File

@ -1,144 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.ext.LexicalHandler;
/**
* Factory for SAXEvents
*
* @author Robert Scholte
* @since 4.0.0
*/
public final class SAXEventFactory
{
private final ContentHandler contentHandler;
private final LexicalHandler lexicalHandler;
protected SAXEventFactory( ContentHandler contentHandler, LexicalHandler lexicalHandler )
{
this.contentHandler = contentHandler;
this.lexicalHandler = lexicalHandler;
}
public SAXEvent characters( final char[] ch, final int start, final int length )
{
final char[] txt = new char[length];
System.arraycopy( ch, start, txt, 0, length );
return () -> contentHandler.characters( txt, 0, length );
}
public SAXEvent endDocument()
{
return contentHandler::endDocument;
}
public SAXEvent endElement( final String uri, final String localName, final String qName )
{
return () -> contentHandler.endElement( uri, localName, qName );
}
public SAXEvent endPrefixMapping( final String prefix )
{
return () -> contentHandler.endPrefixMapping( prefix );
}
public SAXEvent ignorableWhitespace( final char[] ch, final int start, final int length )
{
return () -> contentHandler.ignorableWhitespace( ch, start, length );
}
public SAXEvent processingInstruction( final String target, final String data )
{
return () -> contentHandler.processingInstruction( target, data );
}
public SAXEvent setDocumentLocator( final Locator locator )
{
return () -> contentHandler.setDocumentLocator( locator );
}
public SAXEvent skippedEntity( final String name )
{
return () -> contentHandler.skippedEntity( name );
}
public SAXEvent startDocument()
{
return contentHandler::startDocument;
}
public SAXEvent startElement( final String uri, final String localName, final String qName, final Attributes atts )
{
return () -> contentHandler.startElement( uri, localName, qName, atts );
}
public SAXEvent startPrefixMapping( final String prefix, final String uri )
{
return () -> contentHandler.startPrefixMapping( prefix, uri );
}
public static SAXEventFactory newInstance( ContentHandler contentHandler, LexicalHandler lexicalHandler )
{
return new SAXEventFactory( contentHandler, lexicalHandler );
}
public SAXEvent startDTD( String name, String publicId, String systemId )
{
return () -> lexicalHandler.startDTD( name, publicId, systemId );
}
public SAXEvent endDTD()
{
return lexicalHandler::endDTD;
}
public SAXEvent startEntity( String name )
{
return () -> lexicalHandler.startEntity( name );
}
public SAXEvent endEntity( String name )
{
return () -> lexicalHandler.endEntity( name );
}
public SAXEvent startCDATA()
{
return lexicalHandler::startCDATA;
}
public SAXEvent endCDATA()
{
return lexicalHandler::endCDATA;
}
public SAXEvent comment( char[] ch, int start, int length )
{
final char[] txt = new char[length];
System.arraycopy( ch, start, txt, 0, length );
return () -> lexicalHandler.comment( txt, 0, length );
}
}

View File

@ -1,49 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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.util.regex.Pattern;
/**
* Utility class for SAXEvents
*
* @author Robert Scholte
* @since 4.0.0
*/
public final class SAXEventUtils
{
private static final Pattern PATTERN = Pattern.compile( "[^:]+$" );
private SAXEventUtils()
{
}
/**
* Returns the newLocalName prefixed with the namespace of the oldQName if present
*
* @param oldQName the QName, used for its namespace
* @param newLocalName the preferred localName
* @return the new QName
*/
public static String renameQName( String oldQName, String newLocalName )
{
return PATTERN.matcher( oldQName ).replaceFirst( newLocalName );
}
}

View File

@ -19,191 +19,41 @@ package org.apache.maven.model.transform;
* under the License.
*/
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.ContentHandler;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.apache.maven.model.transform.sax.Factories;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.apache.maven.model.transform.pull.XmlUtils;
import org.codehaus.plexus.util.xml.pull.MXParser;
import org.codehaus.plexus.util.xml.pull.MXSerializer;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
public abstract class AbstractXMLFilterTests
{
protected AbstractSAXFilter getFilter() throws TransformerException, SAXException, ParserConfigurationException
protected XmlPullParser getFilter(XmlPullParser parser)
{
throw new UnsupportedOperationException( "Override one of the getFilter() methods" );
}
protected AbstractSAXFilter getFilter( Consumer<LexicalHandler> result ) throws TransformerException, SAXException, ParserConfigurationException
{
return getFilter();
}
protected String omitXmlDeclaration()
{
return "yes";
}
protected String indentAmount()
{
return null;
}
protected String transform( String input )
throws TransformerException, SAXException, ParserConfigurationException
throws XmlPullParserException, IOException
{
return transform( new StringReader( input ) );
}
/**
* Use this method only for testing a single filter.
*
* @param input
* @param filter
* @return
* @throws TransformerException
* @throws SAXException
* @throws ParserConfigurationException
*/
protected String transform( String input, AbstractSAXFilter filter )
throws TransformerException, SAXException, ParserConfigurationException
{
setParent( filter );
SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
transformerHandler.getTransformer().setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration() );
if ( indentAmount() != null )
{
transformerHandler.getTransformer().setOutputProperty( OutputKeys.INDENT, "yes" );
transformerHandler.getTransformer().setOutputProperty( "{http://xml.apache.org/xslt}indent-amount",
indentAmount() );
}
Transformer transformer = transformerFactory.newTransformer();
Writer writer = new StringWriter();
StreamResult result = new StreamResult( writer );
transformerHandler.setResult( result );
SAXResult transformResult = new SAXResult( transformerHandler );
SAXSource transformSource = new SAXSource( filter, new InputSource( new StringReader( input ) ) );
transformResult.setLexicalHandler( filter );
transformer.transform( transformSource, transformResult );
return writer.toString();
}
protected String transform( Reader input )
throws TransformerException, SAXException, ParserConfigurationException
{
SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
throws XmlPullParserException, IOException {
transformerHandler.getTransformer().setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration() );
if ( indentAmount() != null )
{
transformerHandler.getTransformer().setOutputProperty( OutputKeys.INDENT, "yes" );
transformerHandler.getTransformer().setOutputProperty( "{http://xml.apache.org/xslt}indent-amount",
indentAmount() );
}
Transformer transformer = transformerFactory.newTransformer();
Writer writer = new StringWriter();
StreamResult result = new StreamResult( writer );
transformerHandler.setResult( result );
SAXResult transformResult = new SAXResult( transformerHandler );
AbstractSAXFilter filter = getFilter( l -> transformResult.setLexicalHandler( l ) );
setParent( filter );
filter = new PerCharXMLFilter( filter );
filter.setLexicalHandler( transformerHandler );
SAXSource transformSource = new SAXSource( filter, new InputSource( input ) );
transformer.transform( transformSource, transformResult );
MXParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(input);
XmlPullParser filter = getFilter( parser );
StringWriter writer = new StringWriter();
XmlUtils.writeDocument( filter, writer );
return writer.toString();
}
private void setParent( AbstractSAXFilter filter )
throws SAXException, ParserConfigurationException
{
if ( filter.getParent() == null )
{
XMLReader r = Factories.newXMLReader();
AbstractSAXFilter perChar = new PerCharXMLFilter();
perChar.setParent( r );
filter.setParent( perChar );
filter.setFeature( "http://xml.org/sax/features/namespaces", true );
}
}
/**
* From {@link ContentHandler}
* <q>Your code should not assume that algorithms using char-at-a-time idioms will be working in characterunits;
* in some cases they will split characters. This is relevant wherever XML permits arbitrary characters, such as
* attribute values,processing instruction data, and comments as well as in data reported from this method. It's
* also generally relevant whenever Java code manipulates internationalized text; the issue isn't unique to XML.</q>
*
* @author Robert Scholte
*/
class PerCharXMLFilter
extends AbstractSAXFilter
{
public PerCharXMLFilter()
{
super();
}
public PerCharXMLFilter( AbstractSAXFilter parent )
{
super( parent );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
for ( int i = 0; i < length; i++ )
{
super.characters( ch, start + i, 1 );
}
}
@Override
public void ignorableWhitespace( char[] ch, int start, int length )
throws SAXException
{
for ( int i = 0; i < length; i++ )
{
super.ignorableWhitespace( ch, start + i, 1 );
}
}
}
}

View File

@ -19,32 +19,18 @@ package org.apache.maven.model.transform;
* under the License.
*/
import static org.junit.jupiter.api.Assertions.assertEquals;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.Test;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.junit.jupiter.api.BeforeEach;
import org.xml.sax.SAXException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CiFriendlyXMLFilterTest extends AbstractXMLFilterTests
{
private CiFriendlyXMLFilter filter;
@BeforeEach
public void setUp()
{
filter = new CiFriendlyXMLFilter( true );
filter.setChangelist( "CHANGELIST" );
}
@Override
protected AbstractSAXFilter getFilter()
throws TransformerException, SAXException, ParserConfigurationException
{
protected CiFriendlyXMLFilter getFilter(XmlPullParser parser) {
CiFriendlyXMLFilter filter = new CiFriendlyXMLFilter( parser, true );
filter.setChangelist( "CHANGELIST" );
return filter;
}

View File

@ -25,30 +25,17 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import org.apache.maven.model.transform.sax.AbstractSAXFilter;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
{
@Override
protected String omitXmlDeclaration()
protected XmlPullParser getFilter( XmlPullParser orgParser )
{
return "no";
}
@Override
protected AbstractSAXFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
throws SAXException, ParserConfigurationException, TransformerConfigurationException
{
final BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory = new BuildToRawPomXMLFilterFactory( lexicalHandlerConsumer, true )
final BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory = new BuildToRawPomXMLFilterFactory( true )
{
@Override
protected Function<Path, Optional<RelativeProject>> getRelativePathMapper()
@ -82,10 +69,9 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
};
RawToConsumerPomXMLFilter filter =
new RawToConsumerPomXMLFilterFactory( buildPomXMLFilterFactory ).get( Paths.get( "pom.xml" ) );
filter.setFeature( "http://xml.org/sax/features/namespaces", true );
return filter;
XmlPullParser parser = new RawToConsumerPomXMLFilterFactory( buildPomXMLFilterFactory )
.get( orgParser, Paths.get( "pom.xml" ) );
return parser;
}
@Test
@ -254,8 +240,7 @@ public class ConsumerPomXMLFilterTest extends AbstractXMLFilterTests
+ "<!--post-in-->"
+ "</modules>"
+ "<!--after--></project>";
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project><!--before--><!--after--></project>";
String expected = "<project><!--before--><!--after--></project>";
String actual = transform( input );
assertThat( actual ).and( expected ).areIdentical();
}

View File

@ -19,23 +19,19 @@ package org.apache.maven.model.transform;
* under the License.
*/
import static org.xmlunit.assertj.XmlAssert.assertThat;
import java.util.function.Consumer;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.Test;
import org.xml.sax.ext.LexicalHandler;
import static org.xmlunit.assertj.XmlAssert.assertThat;
public class ModulesXMLFilterTest
extends AbstractXMLFilterTests
{
@Override
protected ModulesXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
protected ModulesXMLFilter getFilter( XmlPullParser parser )
{
ModulesXMLFilter filter = new ModulesXMLFilter();
lexicalHandlerConsumer.accept( filter );
return filter;
return new ModulesXMLFilter( parser );
}
@Test

View File

@ -19,32 +19,43 @@ package org.apache.maven.model.transform;
* under the License.
*/
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.util.function.Function;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ParentXMLFilterTest
extends AbstractXMLFilterTests
{
@Override
protected ParentXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerException, SAXException, ParserConfigurationException
{
ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.of( new RelativeProject( "GROUPID",
"ARTIFACTID",
"1.0.0" ) ) );
filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
lexicalHandlerConsumer.accept( filter );
private Function<XmlPullParser, ParentXMLFilter> filterCreator;
@BeforeEach
void reset() {
filterCreator = null;
}
@Override
protected ParentXMLFilter getFilter( XmlPullParser parser )
{
Function<XmlPullParser, ParentXMLFilter> filterCreator =
(this.filterCreator != null ? this.filterCreator : this::createFilter);
return filterCreator.apply(parser);
}
protected ParentXMLFilter createFilter( XmlPullParser parser ) {
return createFilter( parser,
x -> Optional.of(new RelativeProject("GROUPID", "ARTIFACTID", "1.0.0")),
Paths.get( "pom.xml").toAbsolutePath() );
}
protected ParentXMLFilter createFilter( XmlPullParser parser, Function<Path, Optional<RelativeProject>> pathMapper, Path projectPath ) {
ParentXMLFilter filter = new ParentXMLFilter( parser, pathMapper, projectPath );
return filter;
}
@ -52,7 +63,7 @@ public class ParentXMLFilterTest
public void testMinimum()
throws Exception
{
String input = "<parent/>";
String input = "<project><parent /></project>";
String expected = input;
String actual = transform( input );
assertEquals( expected, actual );
@ -62,11 +73,11 @@ public class ParentXMLFilterTest
public void testNoRelativePath()
throws Exception
{
String input = "<parent>"
String input = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<version>VERSION</version>"
+ "</parent>";
+ "</parent></project>";
String expected = input;
String actual = transform( input );
@ -78,15 +89,19 @@ public class ParentXMLFilterTest
public void testDefaultRelativePath()
throws Exception
{
String input = "<parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "</parent>";
String expected = "<parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<version>1.0.0</version>"
+ "</parent>";
String input = "<project>\n"
+ " <parent>\n"
+ " <groupId>GROUPID</groupId>\n"
+ " <artifactId>ARTIFACTID</artifactId>\n"
+ " </parent>\n"
+ "</project>";
String expected = "<project>" + System.lineSeparator()
+ " <parent>" + System.lineSeparator()
+ " <groupId>GROUPID</groupId>" + System.lineSeparator()
+ " <artifactId>ARTIFACTID</artifactId>" + System.lineSeparator()
+ " <version>1.0.0</version>" + System.lineSeparator()
+ " </parent>" + System.lineSeparator()
+ "</project>";
String actual = transform( input );
@ -103,16 +118,16 @@ public class ParentXMLFilterTest
public void testEmptyRelativePathNoVersion()
throws Exception
{
String input = "<parent>"
String input = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath></relativePath>"
+ "</parent>";
String expected = "<parent>"
+ "</parent></project>";
String expected = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath/>" // SAX optimization, however "" != null ...
+ "</parent>";
+ "<relativePath />" // SAX optimization, however "" != null ...
+ "</parent></project>";
String actual = transform( input );
@ -123,17 +138,17 @@ public class ParentXMLFilterTest
public void testNoVersion()
throws Exception
{
String input = "<parent>"
String input = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath>RELATIVEPATH</relativePath>"
+ "</parent>";
String expected = "<parent>"
+ "</parent></project>";
String expected = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath>RELATIVEPATH</relativePath>"
+ "<version>1.0.0</version>"
+ "</parent>";
+ "</parent></project>";
String actual = transform( input );
@ -144,17 +159,16 @@ public class ParentXMLFilterTest
public void testInvalidRelativePath()
throws Exception
{
ParentXMLFilter filter = new ParentXMLFilter( x -> Optional.ofNullable( null ) );
filter.setProjectPath( Paths.get( "pom.xml").toAbsolutePath() );
filterCreator = parser -> createFilter(parser, x -> Optional.ofNullable( null ), Paths.get( "pom.xml").toAbsolutePath() );
String input = "<parent>"
String input = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath>RELATIVEPATH</relativePath>"
+ "</parent>";
+ "</parent></project>";
String expected = input;
String actual = transform( input, filter );
String actual = transform( input );
assertEquals( expected, actual );
}
@ -163,18 +177,18 @@ public class ParentXMLFilterTest
public void testRelativePathAndVersion()
throws Exception
{
String input = "<parent>"
String input = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath>RELATIVEPATH</relativePath>"
+ "<version>1.0.0</version>"
+ "</parent>";
String expected = "<parent>"
+ "</parent></project>";
String expected = "<project><parent>"
+ "<groupId>GROUPID</groupId>"
+ "<artifactId>ARTIFACTID</artifactId>"
+ "<relativePath>RELATIVEPATH</relativePath>"
+ "<version>1.0.0</version>"
+ "</parent>";
+ "</parent></project>";
String actual = transform( input );
@ -185,17 +199,20 @@ public class ParentXMLFilterTest
public void testWithWeirdNamespace()
throws Exception
{
String input = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
String input = "<relativePath:project xmlns:relativePath=\"relativePath\">"
+ "<relativePath:parent>"
+ "<relativePath:groupId>GROUPID</relativePath:groupId>"
+ "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
+ "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
+ "</relativePath:parent>";
String expected = "<relativePath:parent xmlns:relativePath=\"relativePath\">"
+ "</relativePath:parent></relativePath:project>";
String expected = "<relativePath:project xmlns:relativePath=\"relativePath\">"
+ "<relativePath:parent>"
+ "<relativePath:groupId>GROUPID</relativePath:groupId>"
+ "<relativePath:artifactId>ARTIFACTID</relativePath:artifactId>"
+ "<relativePath:relativePath>RELATIVEPATH</relativePath:relativePath>"
+ "<relativePath:version>1.0.0</relativePath:version>"
+ "</relativePath:parent>";
+ "</relativePath:parent>"
+ "</relativePath:project>";
String actual = transform( input );

View File

@ -19,27 +19,29 @@ package org.apache.maven.model.transform;
* under the License.
*/
import static org.xmlunit.assertj.XmlAssert.assertThat;
import java.util.function.Consumer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.util.function.BiFunction;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import static org.xmlunit.assertj.XmlAssert.assertThat;
public class ReactorDependencyXMLFilterTest
extends AbstractXMLFilterTests
{
private BiFunction<String, String, String> reactorVersionMapper;
@BeforeEach
protected void reset() {
reactorVersionMapper = null;
}
@Override
protected ReactorDependencyXMLFilter getFilter( Consumer<LexicalHandler> lexicalHandlerConsumer )
throws TransformerException, SAXException, ParserConfigurationException
protected ReactorDependencyXMLFilter getFilter(XmlPullParser parser)
{
ReactorDependencyXMLFilter filter = new ReactorDependencyXMLFilter( (g, a) -> "1.0.0" );
lexicalHandlerConsumer.accept( filter );
return filter;
return new ReactorDependencyXMLFilter( parser,
reactorVersionMapper != null ? reactorVersionMapper : (g, a) -> "1.0.0" );
}
@Test
@ -62,7 +64,7 @@ public class ReactorDependencyXMLFilterTest
public void testManagedDependency()
throws Exception
{
ReactorDependencyXMLFilter filter = new ReactorDependencyXMLFilter( (g, a) -> null );
reactorVersionMapper = (g, a) -> null;
String input = "<dependency>"
+ "<groupId>GROUPID</groupId>"
@ -70,7 +72,7 @@ public class ReactorDependencyXMLFilterTest
+ "</dependency>";
String expected = input;
String actual = transform( input, filter );
String actual = transform( input );
assertThat( actual ).isEqualTo( expected );
}

View File

@ -19,17 +19,18 @@ package org.apache.maven.model.transform;
* under the License.
*/
import static org.xmlunit.assertj.XmlAssert.assertThat;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.junit.jupiter.api.Test;
import static org.xmlunit.assertj.XmlAssert.assertThat;
public class RelativePathXMLFilterTest
extends AbstractXMLFilterTests
{
@Override
protected RelativePathXMLFilter getFilter()
protected RelativePathXMLFilter getFilter(XmlPullParser parser)
{
return new RelativePathXMLFilter();
return new RelativePathXMLFilter(parser);
}
@Test

View File

@ -1,148 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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 static org.xmlunit.assertj.XmlAssert.assertThat;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.junit.jupiter.api.Test;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A small example of a pipeline of 2 XML Filters, to understand how to get the expected result
*
* @author Robert Scholte
* @since 4.0.0
*/
public class ChainedFilterTest
{
@Test
public void test()
throws Exception
{
String input = "<project><!-- aBc --><name>dEf</name></project>";
SAXTransformerFactory transformerFactory = (SAXTransformerFactory) Factories.newTransformerFactory();
TransformerHandler transformerHandler = transformerFactory.newTransformerHandler();
Writer writer = new StringWriter();
StreamResult result = new StreamResult( writer );
transformerHandler.setResult( result );
SAXResult transformResult = new SAXResult( transformerHandler );
// Watch the order of filters! In reverse order the values would be 'AweSome'
AbstractSAXFilter filter = new Awesome();
// AbstractSAXFilter doesn't have a constructor with XMLReader, otherwise the LexicalHandler pipeline will be broken
filter.setParent( Factories.newXMLReader() );
// LexicalHandler of transformerResult must be the first filter
transformResult.setLexicalHandler( filter );
filter = new ChangeCase( filter );
// LexicalHandler on last filter must be the transformerHandler
filter.setLexicalHandler( transformerHandler );
SAXSource transformSource = new SAXSource( filter, new InputSource( new StringReader( input ) ) );
Transformer transformer = transformerFactory.newTransformer();
transformer.transform( transformSource, transformResult );
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<project><!--AWESOME--><name>awesome</name></project>";
assertThat( writer.toString() ).and( expected ).areIdentical();
}
static class ChangeCase
extends AbstractSAXFilter
{
public ChangeCase()
{
super();
}
public ChangeCase( AbstractSAXFilter parent )
{
super( parent );
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
String s = new String( ch, start, length ).toUpperCase();
super.comment( s.toCharArray(), 0, s.length() );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
String s = new String( ch, start, length ).toLowerCase();
super.characters( s.toCharArray(), 0, s.length() );
}
}
static class Awesome
extends AbstractSAXFilter
{
public Awesome()
{
super();
}
public Awesome( AbstractSAXFilter parent )
{
super( parent );
}
@Override
public void comment( char[] ch, int start, int length )
throws SAXException
{
String s = "AweSome";
super.comment( s.toCharArray(), 0, s.length() );
}
@Override
public void characters( char[] ch, int start, int length )
throws SAXException
{
String s = "AweSome";
super.characters( s.toCharArray(), 0, s.length() );
}
}
}

View File

@ -1,64 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.xml.sax.ext.LexicalHandler;
public class CommentRenormalizerTest
{
private LexicalHandler lexicalHandler = mock( LexicalHandler.class );
@ParameterizedTest
@ValueSource( strings = { "\n", "\r\n", "\r" } )
public void singleLine( String lineSeparator )
throws Exception
{
CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
char[] ch = "single line".toCharArray();
commentRenormalizer.comment( ch, 0, ch.length );
verify( lexicalHandler ).comment( ch, 0, ch.length );
}
@ParameterizedTest
@ValueSource( strings = { "\n", "\r\n", "\r" } )
public void multiLine( String lineSeparator )
throws Exception
{
CommentRenormalizer commentRenormalizer = new CommentRenormalizer( lexicalHandler, lineSeparator );
String text = "I%sam%sthe%sbest%s";
char[] chIn = String.format( text, "\n", "\n", "\n", "\n" ).toCharArray();
char[] chOut = String.format( text, lineSeparator, lineSeparator, lineSeparator, lineSeparator ).toCharArray();
commentRenormalizer.comment( chIn, 0, chIn.length );
verify( lexicalHandler ).comment( chOut, 0, chOut.length );
}
}

View File

@ -1,41 +0,0 @@
package org.apache.maven.model.transform.sax;
/*
* 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 static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import org.junit.jupiter.api.Test;
public class SAXEventUtilsTest
{
@Test
public void replaceWithNamespace()
{
assertThat( SAXEventUtils.renameQName( "org:bar", "com" ), is( "org:com" ) );
}
@Test
public void replaceWithoutNamespace()
{
assertThat( SAXEventUtils.renameQName( "bar", "com" ), is( "com" ) );
}
}