mirror of https://github.com/apache/maven.git
[MNG-7820] Get rid of plexus-utils introspection classes (#1251)
This commit is contained in:
parent
e91cee9a2d
commit
fd4493580a
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.maven.plugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A cache of introspection information for a specific class instance.
|
||||
* Keys {@link Method} objects by a concatenation of the
|
||||
* method name and the names of classes that make up the parameters.
|
||||
*/
|
||||
class ClassMap {
|
||||
private static final class CacheMiss {}
|
||||
|
||||
private static final CacheMiss CACHE_MISS = new CacheMiss();
|
||||
|
||||
private static final Object OBJECT = new Object();
|
||||
|
||||
/**
|
||||
* Class passed into the constructor used to as
|
||||
* the basis for the Method map.
|
||||
*/
|
||||
private final Class<?> clazz;
|
||||
|
||||
/**
|
||||
* Cache of Methods, or CACHE_MISS, keyed by method
|
||||
* name and actual arguments used to find it.
|
||||
*/
|
||||
private final Map<String, Object> methodCache = new Hashtable<>();
|
||||
|
||||
private MethodMap methodMap = new MethodMap();
|
||||
|
||||
/**
|
||||
* Standard constructor
|
||||
* @param clazz The class.
|
||||
*/
|
||||
ClassMap(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
populateMethodCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the class object whose methods are cached by this map.
|
||||
*/
|
||||
Class<?> getCachedClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Find a Method using the methodKey provided.</p>
|
||||
* <p>Look in the methodMap for an entry. If found,
|
||||
* it'll either be a CACHE_MISS, in which case we
|
||||
* simply give up, or it'll be a Method, in which
|
||||
* case, we return it.</p>
|
||||
* <p>If nothing is found, then we must actually go
|
||||
* and introspect the method from the MethodMap.</p>
|
||||
* @param name Method name.
|
||||
* @param params Method parameters.
|
||||
* @return The found method.
|
||||
* @throws MethodMap.AmbiguousException in case of duplicate methods.
|
||||
*/
|
||||
public Method findMethod(String name, Object... params) throws MethodMap.AmbiguousException {
|
||||
String methodKey = makeMethodKey(name, params);
|
||||
Object cacheEntry = methodCache.get(methodKey);
|
||||
|
||||
if (cacheEntry == CACHE_MISS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
try {
|
||||
cacheEntry = methodMap.find(name, params);
|
||||
} catch (MethodMap.AmbiguousException ae) {
|
||||
// that's a miss :)
|
||||
methodCache.put(methodKey, CACHE_MISS);
|
||||
throw ae;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
methodCache.put(methodKey, CACHE_MISS);
|
||||
} else {
|
||||
methodCache.put(methodKey, cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, this might just be null.
|
||||
return (Method) cacheEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the Map of direct hits. These
|
||||
* are taken from all the public methods
|
||||
* that our class provides.
|
||||
*/
|
||||
private void populateMethodCache() {
|
||||
// get all publicly accessible methods
|
||||
Method[] methods = getAccessibleMethods(clazz);
|
||||
|
||||
// map and cache them
|
||||
for (Method method : methods) {
|
||||
// now get the 'public method', the method declared by a
|
||||
// public interface or class (because the actual implementing
|
||||
// class may be a facade...)
|
||||
|
||||
Method publicMethod = getPublicMethod(method);
|
||||
|
||||
// it is entirely possible that there is no public method for
|
||||
// the methods of this class (i.e. in the facade, a method
|
||||
// that isn't on any of the interfaces or superclass
|
||||
// in which case, ignore it. Otherwise, map and cache
|
||||
if (publicMethod != null) {
|
||||
methodMap.add(publicMethod);
|
||||
methodCache.put(makeMethodKey(publicMethod), publicMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a methodKey for the given method using
|
||||
* the concatenation of the name and the
|
||||
* types of the method parameters.
|
||||
*/
|
||||
private String makeMethodKey(Method method) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
StringBuilder methodKey = new StringBuilder(method.getName());
|
||||
|
||||
for (Class<?> parameterType : parameterTypes) {
|
||||
// If the argument type is primitive then we want
|
||||
// to convert our primitive type signature to the
|
||||
// corresponding Object type so introspection for
|
||||
// methods with primitive types will work correctly.
|
||||
if (parameterType.isPrimitive()) {
|
||||
if (parameterType.equals(Boolean.TYPE)) {
|
||||
methodKey.append("java.lang.Boolean");
|
||||
} else if (parameterType.equals(Byte.TYPE)) {
|
||||
methodKey.append("java.lang.Byte");
|
||||
} else if (parameterType.equals(Character.TYPE)) {
|
||||
methodKey.append("java.lang.Character");
|
||||
} else if (parameterType.equals(Double.TYPE)) {
|
||||
methodKey.append("java.lang.Double");
|
||||
} else if (parameterType.equals(Float.TYPE)) {
|
||||
methodKey.append("java.lang.Float");
|
||||
} else if (parameterType.equals(Integer.TYPE)) {
|
||||
methodKey.append("java.lang.Integer");
|
||||
} else if (parameterType.equals(Long.TYPE)) {
|
||||
methodKey.append("java.lang.Long");
|
||||
} else if (parameterType.equals(Short.TYPE)) {
|
||||
methodKey.append("java.lang.Short");
|
||||
}
|
||||
} else {
|
||||
methodKey.append(parameterType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return methodKey.toString();
|
||||
}
|
||||
|
||||
private static String makeMethodKey(String method, Object... params) {
|
||||
StringBuilder methodKey = new StringBuilder().append(method);
|
||||
|
||||
for (Object param : params) {
|
||||
Object arg = param;
|
||||
|
||||
if (arg == null) {
|
||||
arg = OBJECT;
|
||||
}
|
||||
|
||||
methodKey.append(arg.getClass().getName());
|
||||
}
|
||||
|
||||
return methodKey.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves public methods for a class. In case the class is not
|
||||
* public, retrieves methods with same signature as its public methods
|
||||
* from public superclasses and interfaces (if they exist). Basically
|
||||
* upcasts every method to the nearest acccessible method.
|
||||
*/
|
||||
private static Method[] getAccessibleMethods(Class<?> clazz) {
|
||||
Method[] methods = clazz.getMethods();
|
||||
|
||||
// Short circuit for the (hopefully) majority of cases where the
|
||||
// clazz is public
|
||||
if (Modifier.isPublic(clazz.getModifiers())) {
|
||||
return methods;
|
||||
}
|
||||
|
||||
// No luck - the class is not public, so we're going the longer way.
|
||||
MethodInfo[] methodInfos = new MethodInfo[methods.length];
|
||||
for (int i = methods.length; i-- > 0; ) {
|
||||
methodInfos[i] = new MethodInfo(methods[i]);
|
||||
}
|
||||
|
||||
int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
|
||||
|
||||
// Reallocate array in case some method had no accessible counterpart.
|
||||
if (upcastCount < methods.length) {
|
||||
methods = new Method[upcastCount];
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
for (MethodInfo methodInfo : methodInfos) {
|
||||
if (methodInfo.upcast) {
|
||||
methods[j++] = methodInfo.method;
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively finds a match for each method, starting with the class, and then
|
||||
* searching the superclass and interfaces.
|
||||
*
|
||||
* @param clazz Class to check
|
||||
* @param methodInfos array of methods we are searching to match
|
||||
* @param upcastCount current number of methods we have matched
|
||||
* @return count of matched methods
|
||||
*/
|
||||
private static int getAccessibleMethods(Class<?> clazz, MethodInfo[] methodInfos, int upcastCount) {
|
||||
int l = methodInfos.length;
|
||||
|
||||
// if this class is public, then check each of the currently
|
||||
// 'non-upcasted' methods to see if we have a match
|
||||
if (Modifier.isPublic(clazz.getModifiers())) {
|
||||
for (int i = 0; i < l && upcastCount < l; ++i) {
|
||||
try {
|
||||
MethodInfo methodInfo = methodInfos[i];
|
||||
if (!methodInfo.upcast) {
|
||||
methodInfo.tryUpcasting(clazz);
|
||||
upcastCount++;
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Intentionally ignored - it means it wasn't found in the current class
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Short circuit if all methods were upcast
|
||||
*/
|
||||
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Examine superclass
|
||||
Class<?> superclazz = clazz.getSuperclass();
|
||||
if (superclazz != null) {
|
||||
upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
|
||||
|
||||
// Short circuit if all methods were upcast
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Examine interfaces. Note we do it even if superclazz == null.
|
||||
// This is redundant as currently java.lang.Object does not implement
|
||||
// any interfaces, however nothing guarantees it will not in the future.
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
for (int i = interfaces.length; i-- > 0; ) {
|
||||
upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
|
||||
|
||||
// Short circuit if all methods were upcast
|
||||
if (upcastCount == l) {
|
||||
return upcastCount;
|
||||
}
|
||||
}
|
||||
|
||||
return upcastCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given method, retrieves its publicly accessible counterpart.
|
||||
* This method will look for a method with same name
|
||||
* and signature declared in a public superclass or implemented interface of this
|
||||
* method's declaring class. This counterpart method is publicly callable.
|
||||
*
|
||||
* @param method a method whose publicly callable counterpart is requested.
|
||||
* @return the publicly callable counterpart method. Note that if the parameter
|
||||
* method is itself declared by a public class, this method is an identity
|
||||
* function.
|
||||
*/
|
||||
private static Method getPublicMethod(Method method) {
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
|
||||
// Short circuit for (hopefully the majority of) cases where the declaring
|
||||
// class is public.
|
||||
if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
|
||||
return method;
|
||||
}
|
||||
|
||||
return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the method with specified name and signature in the first public
|
||||
* superclass or implemented interface of the class.
|
||||
*
|
||||
* @param clazz the class whose method is sought
|
||||
* @param name the name of the method
|
||||
* @param paramTypes the classes of method parameters
|
||||
*/
|
||||
private static Method getPublicMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
|
||||
// if this class is public, then try to get it
|
||||
if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
|
||||
try {
|
||||
return clazz.getMethod(name, paramTypes);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// If the class does not have the method, then neither its superclass
|
||||
// nor any of its interfaces has it so quickly return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// try the superclass
|
||||
Class<?> superclazz = clazz.getSuperclass();
|
||||
|
||||
if (superclazz != null) {
|
||||
Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
|
||||
|
||||
if (superclazzMethod != null) {
|
||||
return superclazzMethod;
|
||||
}
|
||||
}
|
||||
|
||||
// and interfaces
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
|
||||
for (Class<?> anInterface : interfaces) {
|
||||
Method interfaceMethod = getPublicMethod(anInterface, name, paramTypes);
|
||||
|
||||
if (interfaceMethod != null) {
|
||||
return interfaceMethod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the iterative discovery process for public methods.
|
||||
*/
|
||||
private static final class MethodInfo {
|
||||
Method method;
|
||||
|
||||
String name;
|
||||
|
||||
Class<?>[] parameterTypes;
|
||||
|
||||
boolean upcast;
|
||||
|
||||
MethodInfo(Method method) {
|
||||
this.method = null;
|
||||
name = method.getName();
|
||||
parameterTypes = method.getParameterTypes();
|
||||
upcast = false;
|
||||
}
|
||||
|
||||
void tryUpcasting(Class<?> clazz) throws NoSuchMethodException {
|
||||
method = clazz.getMethod(name, parameterTypes);
|
||||
name = null;
|
||||
parameterTypes = null;
|
||||
upcast = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.maven.plugin;
|
||||
|
||||
class IntrospectionException extends Exception {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -6090771282553728784L;
|
||||
|
||||
IntrospectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
IntrospectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.maven.plugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class MethodMap {
|
||||
private static final int MORE_SPECIFIC = 0;
|
||||
|
||||
private static final int LESS_SPECIFIC = 1;
|
||||
|
||||
private static final int INCOMPARABLE = 2;
|
||||
|
||||
/**
|
||||
* Keep track of all methods with the same name.
|
||||
*/
|
||||
private final Map<String, List<Method>> methodByNameMap = new Hashtable<>();
|
||||
|
||||
/**
|
||||
* Add a method to a list of methods by name.
|
||||
* For a particular class we are keeping track
|
||||
* of all the methods with the same name.
|
||||
*
|
||||
* @param method The method
|
||||
*/
|
||||
void add(Method method) {
|
||||
String methodName = method.getName();
|
||||
|
||||
List<Method> l = get(methodName);
|
||||
|
||||
if (l == null) {
|
||||
l = new ArrayList<>();
|
||||
methodByNameMap.put(methodName, l);
|
||||
}
|
||||
|
||||
l.add(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of methods with the same name.
|
||||
*
|
||||
* @param key The name of the method.
|
||||
* @return List list of methods
|
||||
*/
|
||||
List<Method> get(String key) {
|
||||
return methodByNameMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method. Attempts to find the
|
||||
* most specific applicable method using the
|
||||
* algorithm described in the JLS section
|
||||
* 15.12.2 (with the exception that it can't
|
||||
* distinguish a primitive type argument from
|
||||
* an object type argument, since in reflection
|
||||
* primitive type arguments are represented by
|
||||
* their object counterparts, so for an argument of
|
||||
* type (say) java.lang.Integer, it will not be able
|
||||
* to decide between a method that takes int and a
|
||||
* method that takes java.lang.Integer as a parameter.
|
||||
* <p>
|
||||
* This turns out to be a relatively rare case
|
||||
* where this is needed - however, functionality
|
||||
* like this is needed.
|
||||
*
|
||||
* @param methodName name of method
|
||||
* @param args the actual arguments with which the method is called
|
||||
* @return the most specific applicable method, or null if no
|
||||
* method is applicable.
|
||||
* @throws AmbiguousException if there is more than one maximally
|
||||
* specific applicable method
|
||||
*/
|
||||
Method find(String methodName, Object... args) throws AmbiguousException {
|
||||
List<Method> methodList = get(methodName);
|
||||
|
||||
if (methodList == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int l = args.length;
|
||||
Class<?>[] classes = new Class[l];
|
||||
|
||||
for (int i = 0; i < l; ++i) {
|
||||
Object arg = args[i];
|
||||
// if we are careful down below, a null argument goes in there
|
||||
// so we can know that the null was passed to the method
|
||||
classes[i] = arg == null ? null : arg.getClass();
|
||||
}
|
||||
|
||||
return getMostSpecific(methodList, classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* simple distinguishable exception, used when
|
||||
* we run across ambiguous overloading
|
||||
*/
|
||||
static class AmbiguousException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 751688436639650618L;
|
||||
}
|
||||
|
||||
private static Method getMostSpecific(List<Method> methods, Class<?>... classes) throws AmbiguousException {
|
||||
LinkedList<Method> applicables = getApplicables(methods, classes);
|
||||
|
||||
if (applicables.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (applicables.size() == 1) {
|
||||
return applicables.getFirst();
|
||||
}
|
||||
|
||||
// This list will contain the maximally specific methods. Hopefully at
|
||||
// the end of the below loop, the list will contain exactly one method,
|
||||
// (the most specific method) otherwise we have ambiguity.
|
||||
LinkedList<Method> maximals = new LinkedList<>();
|
||||
|
||||
for (Method app : applicables) {
|
||||
Class<?>[] appArgs = app.getParameterTypes();
|
||||
boolean lessSpecific = false;
|
||||
|
||||
for (Iterator<Method> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); ) {
|
||||
Method max = maximal.next();
|
||||
|
||||
switch (moreSpecific(appArgs, max.getParameterTypes())) {
|
||||
case MORE_SPECIFIC:
|
||||
// This method is more specific than the previously
|
||||
// known maximally specific, so remove the old maximum.
|
||||
maximal.remove();
|
||||
break;
|
||||
|
||||
case LESS_SPECIFIC:
|
||||
// This method is less specific than some of the
|
||||
// currently known maximally specific methods, so we
|
||||
// won't add it into the set of maximally specific
|
||||
// methods
|
||||
lessSpecific = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (!lessSpecific) {
|
||||
maximals.addLast(app);
|
||||
}
|
||||
}
|
||||
|
||||
if (maximals.size() > 1) {
|
||||
// We have more than one maximally specific method
|
||||
throw new AmbiguousException();
|
||||
}
|
||||
|
||||
return maximals.getFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which method signature (represented by a class array) is more
|
||||
* specific. This defines a partial ordering on the method signatures.
|
||||
*
|
||||
* @param c1 first signature to compare
|
||||
* @param c2 second signature to compare
|
||||
* @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
|
||||
* c1 is less specific than c2, INCOMPARABLE if they are incomparable.
|
||||
*/
|
||||
private static int moreSpecific(Class<?>[] c1, Class<?>[] c2) {
|
||||
boolean c1MoreSpecific = false;
|
||||
boolean c2MoreSpecific = false;
|
||||
|
||||
for (int i = 0; i < c1.length; ++i) {
|
||||
if (c1[i] != c2[i]) {
|
||||
c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible(c2[i], c1[i]);
|
||||
c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible(c1[i], c2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (c1MoreSpecific) {
|
||||
if (c2MoreSpecific) {
|
||||
// Incomparable due to cross-assignable arguments (i.e.
|
||||
// foo(String, Object) vs. foo(Object, String))
|
||||
return INCOMPARABLE;
|
||||
}
|
||||
|
||||
return MORE_SPECIFIC;
|
||||
}
|
||||
|
||||
if (c2MoreSpecific) {
|
||||
return LESS_SPECIFIC;
|
||||
}
|
||||
|
||||
// Incomparable due to non-related arguments (i.e.
|
||||
// foo(Runnable) vs. foo(Serializable))
|
||||
return INCOMPARABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all methods that are applicable to actual argument types.
|
||||
*
|
||||
* @param methods list of all candidate methods
|
||||
* @param classes the actual types of the arguments
|
||||
* @return a list that contains only applicable methods (number of
|
||||
* formal and actual arguments matches, and argument types are assignable
|
||||
* to formal types through a method invocation conversion).
|
||||
*/
|
||||
private static LinkedList<Method> getApplicables(List<Method> methods, Class<?>... classes) {
|
||||
LinkedList<Method> list = new LinkedList<>();
|
||||
|
||||
for (Method method : methods) {
|
||||
if (isApplicable(method, classes)) {
|
||||
list.add(method);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the supplied method is applicable to actual
|
||||
* argument types.
|
||||
*
|
||||
* @param method The method to check for applicability
|
||||
* @param classes The arguments
|
||||
* @return true if the method applies to the parameter types
|
||||
*/
|
||||
private static boolean isApplicable(Method method, Class<?>... classes) {
|
||||
Class<?>[] methodArgs = method.getParameterTypes();
|
||||
|
||||
if (methodArgs.length != classes.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < classes.length; ++i) {
|
||||
if (!isMethodInvocationConvertible(methodArgs[i], classes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a type represented by a class object is
|
||||
* convertible to another type represented by a class object using a
|
||||
* method invocation conversion, treating object types of primitive
|
||||
* types as if they were primitive types (that is, a Boolean actual
|
||||
* parameter type matches boolean primitive formal type). This behavior
|
||||
* is because this method is used to determine applicable methods for
|
||||
* an actual parameter list, and primitive types are represented by
|
||||
* their object duals in reflective method calls.
|
||||
*
|
||||
* @param formal the formal parameter type to which the actual
|
||||
* parameter type should be convertible
|
||||
* @param actual the actual parameter type.
|
||||
* @return true if either formal type is assignable from actual type,
|
||||
* or formal is a primitive type and actual is its corresponding object
|
||||
* type or an object type of a primitive type that can be converted to
|
||||
* the formal type.
|
||||
*/
|
||||
private static boolean isMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
|
||||
// if it's a null, it means the arg was null
|
||||
if (actual == null && !formal.isPrimitive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for identity or widening reference conversion
|
||||
if (actual != null && formal.isAssignableFrom(actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for boxing with widening primitive conversion. Note that
|
||||
// actual parameters are never primitives.
|
||||
if (formal.isPrimitive()) {
|
||||
if (formal == Boolean.TYPE && actual == Boolean.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Character.TYPE && actual == Character.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Byte.TYPE && actual == Byte.class) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class || actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Long.TYPE
|
||||
&& (actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Float.TYPE
|
||||
&& (actual == Float.class
|
||||
|| actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Double.TYPE
|
||||
&& (actual == Double.class
|
||||
|| actual == Float.class
|
||||
|| actual == Long.class
|
||||
|| actual == Integer.class
|
||||
|| actual == Short.class
|
||||
|| actual == Byte.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a type represented by a class object is
|
||||
* convertible to another type represented by a class object using a
|
||||
* method invocation conversion, without matching object and primitive
|
||||
* types. This method is used to determine the more specific type when
|
||||
* comparing signatures of methods.
|
||||
*
|
||||
* @param formal the formal parameter type to which the actual
|
||||
* parameter type should be convertible
|
||||
* @param actual the actual parameter type.
|
||||
* @return true if either formal type is assignable from actual type,
|
||||
* or formal and actual are both primitive types and actual can be
|
||||
* subject to widening conversion to formal.
|
||||
*/
|
||||
private static boolean isStrictMethodInvocationConvertible(Class<?> formal, Class<?> actual) {
|
||||
// we shouldn't get a null into, but if so
|
||||
if (actual == null && !formal.isPrimitive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for identity or widening reference conversion
|
||||
if (formal.isAssignableFrom(actual)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for widening primitive conversion.
|
||||
if (formal.isPrimitive()) {
|
||||
if (formal == Short.TYPE && (actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Integer.TYPE && (actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Long.TYPE && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Float.TYPE
|
||||
&& (actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
if (formal == Double.TYPE
|
||||
&& (actual == Float.TYPE
|
||||
|| actual == Long.TYPE
|
||||
|| actual == Integer.TYPE
|
||||
|| actual == Short.TYPE
|
||||
|| actual == Byte.TYPE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,6 @@ import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
|||
import org.apache.maven.project.MavenProject;
|
||||
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
|
||||
import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
|
||||
import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
|
||||
|
||||
/**
|
||||
* Evaluator for plugin parameters expressions. Content surrounded by <code>${</code> and <code>}</code> is evaluated.
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.apache.maven.plugin.descriptor.MojoDescriptor;
|
|||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
|
||||
import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
|
||||
import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
|
||||
|
||||
/**
|
||||
* Evaluator for plugin parameters expressions. Content surrounded by <code>${</code> and <code>}</code> is evaluated.
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.maven.plugin;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.maven.api.annotations.Nonnull;
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
import org.apache.maven.plugin.MethodMap.AmbiguousException;
|
||||
|
||||
/**
|
||||
* Using simple dotted expressions to extract the values from an Object instance using JSP-like expressions
|
||||
* such as {@code project.build.sourceDirectory}.
|
||||
*/
|
||||
class ReflectionValueExtractor {
|
||||
private static final Object[] OBJECT_ARGS = new Object[0];
|
||||
|
||||
/**
|
||||
* Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
|
||||
* This approach prevents permgen space overflows due to retention of discarded
|
||||
* classloaders.
|
||||
*/
|
||||
private static final Map<Class<?>, ClassMap> CLASS_MAPS = new WeakHashMap<>();
|
||||
|
||||
static final int EOF = -1;
|
||||
|
||||
static final char PROPERTY_START = '.';
|
||||
|
||||
static final char INDEXED_START = '[';
|
||||
|
||||
static final char INDEXED_END = ']';
|
||||
|
||||
static final char MAPPED_START = '(';
|
||||
|
||||
static final char MAPPED_END = ')';
|
||||
|
||||
static class Tokenizer {
|
||||
final String expression;
|
||||
|
||||
int idx;
|
||||
|
||||
Tokenizer(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public int peekChar() {
|
||||
return idx < expression.length() ? expression.charAt(idx) : EOF;
|
||||
}
|
||||
|
||||
public int skipChar() {
|
||||
return idx < expression.length() ? expression.charAt(idx++) : EOF;
|
||||
}
|
||||
|
||||
public String nextToken(char delimiter) {
|
||||
int start = idx;
|
||||
|
||||
while (idx < expression.length() && delimiter != expression.charAt(idx)) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// delimiter MUST be present
|
||||
if (idx <= start || idx >= expression.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expression.substring(start, idx++);
|
||||
}
|
||||
|
||||
public String nextPropertyName() {
|
||||
final int start = idx;
|
||||
|
||||
while (idx < expression.length() && Character.isJavaIdentifierPart(expression.charAt(idx))) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
// property name does not require delimiter
|
||||
if (idx <= start || idx > expression.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expression.substring(start, idx);
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return idx < expression.length() ? idx : EOF;
|
||||
}
|
||||
|
||||
// to make tokenizer look pretty in debugger
|
||||
@Override
|
||||
public String toString() {
|
||||
return idx < expression.length() ? expression.substring(idx) : "<EOF>";
|
||||
}
|
||||
}
|
||||
|
||||
private ReflectionValueExtractor() {}
|
||||
|
||||
/**
|
||||
* <p>The implementation supports indexed, nested and mapped properties.</p>
|
||||
* <ul>
|
||||
* <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
|
||||
* <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
|
||||
* pattern, i.e. "user.addresses[1].street"</li>
|
||||
* <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern,
|
||||
* i.e. "user.addresses(myAddress).street"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param expression not null expression
|
||||
* @param root not null object
|
||||
* @return the object defined by the expression
|
||||
* @throws IntrospectionException if any
|
||||
*/
|
||||
public static Object evaluate(@Nonnull String expression, @Nullable Object root) throws IntrospectionException {
|
||||
return evaluate(expression, root, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The implementation supports indexed, nested and mapped properties.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
|
||||
* <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
|
||||
* pattern, i.e. "user.addresses[1].street"</li>
|
||||
* <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
|
||||
* "user.addresses(myAddress).street"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param expression not null expression
|
||||
* @param root not null object
|
||||
* @param trimRootToken trim root token yes/no.
|
||||
* @return the object defined by the expression
|
||||
* @throws IntrospectionException if any
|
||||
*/
|
||||
public static Object evaluate(@Nonnull String expression, @Nullable Object root, boolean trimRootToken)
|
||||
throws IntrospectionException {
|
||||
Object value = root;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Walk the dots and retrieve the ultimate value desired from the
|
||||
// MavenProject instance.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
if (expression == null || expression.isEmpty() || !Character.isJavaIdentifierStart(expression.charAt(0))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean hasDots = expression.indexOf(PROPERTY_START) >= 0;
|
||||
|
||||
final Tokenizer tokenizer;
|
||||
if (trimRootToken && hasDots) {
|
||||
tokenizer = new Tokenizer(expression);
|
||||
tokenizer.nextPropertyName();
|
||||
if (tokenizer.getPosition() == EOF) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
tokenizer = new Tokenizer("." + expression);
|
||||
}
|
||||
|
||||
int propertyPosition = tokenizer.getPosition();
|
||||
while (value != null && tokenizer.peekChar() != EOF) {
|
||||
switch (tokenizer.skipChar()) {
|
||||
case INDEXED_START:
|
||||
value = getIndexedValue(
|
||||
expression,
|
||||
propertyPosition,
|
||||
tokenizer.getPosition(),
|
||||
value,
|
||||
tokenizer.nextToken(INDEXED_END));
|
||||
break;
|
||||
case MAPPED_START:
|
||||
value = getMappedValue(
|
||||
expression,
|
||||
propertyPosition,
|
||||
tokenizer.getPosition(),
|
||||
value,
|
||||
tokenizer.nextToken(MAPPED_END));
|
||||
break;
|
||||
case PROPERTY_START:
|
||||
propertyPosition = tokenizer.getPosition();
|
||||
value = getPropertyValue(value, tokenizer.nextPropertyName());
|
||||
break;
|
||||
default:
|
||||
// could not parse expression
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Object getMappedValue(
|
||||
final String expression, final int from, final int to, final Object value, final String key)
|
||||
throws IntrospectionException {
|
||||
if (value == null || key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
return ((Map) value).get(key);
|
||||
}
|
||||
|
||||
final String message = String.format(
|
||||
"The token '%s' at position '%d' refers to a java.util.Map, but the value "
|
||||
+ "seems is an instance of '%s'",
|
||||
expression.subSequence(from, to), from, value.getClass());
|
||||
|
||||
throw new IntrospectionException(message);
|
||||
}
|
||||
|
||||
private static Object getIndexedValue(
|
||||
final String expression, final int from, final int to, final Object value, final String indexStr)
|
||||
throws IntrospectionException {
|
||||
try {
|
||||
int index = Integer.parseInt(indexStr);
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
return Array.get(value, index);
|
||||
}
|
||||
|
||||
if (value instanceof List) {
|
||||
return ((List) value).get(index);
|
||||
}
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String message = String.format(
|
||||
"The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
|
||||
+ "seems is an instance of '%s'",
|
||||
expression.subSequence(from, to), from, value.getClass());
|
||||
|
||||
throw new IntrospectionException(message);
|
||||
}
|
||||
|
||||
private static Object getPropertyValue(Object value, String property) throws IntrospectionException {
|
||||
if (value == null || property == null || property.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassMap classMap = getClassMap(value.getClass());
|
||||
String methodBase = Character.toTitleCase(property.charAt(0)) + property.substring(1);
|
||||
String methodName = "get" + methodBase;
|
||||
try {
|
||||
Method method = classMap.findMethod(methodName);
|
||||
|
||||
if (method == null) {
|
||||
// perhaps this is a boolean property??
|
||||
methodName = "is" + methodBase;
|
||||
|
||||
method = classMap.findMethod(methodName);
|
||||
}
|
||||
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return method.invoke(value, OBJECT_ARGS);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IntrospectionException(e.getTargetException());
|
||||
} catch (AmbiguousException | IllegalAccessException e) {
|
||||
throw new IntrospectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassMap getClassMap(Class<?> clazz) {
|
||||
ClassMap classMap = CLASS_MAPS.get(clazz);
|
||||
|
||||
if (classMap == null) {
|
||||
classMap = new ClassMap(clazz);
|
||||
|
||||
CLASS_MAPS.put(clazz, classMap);
|
||||
}
|
||||
|
||||
return classMap;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ package org.apache.maven.plugin;
|
|||
import javax.inject.Inject;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
@ -375,7 +374,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
|
|||
void testRootDirectoryWithNull() throws Exception {
|
||||
ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
|
||||
Exception e = assertThrows(Exception.class, () -> ee.evaluate("${session.rootDirectory}"));
|
||||
e = assertInstanceOf(InvocationTargetException.class, e.getCause());
|
||||
e = assertInstanceOf(IntrospectionException.class, e.getCause());
|
||||
e = assertInstanceOf(IllegalStateException.class, e.getCause());
|
||||
assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.apache.maven.plugin;
|
|||
import javax.inject.Inject;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
@ -388,7 +387,7 @@ public class PluginParameterExpressionEvaluatorV4Test extends AbstractCoreMavenC
|
|||
void testRootDirectoryWithNull() throws Exception {
|
||||
ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
|
||||
Exception e = assertThrows(Exception.class, () -> ee.evaluate("${session.rootDirectory}"));
|
||||
e = assertInstanceOf(InvocationTargetException.class, e.getCause());
|
||||
e = assertInstanceOf(IntrospectionException.class, e.getCause());
|
||||
e = assertInstanceOf(IllegalStateException.class, e.getCause());
|
||||
assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.apache.maven.plugin;
|
||||
|
||||
/*
|
||||
* Copyright The 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* ReflectionValueExtractorTest class.
|
||||
*/
|
||||
public class ReflectionValueExtractorTest {
|
||||
private Project project;
|
||||
|
||||
/**
|
||||
* <p>setUp.</p>
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Dependency dependency1 = new Dependency();
|
||||
dependency1.setArtifactId("dep1");
|
||||
Dependency dependency2 = new Dependency();
|
||||
dependency2.setArtifactId("dep2");
|
||||
|
||||
project = new Project();
|
||||
project.setModelVersion("4.0.0");
|
||||
project.setGroupId("org.apache.maven");
|
||||
project.setArtifactId("maven-core");
|
||||
project.setName("Maven");
|
||||
project.setVersion("2.0-SNAPSHOT");
|
||||
project.setScm(new Scm());
|
||||
project.getScm().setConnection("scm-connection");
|
||||
project.addDependency(dependency1);
|
||||
project.addDependency(dependency2);
|
||||
project.setBuild(new Build());
|
||||
|
||||
// Build up an artifactMap
|
||||
project.addArtifact(new Artifact("g0", "a0", "v0", "e0", "c0"));
|
||||
project.addArtifact(new Artifact("g1", "a1", "v1", "e1", "c1"));
|
||||
project.addArtifact(new Artifact("g2", "a2", "v2", "e2", "c2"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testValueExtraction.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
void testValueExtraction() throws Exception {
|
||||
// ----------------------------------------------------------------------
|
||||
// Top level values
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
assertEquals("4.0.0", ReflectionValueExtractor.evaluate("project.modelVersion", project));
|
||||
|
||||
assertEquals("org.apache.maven", ReflectionValueExtractor.evaluate("project.groupId", project));
|
||||
|
||||
assertEquals("maven-core", ReflectionValueExtractor.evaluate("project.artifactId", project));
|
||||
|
||||
assertEquals("Maven", ReflectionValueExtractor.evaluate("project.name", project));
|
||||
|
||||
assertEquals("2.0-SNAPSHOT", ReflectionValueExtractor.evaluate("project.version", project));
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SCM
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
assertEquals("scm-connection", ReflectionValueExtractor.evaluate("project.scm.connection", project));
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Dependencies
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
List<?> dependencies = (List) ReflectionValueExtractor.evaluate("project.dependencies", project);
|
||||
|
||||
assertNotNull(dependencies);
|
||||
|
||||
assertEquals(2, dependencies.size());
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Dependencies - using index notation
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// List
|
||||
Dependency dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependencies[0]", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
String artifactId = (String) ReflectionValueExtractor.evaluate("project.dependencies[1].artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// Array
|
||||
|
||||
dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependenciesAsArray[0]", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
artifactId = (String) ReflectionValueExtractor.evaluate("project.dependenciesAsArray[1].artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// Map
|
||||
|
||||
dependency = (Dependency) ReflectionValueExtractor.evaluate("project.dependenciesAsMap(dep1)", project);
|
||||
|
||||
assertNotNull(dependency);
|
||||
|
||||
assertEquals("dep1", dependency.getArtifactId());
|
||||
|
||||
artifactId = (String) ReflectionValueExtractor.evaluate("project.dependenciesAsMap(dep2).artifactId", project);
|
||||
|
||||
assertEquals("dep2", artifactId);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Build
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Build build = (Build) ReflectionValueExtractor.evaluate("project.build", project);
|
||||
|
||||
assertNotNull(build);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testValueExtractorWithAInvalidExpression.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testValueExtractorWithAInvalidExpression() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.foo", project));
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.dependencies[10]", project));
|
||||
assertNull(ReflectionValueExtractor.evaluate("project.dependencies[0].foo", project));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedDottedKey.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedDottedKey() throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("a.b", "a.b-value");
|
||||
|
||||
assertEquals("a.b-value", ReflectionValueExtractor.evaluate("h.value(a.b)", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIndexedMapped.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIndexedMapped() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", "a-value");
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add(map);
|
||||
|
||||
assertEquals("a-value", ReflectionValueExtractor.evaluate("h.value[0](a)", new ValueHolder(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedIndexed.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedIndexed() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add("a-value");
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", list);
|
||||
assertEquals("a-value", ReflectionValueExtractor.evaluate("h.value(a)[0]", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testMappedMissingDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testMappedMissingDot() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", new ValueHolder("a-value"));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a)value", new ValueHolder(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIndexedMissingDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIndexedMissingDot() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add(new ValueHolder("a-value"));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0]value", new ValueHolder(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testDotDot.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testDotDot() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("h..value", new ValueHolder("value")));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testBadIndexedSyntax.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testBadIndexedSyntax() throws Exception {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add("a-value");
|
||||
Object value = new ValueHolder(list);
|
||||
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[]", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[a]", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[0)", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value[-1]", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testBadMappedSyntax.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testBadMappedSyntax() throws Exception {
|
||||
Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
map.put("a", "a-value");
|
||||
Object value = new ValueHolder(map);
|
||||
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value()", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a", value));
|
||||
assertNull(ReflectionValueExtractor.evaluate("h.value(a]", value));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIllegalIndexedType.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIllegalIndexedType() throws Exception {
|
||||
try {
|
||||
ReflectionValueExtractor.evaluate("h.value[1]", new ValueHolder("string"));
|
||||
} catch (Exception e) {
|
||||
// TODO assert exception message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testIllegalMappedType.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testIllegalMappedType() throws Exception {
|
||||
try {
|
||||
ReflectionValueExtractor.evaluate("h.value(key)", new ValueHolder("string"));
|
||||
} catch (Exception e) {
|
||||
// TODO assert exception message
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testTrimRootToken.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testTrimRootToken() throws Exception {
|
||||
assertNull(ReflectionValueExtractor.evaluate("project", project, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testArtifactMap.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testArtifactMap() throws Exception {
|
||||
assertEquals(
|
||||
"g0",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g0:a0:c0)", project)).getGroupId());
|
||||
assertEquals(
|
||||
"a1",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g1:a1:c1)", project))
|
||||
.getArtifactId());
|
||||
assertEquals(
|
||||
"c2",
|
||||
((Artifact) ReflectionValueExtractor.evaluate("project.artifactMap(g2:a2:c2)", project))
|
||||
.getClassifier());
|
||||
}
|
||||
|
||||
public static class Artifact {
|
||||
private String groupId;
|
||||
|
||||
private String artifactId;
|
||||
|
||||
private String version;
|
||||
|
||||
private String extension;
|
||||
|
||||
private String classifier;
|
||||
|
||||
public Artifact(String groupId, String artifactId, String version, String extension, String classifier) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
this.extension = extension;
|
||||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public void setExtension(String extension) {
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public String getClassifier() {
|
||||
return classifier;
|
||||
}
|
||||
|
||||
public void setClassifier(String classifier) {
|
||||
this.classifier = classifier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Project {
|
||||
private String modelVersion;
|
||||
|
||||
private String groupId;
|
||||
|
||||
private Scm scm;
|
||||
|
||||
private List<Dependency> dependencies = new ArrayList<>();
|
||||
|
||||
private Build build;
|
||||
|
||||
private String artifactId;
|
||||
|
||||
private String name;
|
||||
|
||||
private String version;
|
||||
|
||||
private Map<String, Artifact> artifactMap = new HashMap<>();
|
||||
private String description;
|
||||
|
||||
public void setModelVersion(String modelVersion) {
|
||||
this.modelVersion = modelVersion;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public void setScm(Scm scm) {
|
||||
this.scm = scm;
|
||||
}
|
||||
|
||||
public void addDependency(Dependency dependency) {
|
||||
this.dependencies.add(dependency);
|
||||
}
|
||||
|
||||
public void setBuild(Build build) {
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
public void setArtifactId(String artifactId) {
|
||||
this.artifactId = artifactId;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Scm getScm() {
|
||||
return scm;
|
||||
}
|
||||
|
||||
public String getModelVersion() {
|
||||
return modelVersion;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public List<Dependency> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public Build getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Dependency[] getDependenciesAsArray() {
|
||||
return getDependencies().toArray(new Dependency[0]);
|
||||
}
|
||||
|
||||
public Map<String, Dependency> getDependenciesAsMap() {
|
||||
Map<String, Dependency> ret = new HashMap<>();
|
||||
for (Dependency dep : getDependencies()) {
|
||||
ret.put(dep.getArtifactId(), dep);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ${project.artifactMap(g:a:v)}
|
||||
public void addArtifact(Artifact a) {
|
||||
artifactMap.put(a.getGroupId() + ":" + a.getArtifactId() + ":" + a.getClassifier(), a);
|
||||
}
|
||||
|
||||
public Map<String, Artifact> getArtifactMap() {
|
||||
return artifactMap;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Build {}
|
||||
|
||||
public static class Dependency {
|
||||
private String artifactId;
|
||||
|
||||
public String getArtifactId() {
|
||||
return artifactId;
|
||||
}
|
||||
|
||||
public void setArtifactId(String id) {
|
||||
artifactId = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Scm {
|
||||
private String connection;
|
||||
|
||||
public void setConnection(String connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public String getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ValueHolder {
|
||||
private final Object value;
|
||||
|
||||
public ValueHolder(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>testRootPropertyRegression.</p>
|
||||
*
|
||||
* @throws Exception if any.
|
||||
*/
|
||||
@Test
|
||||
public void testRootPropertyRegression() throws Exception {
|
||||
Project project = new Project();
|
||||
project.setDescription("c:\\\\org\\apache\\test");
|
||||
Object evalued = ReflectionValueExtractor.evaluate("description", project);
|
||||
assertNotNull(evalued);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue