o Added new test mojo to verify type compatibility of objects transferred from the core to the mojo

git-svn-id: https://svn.apache.org/repos/asf/maven/core-integration-testing/trunk@705750 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Benjamin Bentmann 2008-10-17 21:07:54 +00:00
parent cd3b8315ed
commit 2581677b92
4 changed files with 505 additions and 4 deletions

View File

@ -38,16 +38,18 @@ under the License.
</description>
<inceptionYear>2008</inceptionYear>
<properties>
<maven.test.skip>true</maven.test.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
<scope>test</scope>
</dependency>
<!-- dedicated IT artifact that is surely not shadowed by the Maven core -->
<dependency>
<groupId>${project.groupId}.class-loader</groupId>

View File

@ -0,0 +1,227 @@
package org.apache.maven.plugin.coreit;
/*
* 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.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Assists in evaluating expressions.
*
* @author Benjamin Bentmann
* @version $Id$
*/
class ExpressionUtil
{
private static final Object[] NO_ARGS = {};
private static final Class[] NO_PARAMS = {};
private static final Class[] OBJECT_PARAM = { Object.class };
private static final Class[] STRING_PARAM = { String.class };
/**
* Evaluates the specified expression. Expressions are composed of segments which are separated by a forward slash
* ('/'). Each segment specifies a (public) bean property of the current object and drives the evaluation further
* down the object graph. For lists, arrays and maps segments can additionally specify the index/key of an element.
* The initial segment denotes the root object and the parameter <code>contexts</code> is used to specify which
* root objects are available. For instance, if <code>contexts</code> maps the token "project" to a Maven project
* instance, the expression "project/build/resources/0/directory" specifies the first resource directory of the
* project.
*
* @param expression The expression to evaluate, may be <code>null</code>.
* @param contexts The possible root objects for the expression evaluation, indexed by their identifying token, must
* not be <code>null</code>.
* @return The value of the expression or <code>null</code> if the expression could not be evaluated.
*/
public static Object evaluate( String expression, Map contexts )
{
Object value = null;
if ( expression != null && expression.length() > 0 )
{
List segments = Arrays.asList( expression.split( "/", 0 ) );
if ( !segments.isEmpty() )
{
Object context = contexts.get( segments.get( 0 ) );
if ( context != null )
{
value = evaluate( context, segments.subList( 1, segments.size() ) );
}
}
}
return value;
}
/**
* Evaluates the given expression segments against the specified object.
*
* @param context The object to evaluate the segments against, may be <code>null</code>.
* @param segments The expression segments to evaluate, must not be <code>null</code>.
* @return The value of the evaluation or <code>null</code> if the segments could not be evaluated.
*/
private static Object evaluate( Object context, List segments )
{
Object value = null;
if ( segments.isEmpty() )
{
value = context;
}
else if ( context != null )
{
Object target = null;
String segment = (String) segments.get( 0 );
if ( segment.length() <= 0 )
{
value = context;
}
else if ( context.getClass().isArray() && Character.isDigit( segment.charAt( 0 ) ) )
{
try
{
int index = Integer.parseInt( segment );
target = Array.get( context, index );
}
catch ( RuntimeException e )
{
// invalid index, just ignore
}
}
else if ( ( context instanceof List ) && Character.isDigit( segment.charAt( 0 ) ) )
{
try
{
int index = Integer.parseInt( segment );
target = ( (List) context ).get( index );
}
catch ( RuntimeException e )
{
// invalid index, just ignore
}
}
else
{
target = getProperty( context, segment );
}
value = evaluate( target, segments.subList( 1, segments.size() ) );
}
return value;
}
/**
* Gets the value of a (public) bean property from the specified object.
*
* @param context The object whose bean property should be retrieved, must not be <code>null</code>.
* @param property The name of the bean property, must not be <code>null</code>.
* @return The value of the bean property or <code>null</code> if the property does not exist.
*/
static Object getProperty( Object context, String property )
{
Object value;
Class type = context.getClass();
if ( context instanceof Collection )
{
type = Collection.class;
}
else if ( context instanceof Map )
{
type = Map.class;
}
try
{
try
{
Method method = type.getMethod( property, NO_PARAMS );
value = method.invoke( context, NO_ARGS );
}
catch ( NoSuchMethodException e )
{
try
{
String name = "get" + Character.toUpperCase( property.charAt( 0 ) ) + property.substring( 1 );
Method method = type.getMethod( name, NO_PARAMS );
value = method.invoke( context, NO_ARGS );
}
catch ( NoSuchMethodException e1 )
{
try
{
String name = "is" + Character.toUpperCase( property.charAt( 0 ) ) + property.substring( 1 );
Method method = type.getMethod( name, NO_PARAMS );
value = method.invoke( context, NO_ARGS );
}
catch ( NoSuchMethodException e2 )
{
try
{
Method method;
try
{
method = type.getMethod( "get", STRING_PARAM );
}
catch ( NoSuchMethodException e3 )
{
method = type.getMethod( "get", OBJECT_PARAM );
}
value = method.invoke( context, new Object[] { property } );
}
catch ( NoSuchMethodException e3 )
{
try
{
Field field = type.getField( property );
value = field.get( context );
}
catch ( NoSuchFieldException e4 )
{
if ( "length".equals( property ) && type.isArray() )
{
value = new Integer( Array.getLength( context ) );
}
else
{
throw e4;
}
}
}
}
}
}
}
catch ( Exception e )
{
value = null;
}
return value;
}
}

View File

@ -0,0 +1,150 @@
package org.apache.maven.plugin.coreit;
/*
* 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.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Checks whether objects obtained from the Maven core are assignment-compatible with types loaded from the plugin class
* loader. In other words, checks that types shared with the core realm are imported into the plugin realm.
*
* @goal instanceof
* @phase initialize
*
* @author Benjamin Bentmann
* @version $Id$
*/
public class InstanceofMojo
extends AbstractMojo
{
/**
* The path to the properties file used to track the results of the instanceof tests.
*
* @parameter expression="${clsldr.instanceofPropertiesFile}"
* default-value="${project.build.directory}/instanceof.properties"
*/
private File instanceofPropertiesFile;
/**
* The qualified name of the type to which the objects should be assignment-compatible. This type will be loaded
* from the plugin class loader, just like as if it was imported in the plugin source code.
*
* @parameter expression="${clsldr.className}"
*/
private String className;
/**
* A list of expressions that denote the object instances that should be type-checked.
*
* @parameter
*/
private String[] objectExpressions;
/**
* The current Maven project against which expressions are evaluated.
*
* @parameter default-value="${project}"
* @readonly
*/
private Object project;
/**
* Runs this mojo.
*
* @throws MojoExecutionException If the output file could not be created.
*/
public void execute()
throws MojoExecutionException, MojoFailureException
{
Class type;
try
{
getLog().info( "[MAVEN-CORE-IT-LOG] Loading class " + className );
type = getClass().getClassLoader().loadClass( className );
getLog().info( "[MAVEN-CORE-IT-LOG] Loaded class from " + type.getClassLoader() );
}
catch ( ClassNotFoundException e )
{
throw new MojoExecutionException( "Failed to load type " + className, e );
}
Properties instanceofProperties = new Properties();
Map contexts = new HashMap();
contexts.put( "project", project );
contexts.put( "pom", project );
for ( int i = 0; i < objectExpressions.length; i++ )
{
String expression = objectExpressions[i];
getLog().info( "[MAVEN-CORE-IT-LOG] Evaluating expression " + expression );
Object object = ExpressionUtil.evaluate( expression, contexts );
getLog().info( "[MAVEN-CORE-IT-LOG] Checking object " + object );
if ( object != null )
{
getLog().info( "[MAVEN-CORE-IT-LOG] Loaded object from " + object.getClass().getClassLoader() );
}
instanceofProperties.setProperty( expression.replace( '/', '.' ),
Boolean.toString( type.isInstance( object ) ) );
}
getLog().info( "[MAVEN-CORE-IT-LOG] Creating output file " + instanceofPropertiesFile );
OutputStream out = null;
try
{
instanceofPropertiesFile.getParentFile().mkdirs();
out = new FileOutputStream( instanceofPropertiesFile );
instanceofProperties.store( out, "MAVEN-CORE-IT-LOG" );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Output file could not be created: " + instanceofPropertiesFile, e );
}
finally
{
if ( out != null )
{
try
{
out.close();
}
catch ( IOException e )
{
// just ignore
}
}
}
getLog().info( "[MAVEN-CORE-IT-LOG] Created output file " + instanceofPropertiesFile );
}
}

