blob: 08f9f7c47a7bda3858084ea0c7ef5f55bf941a21 [file] [log] [blame]
package org.apache.maven.shared.filtering;
/*
* 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.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor;
import org.codehaus.plexus.interpolation.multi.DelimiterSpecification;
/**
* A FilterReader implementation, that works with Interpolator interface instead of it's own interpolation
* implementation. This implementation is heavily based on org.codehaus.plexus.util.InterpolationFilterReader.
*
* @author cstamas
* @author Olivier Lamy
* @version $Id$
* @since 1.0
*/
public class MultiDelimiterInterpolatorFilterReaderLineEnding
extends FilterReader
{
/** Interpolator used to interpolate */
private Interpolator interpolator;
private RecursionInterceptor recursionInterceptor;
/** replacement text from a token */
private String replaceData = null;
/** Index into replacement data */
private int replaceIndex = -1;
/** Index into previous data */
private int previousIndex = -1;
/** Default begin token. */
public static final String DEFAULT_BEGIN_TOKEN = "${";
/** Default end token. */
public static final String DEFAULT_END_TOKEN = "}";
/** true by default to preserve backward comp */
private boolean interpolateWithPrefixPattern = true;
private String escapeString;
private boolean useEscape = false;
/** if true escapeString will be preserved \{foo} -> \{foo} */
private boolean preserveEscapeString = false;
private LinkedHashSet delimiters = new LinkedHashSet();
private DelimiterSpecification currentSpec;
private String beginToken;
private String originalBeginToken;
private String endToken;
private boolean supportMultiLineFiltering;
private Character preserveChar = null;
/**
* This constructor uses default begin token ${ and default end token }.
*
* @param in reader to use
* @param interpolator interpolator instance to use
* @param supportMultiLineFiltering If multi line filtering is allowed
*/
public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
boolean supportMultiLineFiltering )
{
this( in, interpolator, new SimpleRecursionInterceptor(), supportMultiLineFiltering );
}
/**
* @param in reader to use
* @param interpolator interpolator instance to use
* @param ri The {@link RecursionInterceptor} to use to prevent recursive expressions.
* @param supportMultiLineFiltering If multi line filtering is allowed
*/
public MultiDelimiterInterpolatorFilterReaderLineEnding( Reader in, Interpolator interpolator,
RecursionInterceptor ri,
boolean supportMultiLineFiltering )
{
super( in );
this.interpolator = interpolator;
// always cache answers, since we'll be sending in pure expressions, not mixed text.
this.interpolator.setCacheAnswers( true );
recursionInterceptor = ri;
delimiters.add( DelimiterSpecification.DEFAULT_SPEC );
this.supportMultiLineFiltering = supportMultiLineFiltering;
}
public boolean removeDelimiterSpec( String delimiterSpec )
{
return delimiters.remove( DelimiterSpecification.parse( delimiterSpec ) );
}
public MultiDelimiterInterpolatorFilterReaderLineEnding setDelimiterSpecs( HashSet specs )
{
delimiters.clear();
for ( Iterator it = specs.iterator(); it.hasNext(); )
{
String spec = (String) it.next();
delimiters.add( DelimiterSpecification.parse( spec ) );
}
return this;
}
/**
* Skips characters. This method will block until some characters are available, an I/O error occurs, or the end of
* the stream is reached.
*
* @param n The number of characters to skip
* @return the number of characters actually skipped
* @exception IllegalArgumentException If <code>n</code> is negative.
* @exception IOException If an I/O error occurs
*/
public long skip( long n )
throws IOException
{
if ( n < 0L )
{
throw new IllegalArgumentException( "skip value is negative" );
}
for ( long i = 0; i < n; i++ )
{
if ( read() == -1 )
{
return i;
}
}
return n;
}
/**
* Reads characters into a portion of an array. This method will block until some input is available, an I/O error
* occurs, or the end of the stream is reached.
*
* @param cbuf Destination buffer to write characters to. Must not be <code>null</code>.
* @param off Offset at which to start storing characters.
* @param len Maximum number of characters to read.
* @return the number of characters read, or -1 if the end of the stream has been reached
* @exception IOException If an I/O error occurs
*/
public int read( char cbuf[], int off, int len )
throws IOException
{
for ( int i = 0; i < len; i++ )
{
int ch = read();
if ( ch == -1 )
{
if ( i == 0 )
{
return -1;
}
else
{
return i;
}
}
cbuf[off + i] = (char) ch;
}
return len;
}
/**
* Returns the next character in the filtered stream, replacing tokens from the original stream.
*
* @return the next character in the resulting stream, or -1 if the end of the resulting stream has been reached
* @exception IOException if the underlying stream throws an IOException during reading
*/
public int read()
throws IOException
{
if ( replaceIndex != -1 && replaceIndex < replaceData.length() )
{
int ch = replaceData.charAt( replaceIndex++ );
if ( replaceIndex >= replaceData.length() )
{
replaceIndex = -1;
}
return ch;
}
if ( preserveChar != null )
{
char copy = Character.valueOf( preserveChar.charValue() ).charValue();
preserveChar = null;
replaceIndex = -1;
return copy;
}
int ch = -1;
if ( previousIndex != -1 && previousIndex < this.endToken.length() )
{
ch = this.endToken.charAt( previousIndex++ );
}
else
{
ch = in.read();
}
if ( ch == '\n' && !supportMultiLineFiltering )
{
previousIndex = -1;
return ch;
}
boolean inEscape = false;
if ( ( inEscape = ( useEscape && ch == escapeString.charAt( 0 ) ) ) || reselectDelimiterSpec( ch ) )
{
StringBuffer key = new StringBuffer( );
key.append( (char) ch );
// this will happen when we're using an escape string, and ONLY then.
boolean atEnd = false;
if ( inEscape )
{
for ( int i = 0; i < escapeString.length() - 1; i++ )
{
ch = in.read();
if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
{
atEnd = true;
break;
}
key.append( (char) ch );
}
if ( !atEnd )
{
ch = in.read();
if ( !reselectDelimiterSpec( ch ) )
{
// here we are after the escape but didn't found the a startToken
// but we have read this means it will be removed
// so we preserve it
replaceData = key.toString();
replaceIndex = 1;
preserveChar = Character.valueOf( (char) ch );
return replaceData.charAt( 0 );
}
else
{
key.append( (char) ch );
}
}
}
int beginTokenMatchPos = 1;
do
{
if ( atEnd )
{
// didn't finish reading the escape string.
break;
}
if ( previousIndex != -1 && previousIndex < this.endToken.length() )
{
ch = this.endToken.charAt( previousIndex++ );
}
else
{
ch = in.read();
}
if ( ch == '\n' && !supportMultiLineFiltering )
{
// EOL
key.append( (char) ch );
break;
}
if ( ch != -1 )
{
key.append( (char) ch );
if ( ( beginTokenMatchPos < this.originalBeginToken.length() )
&& ( ch != this.originalBeginToken.charAt( beginTokenMatchPos ) ) )
{
ch = -1; // not really EOF but to trigger code below
break;
}
}
else
{
break;
}
beginTokenMatchPos++;
}
while ( ch != this.endToken.charAt( 0 ) );
// now test endToken
if ( ch != -1 && ( ch != '\n' && !supportMultiLineFiltering ) && this.endToken.length() > 1 )
{
int endTokenMatchPos = 1;
do
{
if ( previousIndex != -1 && previousIndex < this.endToken.length() )
{
ch = this.endToken.charAt( previousIndex++ );
}
else
{
ch = in.read();
}
if ( ch != -1 )
{
key.append( (char) ch );
if ( ch != this.endToken.charAt( endTokenMatchPos++ )
|| ( ch != '\n' && !supportMultiLineFiltering ) )
{
ch = -1; // not really EOF but to trigger code below
break;
}
}
else
{
break;
}
}
while ( endTokenMatchPos < this.endToken.length() );
}
// There is nothing left to read so we have the situation where the begin/end token
// are in fact the same and as there is nothing left to read we have got ourselves
// end of a token boundary so let it pass through.
if ( ch == -1 || ( ch == '\n' && !supportMultiLineFiltering ) )
{
replaceData = key.toString();
replaceIndex = 1;
return replaceData.charAt( 0 );
}
String value = null;
try
{
boolean escapeFound = false;
if ( useEscape )
{
if ( key.toString().startsWith( beginToken ) )
{
String keyStr = key.toString();
if ( !preserveEscapeString )
{
value = keyStr.substring( escapeString.length(), keyStr.length() );
}
else
{
value = keyStr;
}
escapeFound = true;
}
}
if ( !escapeFound )
{
if ( interpolateWithPrefixPattern )
{
value = interpolator.interpolate( key.toString(), "", recursionInterceptor );
}
else
{
value = interpolator.interpolate( key.toString(), recursionInterceptor );
}
}
}
catch ( InterpolationException e )
{
IllegalArgumentException error = new IllegalArgumentException( e.getMessage() );
error.initCause( e );
throw error;
}
if ( value != null )
{
if ( value.length() != 0 )
{
replaceData = value;
replaceIndex = 0;
}
return read();
}
else
{
previousIndex = 0;
replaceData = key.substring( 0, key.length() - this.endToken.length() );
replaceIndex = 0;
return this.beginToken.charAt( 0 );
}
}
return ch;
}
private boolean reselectDelimiterSpec( int ch )
{
for ( Iterator it = delimiters.iterator(); it.hasNext(); )
{
DelimiterSpecification spec = (DelimiterSpecification) it.next();
if ( ch == spec.getBegin().charAt( 0 ) )
{
currentSpec = spec;
originalBeginToken = currentSpec.getBegin();
beginToken = useEscape ? escapeString + originalBeginToken : originalBeginToken;
endToken = currentSpec.getEnd();
return true;
}
}
return false;
}
public boolean isInterpolateWithPrefixPattern()
{
return interpolateWithPrefixPattern;
}
public void setInterpolateWithPrefixPattern( boolean interpolateWithPrefixPattern )
{
this.interpolateWithPrefixPattern = interpolateWithPrefixPattern;
}
public String getEscapeString()
{
return escapeString;
}
public void setEscapeString( String escapeString )
{
// TODO NPE if escapeString is null ?
if ( escapeString != null && escapeString.length() >= 1 )
{
this.escapeString = escapeString;
this.useEscape = escapeString != null && escapeString.length() >= 1;
}
}
public boolean isPreserveEscapeString()
{
return preserveEscapeString;
}
public void setPreserveEscapeString( boolean preserveEscapeString )
{
this.preserveEscapeString = preserveEscapeString;
}
public RecursionInterceptor getRecursionInterceptor()
{
return recursionInterceptor;
}
public MultiDelimiterInterpolatorFilterReaderLineEnding setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
{
this.recursionInterceptor = recursionInterceptor;
return this;
}
}