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(); + + } +}