diff --git a/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/pom.xml b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/pom.xml
index 46a61a4186..9f99766401 100644
--- a/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/pom.xml
+++ b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/pom.xml
@@ -38,16 +38,18 @@ under the License.
2008
-
- true
-
-
org.apache.maven
maven-plugin-api
2.0
+
+ junit
+ junit
+ 3.8.2
+ test
+
${project.groupId}.class-loader
diff --git a/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/ExpressionUtil.java b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/ExpressionUtil.java
new file mode 100644
index 0000000000..9f32f8b04e
--- /dev/null
+++ b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/ExpressionUtil.java
@@ -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 contexts
is used to specify which
+ * root objects are available. For instance, if contexts
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 null
.
+ * @param contexts The possible root objects for the expression evaluation, indexed by their identifying token, must
+ * not be null
.
+ * @return The value of the expression or null
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 null
.
+ * @param segments The expression segments to evaluate, must not be null
.
+ * @return The value of the evaluation or null
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 null
.
+ * @param property The name of the bean property, must not be null
.
+ * @return The value of the bean property or null
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;
+ }
+
+}
diff --git a/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/InstanceofMojo.java b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/InstanceofMojo.java
new file mode 100644
index 0000000000..7547680115
--- /dev/null
+++ b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/main/java/org/apache/maven/plugin/coreit/InstanceofMojo.java
@@ -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 );
+ }
+
+}
diff --git a/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/test/java/org/apache/maven/plugin/coreit/ExpressionUtilTest.java b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/test/java/org/apache/maven/plugin/coreit/ExpressionUtilTest.java
new file mode 100644
index 0000000000..fe797405c0
--- /dev/null
+++ b/its/core-it-support/core-it-plugins/maven-it-plugin-class-loader/maven-it-plugin-class-loader/src/test/java/org/apache/maven/plugin/coreit/ExpressionUtilTest.java
@@ -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();
+
+ }
+}