View File

@ -0,0 +1,122 @@
package org.apache.maven.plugin.coreit;
/*
* 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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import junit.framework.TestCase;
/**
* @author Benjamin Bentmann
* @version $Id$
*/
public class ExpressionUtilTest
extends TestCase
{
public void testEvaluate()
{
Object array = new String[] { "one", "two", "three" };
Object list = Arrays.asList( new String[] { "0", "-1", "-2" } );
Object map = Collections.singletonMap( "some.key", "value" );
Object bean = new BeanTwo();
Map contexts = new HashMap();
contexts.put( "array", array );
contexts.put( "list", list );
contexts.put( "map", map );
contexts.put( "bean", bean );
assertSame( array, ExpressionUtil.evaluate( "array", contexts ) );
assertSame( array, ExpressionUtil.evaluate( "array/", contexts ) );
assertSame( list, ExpressionUtil.evaluate( "list", contexts ) );
assertSame( map, ExpressionUtil.evaluate( "map", contexts ) );
assertSame( bean, ExpressionUtil.evaluate( "bean", contexts ) );
assertNull( ExpressionUtil.evaluate( "no-root", contexts ) );
assertEquals( new Integer( 3 ), ExpressionUtil.evaluate( "array/length", contexts ) );
assertEquals( "three", ExpressionUtil.evaluate( "array/2", contexts ) );
assertEquals( new Integer( 5 ), ExpressionUtil.evaluate( "array/2/length", contexts ) );
assertNull( ExpressionUtil.evaluate( "array/invalid", contexts ) );
assertNull( ExpressionUtil.evaluate( "array/-1", contexts ) );
assertNull( ExpressionUtil.evaluate( "array/999", contexts ) );
assertEquals( new Integer( 3 ), ExpressionUtil.evaluate( "list/size", contexts ) );
assertEquals( "-2", ExpressionUtil.evaluate( "list/2", contexts ) );
assertNull( ExpressionUtil.evaluate( "list/invalid", contexts ) );
assertNull( ExpressionUtil.evaluate( "list/-1", contexts ) );
assertNull( ExpressionUtil.evaluate( "list/999", contexts ) );
assertEquals( new Integer( 1 ), ExpressionUtil.evaluate( "map/size", contexts ) );
assertEquals( "value", ExpressionUtil.evaluate( "map/some.key", contexts ) );
assertNull( ExpressionUtil.evaluate( "map/invalid", contexts ) );
assertEquals( "field", ExpressionUtil.evaluate( "bean/field", contexts ) );
assertNull( ExpressionUtil.evaluate( "bean/invalid", contexts ) );
assertEquals( "prop", ExpressionUtil.evaluate( "bean/bean/prop", contexts ) );
assertEquals( "flag", ExpressionUtil.evaluate( "bean/bean/flag", contexts ) );
assertEquals( "arg", ExpressionUtil.evaluate( "bean/bean/arg", contexts ) );
}
public void testGetProperty()
{
BeanOne bean1 = new BeanOne();
BeanTwo bean2 = new BeanTwo();
assertEquals( bean1.isFlag(), ExpressionUtil.getProperty( bean1, "flag" ) );
assertEquals( bean1.getProp(), ExpressionUtil.getProperty( bean1, "prop" ) );
assertEquals( bean1.get( "get" ), ExpressionUtil.getProperty( bean1, "get" ) );
assertNull( ExpressionUtil.getProperty( bean2, "invalid" ) );
assertEquals( bean2.field, ExpressionUtil.getProperty( bean2, "field" ) );
assertSame( bean2.bean, ExpressionUtil.getProperty( bean2, "bean" ) );
assertEquals( new Integer( 0 ), ExpressionUtil.getProperty( new String[0], "length" ) );
}
public static class BeanOne
{
public String isFlag()
{
return "flag";
}
public String getProp()
{
return "prop";
}
public String get( String arg )
{
return arg;
}
}
public static class BeanTwo
{
public String field = "field";
public BeanOne bean = new BeanOne();
}
}