From cbec1179b387e9b9bb53a5d577eb13395961eb38 Mon Sep 17 00:00:00 2001 From: Kristian Rosenvold Date: Sun, 1 Feb 2015 11:03:16 +0100 Subject: [PATCH 1/3] Added InterpolationCache first version --- dependency-reduced-pom.xml | 82 ++++++ pom.xml | 43 +++ .../fixed/FixedStringSearchInterpolator.java | 32 ++- .../fixed/InterpolationContextCache.java | 204 +++++++++++++++ .../fixed/InterpolationContextCacheTest.java | 245 ++++++++++++++++++ 5 files changed, 597 insertions(+), 9 deletions(-) create mode 100644 dependency-reduced-pom.xml create mode 100644 src/main/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCache.java create mode 100644 src/test/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCacheTest.java diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..0db3020 --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,82 @@ + + + + plexus + org.codehaus.plexus + 3.3.3 + ../pom.xml/pom.xml + + 4.0.0 + plexus-interpolation + Plexus Interpolation API + 1.23-SNAPSHOT + + JIRA + https://github.com/codehaus-plexus/plexus-interpolation/issues + + + scm:git:git@github.com:codehaus-plexus/plexus-interpolation.git + scm:git:git@github.com:codehaus-plexus/plexus-interpolation.git + http://github.com/codehaus-plexus/plexus-interpolation + + + + + maven-compiler-plugin + + 1.6 + 1.6 + + + + maven-release-plugin + + + **/src/test/resources/utf8/** + + + + + maven-shade-plugin + 2.3 + + + package + + shade + + + true + + + commons-io:commons-io + + + + + org.apache.commons.io + org.codehaus.plexus.interpolation.io + + + + + + + + + + + junit + junit + 4.12 + test + + + hamcrest-core + org.hamcrest + + + + + + diff --git a/pom.xml b/pom.xml index ad76542..7d198c1 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + org.apache.maven.plugins maven-release-plugin @@ -38,6 +47,40 @@ + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + + + commons-io:commons-io + + + + + org.apache.commons.io + org.codehaus.plexus.interpolation.io + + + + + + + + + commons-io + commons-io + 2.2 + + diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java index 95b15d6..92f5824 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java @@ -29,14 +29,14 @@ * A fixed string search interpolator is permanently bound to a given set of value sources, * an is totally fixed and stateless over these value sources. *

- * The fixed interpolator is also a #StatelessValueSource and can be used as a source + * The fixed interpolator is also a {@link FixedValueSource} and can be used as a source * for a different fixed interpolator, creating a scope chain. *

* Once constructed, this interpolator will always point to the same set of objects (value sources), * in such a way that if the underlying object is fixed, expressions will always * evaluate to the same result. *

- * Th fixed interpolator can be shared among different clients and is thread safe to + * The fixed interpolator can be shared among different clients and is thread safe to * the extent the underlying value sources can be accessed safely. * Since interpolation expressions cannot modify the objects, thread safety concerns * this will normally be limited to safe publication and memory model visibility of @@ -63,12 +63,17 @@ public class FixedStringSearchInterpolator private final String escapeString; + private final InterpolationContextCache cache; + private FixedStringSearchInterpolator( String startExpr, String endExpr, String escapeString, - InterpolationPostProcessor postProcessor, FixedValueSource... valueSources ) + InterpolationPostProcessor postProcessor, + InterpolationContextCache cache, + FixedValueSource... valueSources ) { this.startExpr = startExpr; this.endExpr = endExpr; this.escapeString = escapeString; + this.cache = cache; if ( valueSources == null ) { throw new IllegalArgumentException( "valueSources cannot be null" ); @@ -88,13 +93,18 @@ private FixedStringSearchInterpolator( String startExpr, String endExpr, String public static FixedStringSearchInterpolator create( String startExpr, String endExpr, FixedValueSource... valueSources ) { - return new FixedStringSearchInterpolator( startExpr, endExpr, null, null, valueSources ); + return new FixedStringSearchInterpolator( startExpr, endExpr, null, null, null, valueSources ); } public static FixedStringSearchInterpolator create( FixedValueSource... valueSources ) { - return new FixedStringSearchInterpolator( DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, valueSources ); + return new FixedStringSearchInterpolator( DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, null, valueSources ); + } + + public static FixedStringSearchInterpolator create( InterpolationContextCache cache, FixedValueSource... valueSources ) + { + return new FixedStringSearchInterpolator( DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, cache, valueSources ); } public static FixedStringSearchInterpolator createWithPermittedNulls( FixedValueSource... valueSources ) @@ -104,23 +114,23 @@ public static FixedStringSearchInterpolator createWithPermittedNulls( FixedValue { if (item != null) nonnulls.add( item); } - return new FixedStringSearchInterpolator( DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, nonnulls.toArray(new FixedValueSource[nonnulls.size()]) ); + return new FixedStringSearchInterpolator( DEFAULT_START_EXPR, DEFAULT_END_EXPR, null, null, null, nonnulls.toArray(new FixedValueSource[nonnulls.size()]) ); } public FixedStringSearchInterpolator withExpressionMarkers( String startExpr, String endExpr ) { - return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, valueSources ); + return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, null, valueSources ); } public FixedStringSearchInterpolator withPostProcessor( InterpolationPostProcessor postProcessor ) { - return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, valueSources ); + return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, null, valueSources ); } public FixedStringSearchInterpolator withEscapeString( String escapeString ) { - return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, valueSources ); + return new FixedStringSearchInterpolator( startExpr, endExpr, escapeString, postProcessor, null, valueSources ); } public String interpolate( String input ) @@ -257,6 +267,10 @@ public String interpolate( String input, InterpolationState interpolationState ) { value = interpolate( String.valueOf( value ), interpolationState ); + if (cache != null) { + cache.putValue( realExpr, value ); + } + if ( postProcessor != null ) { Object newVal = postProcessor.execute( realExpr, value ); diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCache.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCache.java new file mode 100644 index 0000000..5616fb5 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCache.java @@ -0,0 +1,204 @@ +/* + Copyright 2015 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package org.codehaus.plexus.interpolation.fixed; + +import org.apache.commons.io.FileUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedHashMap; + +/** + * @author Kristian Rosenvold + */ +public class InterpolationContextCache +{ + private final LinkedHashMap values = new LinkedHashMap(); + + private final MessageDigest md = getMesageDigestInstance(); + + private final File cacheFile; + + private boolean cacheable = true; + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + public InterpolationContextCache( File cacheFile ) + { + this.cacheFile = cacheFile; + } + + /** + * Indicates if the cached interpolation expressions resolve to the same values in the new interpolator + * + * @param interpolator The new interpolator + * @param interpolationState The interpolation state, will be cleared completion of this method + * @return true if the new interpolator resolves to the same values as the old one. If false, the caller should + * always re-interpolate, no matter what + * @throws IOException + */ + public boolean resolvesToSameValues( FixedValueSource interpolator, InterpolationState interpolationState ) + throws IOException + { + if (!hasCacheFile()) return false; + BufferedReader br = new BufferedReader( new FileReader( cacheFile ) ); + String sha1 = br.readLine(); + String key; + Object value; + + while ( ( key = br.readLine() ) != null ) + { + value = interpolator.getValue( key, interpolationState ); + addToHash( value ); + } + + interpolationState.clear(); + return getHexHash().equals( sha1 ); + } + + private static MessageDigest getMesageDigestInstance() + { + try + { + return MessageDigest.getInstance( "SHA-1" ); + } + catch ( NoSuchAlgorithmException e ) + { + throw new RuntimeException( e ); + } + } + + /** + * Registers the result of an interpolation + * + * @param key The interpolation key + * @param value The resolved value + */ + + public InterpolationContextCache putValue( String key, Object value ) + { + Object existing = values.get( key ); + if ( existing != null ) + { + if ( !existing.equals( value ) ) + { + cacheable = false; + } + } + else + { + values.put( key, value ); + } + return this; + } + + public InterpolationContextCache store() + throws IOException + { + if ( !cacheable ) + { + if ( cacheFile.exists() ) + { + FileUtils.deleteQuietly( cacheFile ); + } + return this; + } + FileOutputStream fos = new FileOutputStream( cacheFile ); + Writer writer = new OutputStreamWriter( fos, UTF_8 ); + writer.write( getSha1( values.values() ) ); + writer.write( '\n' ); + for ( String s : values.keySet() ) + { + writer.write( s ); + writer.write( '\n' ); + } + writer.close(); + return this; + } + + /** + * Inquires if we have to filter no matter what, based on file attributes + * @param targetFile The filtered output file + * @return True if the source file is newer than the target, or the target does not exist + */ + public boolean mustFilterDueToUpdateCheck( File sourceFile, File targetFile ) + { + return !targetFile.exists() || sourceFile.lastModified() > targetFile.lastModified(); + } + + /** + * Indicates if we have a cache file + * @return True if we have a cache file + */ + private boolean hasCacheFile( ){ + return cacheFile.exists(); + } + + + private String getSha1( Iterable values ) + { + for ( Object value : values ) + { + addToHash( value ); + } + + return getHexHash(); + } + + private String getHexHash() + { + byte[] sha1hash = md.digest(); + md.reset(); + return asHexString( sha1hash ); + } + + private void addToHash( Object value ) + { + // dont really care which encoding is used as long as we're consistent + md.update( value.toString().getBytes( UTF_8 ) ); + } + + @SuppressWarnings( "checkstyle:magicnumber" ) + private static String asHexString( byte[] bytes ) + { + if ( bytes == null ) + { + return null; + } + final StringBuilder result = new StringBuilder( 2 * bytes.length ); + for ( byte b : bytes ) + { + result.append( HEX.charAt( ( b & 0xF0 ) >> 4 ) ).append( HEX.charAt( ( b & 0x0F ) ) ); + } + return result.toString(); + } + + private static final String HEX = "0123456789ABCDEF"; + + public File getCacheFile() + { + return cacheFile; + } +} diff --git a/src/test/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCacheTest.java b/src/test/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCacheTest.java new file mode 100644 index 0000000..8009f2b --- /dev/null +++ b/src/test/java/org/codehaus/plexus/interpolation/fixed/InterpolationContextCacheTest.java @@ -0,0 +1,245 @@ +/* + Copyright 2015 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package org.codehaus.plexus.interpolation.fixed; + +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator.create; +import static org.junit.Assert.*; + +public class InterpolationContextCacheTest +{ + + private static final int MINIMAL_FS_TIMESTAMP_GRANULARITY = 2000; + + private File testDir = new File( "target/output" ); + + { + //noinspection ResultOfMethodCallIgnored + testDir.mkdirs(); + } + + private final String initalpayload = "a${v1}payload\n"; + + private final String expectedPayload = "aabcpayload\n"; + + @Test + public void basicStoreOfAttrs() + throws Exception + { + assertContents( getBasicCache( new File( testDir, "testStore.txt" ) ).store() ); + } + + @Test + public void repeatedValuesShouldProduceSameResults() + throws Exception + { + assertContents( getBasicCache( new File( testDir, "testStore2.txt" ) ).putValue( "v1", "abc" ).store() ); + } + + + @Test + public void shouldDeletesExistingWhenNowUncacheableDoesNotStore() + throws Exception + { + InterpolationContextCache ic = getBasicCache( new File( testDir, "testStore3.txt" ) ); + ic.store(); + assertTrue( ic.getCacheFile().exists() ); + ic.putValue( "v1", "abdc" ); + ic.store(); // removes existing file + assertFalse( ic.getCacheFile().exists() ); + ic.store(); // does not store + assertFalse( ic.getCacheFile().exists() ); + } + + @Test + public void shouldNotStoreWhenUncacheable() + throws Exception + { + InterpolationContextCache ic = getBasicCache( new File( testDir, "testStore4.txt" ) ); + ic.putValue( "v1", "abdc" ); + ic.store(); // does not store + assertFalse( ic.getCacheFile().exists() ); + } + + @Test + public void shouldBeAbleToResolveToSame() + throws IOException + { + InterpolationContextCache ic = getBasicCache( new File( testDir, "testStore5.txt" ) ); + ic.store(); + + assertTrue( ic.resolvesToSameValues( getMatchingValueSource(), new InterpolationState() ) ); + } + + @Test + public void shouldNotBeAbleToResolveToSame() + throws IOException + { + InterpolationContextCache ic = getBasicCache( new File( testDir, "testStore6.txt" ) ); + ic.store(); + + assertFalse( ic.resolvesToSameValues( getNonMatchingValueSource(), new InterpolationState() ) ); + } + + @Test + public void needsToFilter() + throws IOException + { + File cacheFile = interpolateOnceToWriteCacheFile( "needsToFilter" ); + + InterpolationContextCache nextRun = getBasicCache( cacheFile ); + + FixedStringSearchInterpolator nextInterpolator = create( nextRun, getNonMatchingValueSource() ); + + assertFalse( nextRun.resolvesToSameValues( nextInterpolator, new InterpolationState() ) ); + } + + @Test + public void doesNotNeedToFilter() + throws IOException + { + File cacheFile = interpolateOnceToWriteCacheFile( "needsToFilter" ); + + InterpolationContextCache nextRun = getBasicCache( cacheFile ); + + FixedStringSearchInterpolator nextInterpolator = create( nextRun, getMatchingValueSource() ); + + assertTrue( nextRun.resolvesToSameValues( nextInterpolator, new InterpolationState() ) ); + } + + @Test + public void mustFilterNoMatterWhatWhenTimeStampChanges() + throws IOException + { + long timeStamp = System.currentTimeMillis(); + File sourceFile = writeTestFile( "shouldNotNeedToFilter", initalpayload, timeStamp ); + File target = writeTestFile( "shouldNotNeedToFilter.interpolated", expectedPayload, timeStamp ); + + InterpolationContextCache nextRun = getBasicCache( interpolateOnceToWriteCacheFile( "needsToFilter" ) ); + + assertFalse( nextRun.mustFilterDueToUpdateCheck( sourceFile, target ) ); + + assertTrue( sourceFile.setLastModified( timeStamp + MINIMAL_FS_TIMESTAMP_GRANULARITY ) ); + + assertTrue( nextRun.mustFilterDueToUpdateCheck( sourceFile, target ) ); + } + + @Test + public void missingCacheFileAlways() + throws IOException + { + InterpolationContextCache nextRun = getBasicCache( new File( "NonExisting" ) ); + + FixedStringSearchInterpolator nextInterpolator = create( nextRun, getNonMatchingValueSource() ); + + assertFalse( nextRun.resolvesToSameValues( nextInterpolator, new InterpolationState() ) ); + } + + @Test + public void mustFilterNoMatterWithoutTargetFile() + throws IOException + { + long timeStamp = System.currentTimeMillis(); + File sourceFile = writeTestFile( "shouldNotNeedToFilter", initalpayload, timeStamp ); + File target = new File("doesNotExist" ); + + + InterpolationContextCache nextRun = getBasicCache( new File("cacheFile" )); + + assertTrue( nextRun.mustFilterDueToUpdateCheck( sourceFile, target ) ); + } + + + + private File interpolateOnceToWriteCacheFile( String cacheFileName ) + throws IOException + { + File cacheFile = createTestFile( cacheFileName + ".cacheFile" ); + InterpolationContextCache basicCache = getBasicCache( cacheFile ); + + FixedStringSearchInterpolator interpolator = create( basicCache, getMatchingValueSource() ); + + InterpolationState interpolationState = new InterpolationState(); + String actual = interpolator.interpolate( initalpayload, interpolationState ); + assertEquals( expectedPayload, actual ); + + basicCache.store(); + return cacheFile; + } + + @SuppressWarnings( "ResultOfMethodCallIgnored" ) + private File writeTestFile( String fileName, String a1payload, long timeStamp ) + throws IOException + { + File file = createTestFile( fileName ); + FileOutputStream fos = new FileOutputStream( file ); + fos.write( a1payload.getBytes( "utf-8" )); + fos.close(); + file.setLastModified( timeStamp ); + return file; + + } + + private File createTestFile( String fileName ) + { + return new File( testDir, fileName ); + } + + private InterpolationContextCache getBasicCache( File cachefile ) + { + InterpolationContextCache ic = new InterpolationContextCache( cachefile ); + ic.putValue( "v1", "abc" ); + ic.putValue( "v2", "cde" ); + return ic; + } + + private FixedValueSource getMatchingValueSource() + { + Map values = new HashMap(); + values.put( "v1", "abc" ); + values.put( "v2", "cde" ); + return new MapBasedValueSource( values ); + } + + private FixedValueSource getNonMatchingValueSource() + { + Map values = new HashMap(); + values.put( "v1", "aDDc" ); + values.put( "v2", "cde" ); + return new MapBasedValueSource( values ); + } + + private void assertContents( InterpolationContextCache ic ) + throws IOException + { + BufferedReader br = new BufferedReader( new FileReader( ic.getCacheFile() ) ); + assertEquals( "D9F4E651F88121479D8C6CDA4441ECBA65687415", br.readLine() ); + assertEquals( "v1", br.readLine() ); + assertEquals( "v2", br.readLine() ); + br.close(); + } + +} \ No newline at end of file From a7399f315e709687e33fc99ba967a3f9b91fd404 Mon Sep 17 00:00:00 2001 From: Kristian Rosenvold Date: Sun, 1 Feb 2015 22:43:37 +0100 Subject: [PATCH 2/3] Added 2 more fixed interpolators with tests --- dependency-reduced-pom.xml | 4 +- pom.xml | 4 +- .../fixed/FixedInterpolator.java | 38 +++ ...ultiDelimiterStringSearchInterpolator.java | 277 ++++++++++++++++ .../fixed/FixedRegexBasedInterpolator.java | 295 ++++++++++++++++++ .../fixed/FixedStringSearchInterpolator.java | 2 +- ...DelimiterStringSearchInterpolatorTest.java | 59 ++++ .../FixedRegexBasedInterpolatorTest.java | 170 ++++++++++ 8 files changed, 844 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/codehaus/plexus/interpolation/fixed/FixedInterpolator.java create mode 100644 src/main/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolator.java create mode 100644 src/main/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolator.java create mode 100644 src/test/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolatorTest.java create mode 100644 src/test/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolatorTest.java diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 0db3020..001fdea 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -24,8 +24,8 @@ maven-compiler-plugin - 1.6 - 1.6 + 1.5 + 1.5 diff --git a/pom.xml b/pom.xml index 7d198c1..901d2db 100644 --- a/pom.xml +++ b/pom.xml @@ -32,8 +32,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.5 + 1.5 diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedInterpolator.java new file mode 100644 index 0000000..0838ca2 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedInterpolator.java @@ -0,0 +1,38 @@ +package org.codehaus.plexus.interpolation.fixed; +/* + * Copyright 2014 Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.codehaus.plexus.interpolation.InterpolationException; +import org.codehaus.plexus.interpolation.RecursionInterceptor; + +/** + * A stateless interpolator that can be re-used safely. + * May also be thread safe, depending on safety of underlying model objects + * + */ +public interface FixedInterpolator +{ + /** + * Interpolate the supplied string with the enclosed interpolationstate + * @param interpolationState the state of the interpolation operation + * @param input The input string to interpolate + * @return the interpolated value + */ + + public String interpolate( String input, InterpolationState interpolationState ) + throws InterpolationCycleException; + +} diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolator.java new file mode 100644 index 0000000..5b6ddb1 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolator.java @@ -0,0 +1,277 @@ +package org.codehaus.plexus.interpolation.fixed; + +/* + * Copyright 2001-2009 Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.codehaus.plexus.interpolation.InterpolationPostProcessor; +import org.codehaus.plexus.interpolation.multi.DelimiterSpecification; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +public class FixedMultiDelimiterStringSearchInterpolator + implements FixedInterpolator +{ + + private static final int MAX_TRIES = 10; + + private final List valueSources; + + private final InterpolationPostProcessor postProcessors; + + private final LinkedHashSet delimiters; + + private String escapeString; + + public FixedMultiDelimiterStringSearchInterpolator( List valueSources, InterpolationPostProcessor postProcessors, + LinkedHashSet delimiters ) + { + this.valueSources = valueSources; + this.postProcessors = postProcessors; + this.delimiters = delimiters; + this.delimiters.add( DelimiterSpecification.DEFAULT_SPEC ); + } + + public static FixedMultiDelimiterStringSearchInterpolator create( ) + { + LinkedHashSet delimiters = new LinkedHashSet( 1 ); + delimiters.add(DelimiterSpecification.DEFAULT_SPEC ); + return new FixedMultiDelimiterStringSearchInterpolator( new ArrayList( ), null, delimiters); + } + + public FixedMultiDelimiterStringSearchInterpolator withDelimiterSpec( DelimiterSpecification vs ) + { + LinkedHashSet newList = new LinkedHashSet( delimiters ); + newList.add( vs); + return new FixedMultiDelimiterStringSearchInterpolator( valueSources, postProcessors, newList); + } + + public FixedMultiDelimiterStringSearchInterpolator withDelimiterSpec( String delimiterSpec ) + { + return withDelimiterSpec( DelimiterSpecification.parse( delimiterSpec )); + } + + public FixedMultiDelimiterStringSearchInterpolator withDelimiterSpec( Iterable delimiterSpec ) + { + FixedMultiDelimiterStringSearchInterpolator current = this; + for ( String s : delimiterSpec ) + { + current = current.withDelimiterSpec( s ); + } + return current; + } + + + public FixedMultiDelimiterStringSearchInterpolator addDelimiterSpec( String delimiterSpec ) + { + if ( delimiterSpec == null ) + { + return this; + } + delimiters.add( DelimiterSpecification.parse( delimiterSpec ) ); + return this; + } + + + public FixedMultiDelimiterStringSearchInterpolator withValueSource( FixedValueSource vs ) + { + addValueSource( vs ); + return this; + } + + public FixedMultiDelimiterStringSearchInterpolator withPostProcessor( InterpolationPostProcessor postProcessor ) + { + return new FixedMultiDelimiterStringSearchInterpolator( valueSources, postProcessor, delimiters ); + } + + /** + * {@inheritDoc} + */ + public void addValueSource( FixedValueSource valueSource ) + { + valueSources.add( valueSource ); + } + + + public String interpolate( String input, InterpolationState interpolationState ) + { + if ( input == null ) + { + // return empty String to prevent NPE too + return ""; + } + StringBuilder result = new StringBuilder( input.length() * 2 ); + + String lastResult = input; + int tries = 0; + do + { + tries++; + if ( result.length() > 0 ) + { + lastResult = result.toString(); + result.setLength( 0 ); + } + + int startIdx = -1; + int endIdx = -1; + + DelimiterSpecification selectedSpec; + while( ( selectedSpec = select( input, endIdx ) ) != null ) + { + String startExpr = selectedSpec.getBegin(); + String endExpr = selectedSpec.getEnd(); + + startIdx = selectedSpec.getNextStartIndex(); + result.append( input, endIdx + 1, startIdx ); + + endIdx = input.indexOf( endExpr, startIdx + 1 ); + if ( endIdx < 0 ) + { + break; + } + + String wholeExpr = input.substring( startIdx, endIdx + endExpr.length() ); + String realExpr = wholeExpr.substring( startExpr.length(), wholeExpr.length() - endExpr.length() ); + + if ( startIdx >= 0 && escapeString != null && escapeString.length() > 0 ) + { + int startEscapeIdx = startIdx == 0 ? 0 : startIdx - escapeString.length(); + if ( startEscapeIdx >= 0 ) + { + String escape = input.substring( startEscapeIdx, startIdx ); + if ( escape != null && escapeString.equals( escape ) ) + { + result.append( wholeExpr ); + result.replace( startEscapeIdx, startEscapeIdx + escapeString.length(), "" ); + continue; + } + } + } + + boolean resolved = false; + if ( ! interpolationState.unresolvable.contains( wholeExpr ) ) + { + if ( realExpr.startsWith( "." ) ) + { + realExpr = realExpr.substring( 1 ); + } + + if ( interpolationState.recursionInterceptor.hasRecursiveExpression( realExpr ) ) + { + throw new InterpolationCycleException( interpolationState.recursionInterceptor, realExpr, wholeExpr ); + } + + interpolationState.recursionInterceptor.expressionResolutionStarted( realExpr ); + + Object value = null; + Object bestAnswer = null; + for ( FixedValueSource vs : valueSources ) + { + if (value != null ) break; + + value = vs.getValue( realExpr, interpolationState ); + + if ( value != null && value.toString().contains( wholeExpr ) ) + { + bestAnswer = value; + value = null; + } + } + + // this is the simplest recursion check to catch exact recursion + // (non synonym), and avoid the extra effort of more string + // searching. + if ( value == null && bestAnswer != null ) + { + throw new InterpolationCycleException( interpolationState.recursionInterceptor, realExpr, wholeExpr ); + } + + if ( value != null ) + { + value = interpolate( String.valueOf( value ), interpolationState ); + + if ( postProcessors != null ) + { + Object newVal = postProcessors.execute( realExpr, value ); + if ( newVal != null ) + { + value = newVal; + } + } + + // could use: + // result = matcher.replaceFirst( stringValue ); + // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour + result.append( String.valueOf( value ) ); + resolved = true; + } + else + { + interpolationState.unresolvable.add( wholeExpr ); + } + + interpolationState.recursionInterceptor.expressionResolutionFinished( realExpr ); + } + + if ( !resolved ) + { + result.append( wholeExpr ); + } + + if ( endIdx > -1 ) + { + endIdx += endExpr.length() - 1; + } + } + + if ( endIdx == -1 && startIdx > -1 ) + { + result.append( input, startIdx, input.length() ); + } + else if ( endIdx < input.length() ) + { + result.append( input, endIdx + 1, input.length() ); + } + } + while( !lastResult.equals( result.toString() ) && tries < MAX_TRIES ); + + return result.toString(); + } + + private DelimiterSpecification select( String input, int lastEndIdx ) + { + DelimiterSpecification selected = null; + + for ( DelimiterSpecification spec : delimiters ) + { + spec.clearNextStart(); + + if ( selected == null ) + { + int idx = input.indexOf( spec.getBegin(), lastEndIdx + 1 ); + if ( idx > -1 ) + { + spec.setNextStartIndex( idx ); + selected = spec; + } + } + } + + return selected; + } +} diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolator.java new file mode 100644 index 0000000..9411417 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolator.java @@ -0,0 +1,295 @@ +package org.codehaus.plexus.interpolation.fixed; + +/* + * Copyright 2001-2008 Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.codehaus.plexus.interpolation.InterpolationPostProcessor; +import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor; +import org.codehaus.plexus.interpolation.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Expansion of the original RegexBasedInterpolator, found in plexus-utils, this + * interpolator provides options for setting custom prefix/suffix regex parts, + * and includes a {@link org.codehaus.plexus.interpolation.RecursionInterceptor} parameter in its interpolate(..) + * call, to allow the detection of cyclical expression references. + * + * @version $Id$ + */ +public class FixedRegexBasedInterpolator + implements FixedInterpolator +{ + + private final String startRegex; + + private final String endRegex; + + private final String prefixPattern; + + private final List valueSources; + + private final InterpolationPostProcessor postProcessor; + + private boolean reusePatterns = false; + + public static final String DEFAULT_REGEXP = "\\$\\{(.+?)\\}"; + + /** + * the key is the regex the value is the Pattern + * At the class construction time the Map will contains the default Pattern + */ + private Map compiledPatterns = new WeakHashMap(); + + private FixedRegexBasedInterpolator( String startRegex, String endRegex, String prefixPattern, + List valueSources, InterpolationPostProcessor postProcessor, + boolean reusePatterns ) + { + this.startRegex = startRegex; + this.endRegex = endRegex; + this.valueSources = valueSources; + this.prefixPattern = prefixPattern != null && prefixPattern.length() > 0 ? prefixPattern : null; + this.postProcessor = postProcessor; + this.reusePatterns = reusePatterns; + compiledPatterns.put( DEFAULT_REGEXP, Pattern.compile( DEFAULT_REGEXP ) ); + } + + + + public static FixedRegexBasedInterpolator create() + { + return new FixedRegexBasedInterpolator( null, null, null, new ArrayList(), null, false ); + } + + + public static FixedRegexBasedInterpolator create(String startRegex, String endRegex, FixedValueSource... fixedValueSources) + { + return FixedRegexBasedInterpolator.create().withStartRegex( startRegex ) + .withEndRegex( endRegex ).withValueSources( fixedValueSources ); + } + + public static FixedRegexBasedInterpolator create( FixedValueSource... fixedValueSources) + { + return create().withValueSources( fixedValueSources ); + } + + public FixedRegexBasedInterpolator withPrefix( String prefix ) + { + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefix, valueSources, postProcessor, + reusePatterns ); + } + + public FixedRegexBasedInterpolator withValueSources( FixedValueSource... valueSources ) + { + FixedRegexBasedInterpolator result = this; + for ( FixedValueSource valueSource : valueSources ) + { + result = withValueSource( valueSource ); + } + return result; + } + + public FixedRegexBasedInterpolator withValueSource( FixedValueSource vs ) + { + List sources = new ArrayList( valueSources ); + sources.add( vs ); + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefixPattern, sources, postProcessor, + reusePatterns ); + } + + public FixedRegexBasedInterpolator withStartRegex( String startRegex ) + { + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefixPattern, valueSources, postProcessor, + reusePatterns ); + } + + public FixedRegexBasedInterpolator withEndRegex( String endRegex ) + { + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefixPattern, valueSources, postProcessor, + reusePatterns ); + } + + public FixedRegexBasedInterpolator withPostProcessor( InterpolationPostProcessor postProcessor ) + { + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefixPattern, valueSources, postProcessor, + reusePatterns ); + } + + public FixedRegexBasedInterpolator reusePatterns( boolean reusePatterns ) + { + return new FixedRegexBasedInterpolator( startRegex, endRegex, prefixPattern, valueSources, postProcessor, + reusePatterns ); + } + + /** + * Attempt to resolve all expressions in the given input string, using the + * given pattern to first trim an optional prefix from each expression. The + * supplied recursion interceptor will provide protection from expression + * cycles, ensuring that the input can be resolved or an exception is + * thrown. + * + * @param input The input string to interpolate + * @param interpolationState The state used during interpolation. + */ + public String interpolate( String input, InterpolationState interpolationState ) + throws org.codehaus.plexus.interpolation.fixed.InterpolationCycleException + { + if ( input == null ) + { + // return empty String to prevent NPE too + return ""; + } + if ( interpolationState.recursionInterceptor == null ) + { + interpolationState.setRecursionInterceptor( new SimpleRecursionInterceptor() ); + } + + int realExprGroup = 2; + Pattern expressionPattern; + if ( startRegex != null || endRegex != null ) + { + if ( prefixPattern == null ) + { + expressionPattern = getPattern( startRegex + endRegex ); + realExprGroup = 1; + } + else + { + expressionPattern = getPattern( startRegex + prefixPattern + endRegex ); + } + + } + else if ( prefixPattern != null ) + { + expressionPattern = getPattern( "\\$\\{(" + prefixPattern + ")?(.+?)\\}" ); + } + else + { + expressionPattern = getPattern( DEFAULT_REGEXP ); + realExprGroup = 1; + } + + return interpolate( input, interpolationState, expressionPattern, realExprGroup ); + } + + private Pattern getPattern( String regExp ) + { + if ( !reusePatterns ) + { + return Pattern.compile( regExp ); + } + + Pattern pattern; + synchronized ( this ) + { + pattern = compiledPatterns.get( regExp ); + + if ( pattern != null ) + { + return pattern; + } + + pattern = Pattern.compile( regExp ); + compiledPatterns.put( regExp, pattern ); + } + + return pattern; + } + + /** + * Entry point for recursive resolution of an expression and all of its + * nested expressions. + * + * @todo Ensure unresolvable expressions don't trigger infinite recursion. + */ + private String interpolate( String input, InterpolationState interpolationState, Pattern expressionPattern, + int realExprGroup ) + { + if ( input == null ) + { + // return empty String to prevent NPE too + return ""; + } + String result = input; + + Matcher matcher = expressionPattern.matcher( result ); + + while ( matcher.find() ) + { + String wholeExpr = matcher.group( 0 ); + String realExpr = matcher.group( realExprGroup ); + + if ( realExpr.startsWith( "." ) ) + { + realExpr = realExpr.substring( 1 ); + } + + if ( interpolationState.recursionInterceptor.hasRecursiveExpression( realExpr ) ) + { + throw new InterpolationCycleException( interpolationState.recursionInterceptor, realExpr, wholeExpr ); + } + + interpolationState.recursionInterceptor.expressionResolutionStarted( realExpr ); + try + { + Object value = null; + for ( FixedValueSource vs : valueSources ) + { + if ( value != null ) + { + break; + } + + value = vs.getValue( realExpr, interpolationState ); + } + + if ( value != null ) + { + value = + interpolate( String.valueOf( value ), interpolationState, expressionPattern, realExprGroup ); + + if ( postProcessor != null ) + { + Object newVal = postProcessor.execute( realExpr, value ); + if ( newVal != null ) + { + value = newVal; + } + } + + // could use: + // result = matcher.replaceFirst( stringValue ); + // but this could result in multiple lookups of stringValue, and replaceAll is not correct behaviour + result = StringUtils.replace( result, wholeExpr, String.valueOf( value ) ); + + matcher.reset( result ); + } + } + finally + { + interpolationState.recursionInterceptor.expressionResolutionFinished( realExpr ); + } + } + + return result; + } + +} diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java index 92f5824..5cc49e9 100644 --- a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedStringSearchInterpolator.java @@ -46,7 +46,7 @@ * The fixed interpolator can be a valuesource */ public class FixedStringSearchInterpolator - implements FixedValueSource + implements FixedValueSource, FixedInterpolator { private final FixedValueSource[] valueSources; diff --git a/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolatorTest.java b/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolatorTest.java new file mode 100644 index 0000000..697571f --- /dev/null +++ b/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedMultiDelimiterStringSearchInterpolatorTest.java @@ -0,0 +1,59 @@ +package org.codehaus.plexus.interpolation.fixed; + +import org.codehaus.plexus.interpolation.*; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class FixedMultiDelimiterStringSearchInterpolatorTest +{ + + @Test + public void interpolationWithDifferentDelimiters() + throws InterpolationException + { + Map ctx = new HashMap(); + ctx.put( "name", "User" ); + ctx.put( "otherName", "@name@" ); + + String input = "${otherName}"; + + FixedValueSource vs = new MapBasedValueSource( ctx ); + FixedMultiDelimiterStringSearchInterpolator interpolator = FixedMultiDelimiterStringSearchInterpolator.create().withDelimiterSpec( + Arrays.asList( "@" ) ) + .withValueSource( vs ); + + InterpolationState is = new InterpolationState(); + + String result = interpolator.interpolate( input,is ); + + assertEquals( ctx.get( "name" ), result ); + } + + @Test + public void testSuccessiveInterpolationWithDifferentDelimiters_ReversedDelimiterSequence() + throws InterpolationException + { + Map ctx = new HashMap(); + ctx.put( "name", "User" ); + ctx.put( "otherName", "${name}" ); + + String input = "@otherName@"; + + FixedValueSource vs = new MapBasedValueSource( ctx ); + FixedMultiDelimiterStringSearchInterpolator interpolator = FixedMultiDelimiterStringSearchInterpolator.create().addDelimiterSpec( + "@" ) + .withValueSource( vs ); + + InterpolationState is = new InterpolationState(); + + String result = interpolator.interpolate( input, is ); + + assertEquals( ctx.get( "name" ), result ); + } + +} \ No newline at end of file diff --git a/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolatorTest.java b/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolatorTest.java new file mode 100644 index 0000000..d6731de --- /dev/null +++ b/src/test/java/org/codehaus/plexus/interpolation/fixed/FixedRegexBasedInterpolatorTest.java @@ -0,0 +1,170 @@ +package org.codehaus.plexus.interpolation.fixed; + +import org.codehaus.plexus.interpolation.InterpolationPostProcessor; +import org.codehaus.plexus.interpolation.SimpleRecursionInterceptor; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.*; + +public class FixedRegexBasedInterpolatorTest +{ + @SuppressWarnings( "UnusedDeclaration" ) + public String getVar() + { + return "testVar"; + } + + @Test + public void shouldFailOnExpressionCycle() + { + Properties props = new Properties(); + props.setProperty( "key1", "${key2}" ); + props.setProperty( "key2", "${key1}" ); + + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( + new org.codehaus.plexus.interpolation.fixed.PropertiesBasedValueSource( props ) ); + + InterpolationState is = new InterpolationState(); + is.setRecursionInterceptor( new SimpleRecursionInterceptor() ); + try + { + rbi.interpolate( "${key1}", is ); + + fail( "Should detect expression cycle and fail." ); + } + catch ( InterpolationCycleException e ) + { + // expected + } + } + + @Test + public void shouldResolveByMy_getVar_Method() + { + FixedRegexBasedInterpolator rbi = + FixedRegexBasedInterpolator.create( new ObjectBasedValueSource( this ) ); + InterpolationState is = new InterpolationState(); + + String result = rbi.withPrefix( "this" ).interpolate( "this is a ${this.var}", is ); + + assertEquals( "this is a testVar", result ); + } + + @Test + public void shouldResolveByContextValue() + { + Map context = new HashMap(); + context.put( "var", "testVar" ); + + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( + new org.codehaus.plexus.interpolation.fixed.MapBasedValueSource( context ) ); + InterpolationState is = new InterpolationState(); + + String result = rbi.withPrefix( "this" ).interpolate( "this is a ${this.var}", is ); + + assertEquals( "this is a testVar", result ); + } + + @Test + public void shouldResolveByEnvar() + throws IOException + { + InterpolationState is = new InterpolationState(); + FixedRegexBasedInterpolator rbi = + FixedRegexBasedInterpolator.create( new EnvarBasedValueSource() ); + + String result = rbi.withPrefix( "this" ).interpolate( "this is a ${env.HOME}", is ); + + assertFalse( "this is a ${HOME}".equals( result ) ); + assertFalse( "this is a ${env.HOME}".equals( result ) ); + } + + @Test + public void useAlternateRegex() + throws Exception + { + Map context = new HashMap(); + context.put( "var", "testVar" ); + + InterpolationState is = new InterpolationState(); + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( "\\@\\{(", ")?([^}]+)\\}@", + new org.codehaus.plexus.interpolation.fixed.MapBasedValueSource( + context ) ); + + String result = rbi.withPrefix( "this" ).interpolate( "this is a @{this.var}@", is ); + + assertEquals( "this is a testVar", result ); + } + + @Test + public void testNPEFree() + throws Exception + { + Map context = new HashMap(); + context.put( "var", "testVar" ); + + InterpolationState is = new InterpolationState(); + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( "\\@\\{(", ")?([^}]+)\\}@", + new org.codehaus.plexus.interpolation.fixed.MapBasedValueSource( + context ) ); + + String result = rbi.interpolate( null, is ); + + assertEquals( "", result ); + } + + @Test + public void testUsePostProcessor_DoesNotChangeValue() + { + Map context = new HashMap(); + context.put( "test.var", "testVar" ); + + InterpolationState is = new InterpolationState(); + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( + new org.codehaus.plexus.interpolation.fixed.MapBasedValueSource( context ) ).withPostProcessor( + new InterpolationPostProcessor() + { + public Object execute( String expression, Object value ) + { + return null; + } + } ); + + String result = rbi.interpolate( "this is a ${test.var}", is ); + + assertEquals( "this is a testVar", result ); + } + + @Test + public void testUsePostProcessor_ChangesValue() + { + int loopNumber = 200000; + + Map context = new HashMap(); + context.put( "test.var", "testVar" ); + + InterpolationState is = new InterpolationState(); + FixedRegexBasedInterpolator rbi = FixedRegexBasedInterpolator.create( + new org.codehaus.plexus.interpolation.fixed.MapBasedValueSource( context ) ).withPostProcessor( + new InterpolationPostProcessor() + { + public Object execute( String expression, Object value ) + { + return value + "2"; + } + } ); + + for ( int i = 0; i < loopNumber; i++ ) + { + String result = rbi.interpolate( "this is a ${test.var}", is ); + + assertEquals( "this is a testVar2", result ); + } + } + +} \ No newline at end of file From 191885995950646610443b70a87a61eeb27b1f5a Mon Sep 17 00:00:00 2001 From: Kristian Rosenvold Date: Mon, 2 Feb 2015 20:29:56 +0100 Subject: [PATCH 3/3] Added FixedSingleResponseValueSource --- .../fixed/FixedSingleResponseValueSource.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/java/org/codehaus/plexus/interpolation/fixed/FixedSingleResponseValueSource.java diff --git a/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedSingleResponseValueSource.java b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedSingleResponseValueSource.java new file mode 100644 index 0000000..fa1bb83 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/interpolation/fixed/FixedSingleResponseValueSource.java @@ -0,0 +1,52 @@ +package org.codehaus.plexus.interpolation.fixed; + +/* + * Copyright 2001-2009 Codehaus Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.codehaus.plexus.interpolation.ValueSource; + +import java.util.Collections; +import java.util.List; + +/** + * If the expression matches, simply return the response object. + * @since 1.12 + */ +public class FixedSingleResponseValueSource + implements FixedValueSource +{ + + private final String expression; + private final Object response; + + public FixedSingleResponseValueSource( String expression, Object response ) + { + this.expression = expression; + this.response = response; + } + + + public Object getValue( String expression, InterpolationState interpolationState ) + { + if ( this.expression.equals( expression ) ) + { + return response; + } + + return null; + } + +}