Added: poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/MethodUtils.java URL: http://svn.apache.org/viewvc/poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/MethodUtils.java?rev=1617141&view=auto ============================================================================== --- poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/MethodUtils.java (added) +++ poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/MethodUtils.java Sun Aug 10 18:25:10 2014 @@ -0,0 +1,1334 @@ +/* ==================================================================== + 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.poi.util; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p> + * + * <h3>Known Limitations</h3> + * <h4>Accessing Public Methods In A Default Access Superclass</h4> + * <p>There is an issue when invoking public methods contained in a default access superclass. + * Reflection locates these methods fine and correctly assigns them as public. + * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p> + * + * <p><code>MethodUtils</code> contains a workaround for this situation. + * It will attempt to call <code>setAccessible</code> on this method. + * If this call succeeds, then the method can be invoked as normal. + * This call will only succeed when the application has sufficient security privilages. + * If this call fails then a warning will be logged and the method may fail.</p> + * + * @author Craig R. McClanahan + * @author Ralph Schaer + * @author Chris Audley + * @author Rey François + * @author Gregor Raýman + * @author Jan Sorensen + * @author Robert Burrell Donkin + */ + +public class MethodUtils { + + // --------------------------------------------------------- Private Methods + + /** + * Only log warning about accessibility work around once. + * <p> + * Note that this is broken when this class is deployed via a shared + * classloader in a container, as the warning message will be emitted + * only once, not once per webapp. However making the warning appear + * once per webapp means having a map keyed by context classloader + * which introduces nasty memory-leak problems. As this warning is + * really optional we can ignore this problem; only one of the webapps + * will get the warning in its logs but that should be good enough. + */ + private static boolean loggedAccessibleWarning = false; + + /** + * Indicates whether methods should be cached for improved performance. + * <p> + * Note that when this class is deployed via a shared classloader in + * a container, this will affect all webapps. However making this + * configurable per webapp would mean having a map keyed by context classloader + * which may introduce memory-leak problems. + */ + private static boolean CACHE_METHODS = true; + + /** An empty class array */ + private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0]; + /** An empty object array */ + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + // --------------------------------------------------------- Public Methods + + /** + * <p>Invoke a named method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than <code>invokeExactMethod()</code>. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeMethod(Object object,String methodName,Object [] args)}. + * </p> + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param arg use this argument + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeMethod( + Object object, + String methodName, + Object arg) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + Object[] args = {arg}; + return invokeMethod(object, methodName, args); + + } + + + /** + * <p>Invoke a named method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. + * </p> + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeMethod( + Object object, + String methodName, + Object[] args) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + int arguments = args.length; + Class[] parameterTypes = new Class[arguments]; + for (int i = 0; i < arguments; i++) { + parameterTypes[i] = args[i].getClass(); + } + return invokeMethod(object, methodName, args, parameterTypes); + + } + + + /** + * <p>Invoke a named method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than {@link + * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeMethod( + Object object, + String methodName, + Object[] args, + Class[] parameterTypes) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (parameterTypes == null) { + parameterTypes = EMPTY_CLASS_PARAMETERS; + } + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + + Method method = getMatchingAccessibleMethod( + object.getClass(), + methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on object: " + object.getClass().getName()); + } + return method.invoke(object, args); + } + + + /** + * <p>Invoke a method whose parameter type matches exactly the object + * type.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. + * </p> + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param arg use this argument + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod( + Object object, + String methodName, + Object arg) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + Object[] args = {arg}; + return invokeExactMethod(object, methodName, args); + + } + + + /** + * <p>Invoke a method whose parameter types match exactly the object + * types.</p> + * + * <p> This uses reflection to invoke the method obtained from a call to + * <code>getAccessibleMethod()</code>.</p> + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod( + Object object, + String methodName, + Object[] args) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + int arguments = args.length; + Class[] parameterTypes = new Class[arguments]; + for (int i = 0; i < arguments; i++) { + parameterTypes[i] = args[i].getClass(); + } + return invokeExactMethod(object, methodName, args, parameterTypes); + + } + + + /** + * <p>Invoke a method whose parameter types match exactly the parameter + * types given.</p> + * + * <p>This uses reflection to invoke the method obtained from a call to + * <code>getAccessibleMethod()</code>.</p> + * + * @param object invoke method on this object + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactMethod( + Object object, + String methodName, + Object[] args, + Class[] parameterTypes) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + + if (parameterTypes == null) { + parameterTypes = EMPTY_CLASS_PARAMETERS; + } + + Method method = getAccessibleMethod( + object.getClass(), + methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on object: " + object.getClass().getName()); + } + return method.invoke(object, args); + + } + + /** + * <p>Invoke a static method whose parameter types match exactly the parameter + * types given.</p> + * + * <p>This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.</p> + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod( + Class objectClass, + String methodName, + Object[] args, + Class[] parameterTypes) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + + if (parameterTypes == null) { + parameterTypes = EMPTY_CLASS_PARAMETERS; + } + + Method method = getAccessibleMethod( + objectClass, + methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + objectClass.getName()); + } + return method.invoke(null, args); + + } + + /** + * <p>Invoke a named static method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than {@link #invokeExactMethod(Object, String, Object[], Class[])}. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}. + * </p> + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param arg use this argument + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod( + Class objectClass, + String methodName, + Object arg) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + Object[] args = {arg}; + return invokeStaticMethod (objectClass, methodName, args); + + } + + + /** + * <p>Invoke a named static method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. + * </p> + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod( + Class objectClass, + String methodName, + Object[] args) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + int arguments = args.length; + Class[] parameterTypes = new Class[arguments]; + for (int i = 0; i < arguments; i++) { + parameterTypes[i] = args[i].getClass(); + } + return invokeStaticMethod (objectClass, methodName, args, parameterTypes); + + } + + + /** + * <p>Invoke a named static method whose parameter type matches the object type.</p> + * + * <p>The behaviour of this method is less deterministic + * than {@link + * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. + * It loops through all methods with names that match + * and then executes the first it finds with compatable parameters.</p> + * + * <p>This method supports calls to methods taking primitive parameters + * via passing in wrapping classes. So, for example, a <code>Boolean</code> class + * would match a <code>boolean</code> primitive.</p> + * + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @param parameterTypes match these parameters - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeStaticMethod( + Class objectClass, + String methodName, + Object[] args, + Class[] parameterTypes) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + if (parameterTypes == null) { + parameterTypes = EMPTY_CLASS_PARAMETERS; + } + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + + Method method = getMatchingAccessibleMethod( + objectClass, + methodName, + parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No such accessible method: " + + methodName + "() on class: " + objectClass.getName()); + } + return method.invoke(null, args); + } + + + /** + * <p>Invoke a static method whose parameter type matches exactly the object + * type.</p> + * + * <p> This is a convenient wrapper for + * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}. + * </p> + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param arg use this argument + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod( + Class objectClass, + String methodName, + Object arg) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + + Object[] args = {arg}; + return invokeExactStaticMethod (objectClass, methodName, args); + + } + + + /** + * <p>Invoke a static method whose parameter types match exactly the object + * types.</p> + * + * <p> This uses reflection to invoke the method obtained from a call to + * {@link #getAccessibleMethod(Class, String, Class[])}.</p> + * + * @param objectClass invoke static method on this class + * @param methodName get method with this name + * @param args use these arguments - treat null as empty array + * @return The value returned by the invoked method + * + * @throws NoSuchMethodException if there is no such accessible method + * @throws InvocationTargetException wraps an exception thrown by the + * method invoked + * @throws IllegalAccessException if the requested method is not accessible + * via reflection + */ + public static Object invokeExactStaticMethod( + Class objectClass, + String methodName, + Object[] args) + throws + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException { + if (args == null) { + args = EMPTY_OBJECT_ARRAY; + } + int arguments = args.length; + Class[] parameterTypes = new Class[arguments]; + for (int i = 0; i < arguments; i++) { + parameterTypes[i] = args[i].getClass(); + } + return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes); + + } + + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) with given name and a single parameter. If no such method + * can be found, return <code>null</code>. + * Basically, a convenience wrapper that constructs a <code>Class</code> + * array for you.</p> + * + * @param clazz get method from this class + * @param methodName get method with this name + * @param parameterType taking this type of parameter + * @return The accessible method + */ + public static Method getAccessibleMethod( + Class clazz, + String methodName, + Class parameterType) { + + Class[] parameterTypes = {parameterType}; + return getAccessibleMethod(clazz, methodName, parameterTypes); + + } + + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) with given name and parameters. If no such method + * can be found, return <code>null</code>. + * This is just a convenient wrapper for + * {@link #getAccessibleMethod(Method method)}.</p> + * + * @param clazz get method from this class + * @param methodName get method with this name + * @param parameterTypes with these parameters types + * @return The accessible method + */ + public static Method getAccessibleMethod( + Class clazz, + String methodName, + Class[] parameterTypes) { + + try { + MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true); + Method method = getAccessibleMethod + (clazz, clazz.getMethod(methodName, parameterTypes)); + return method; + } catch (NoSuchMethodException e) { + return (null); + } + + } + + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) that implements the specified Method. If no such method + * can be found, return <code>null</code>.</p> + * + * @param method The method that we wish to call + * @return The accessible method + */ + public static Method getAccessibleMethod(Method method) { + + // Make sure we have a method to check + if (method == null) { + return (null); + } + + return getAccessibleMethod(method.getDeclaringClass(), method); + + } + + + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) that implements the specified Method. If no such method + * can be found, return <code>null</code>.</p> + * + * @param clazz The class of the object + * @param method The method that we wish to call + * @return The accessible method + */ + public static Method getAccessibleMethod(Class clazz, Method method) { + + // Make sure we have a method to check + if (method == null) { + return (null); + } + + // If the requested method is not public we cannot call it + if (!Modifier.isPublic(method.getModifiers())) { + return (null); + } + + boolean sameClass = true; + if (clazz == null) { + clazz = method.getDeclaringClass(); + } else { + sameClass = clazz.equals(method.getDeclaringClass()); + if (!method.getDeclaringClass().isAssignableFrom(clazz)) { + throw new IllegalArgumentException(clazz.getName() + + " is not assignable from " + method.getDeclaringClass().getName()); + } + } + + // If the class is public, we are done + if (Modifier.isPublic(clazz.getModifiers())) { + if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + setMethodAccessible(method); // Default access superclass workaround + } + return (method); + } + + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + + // Check the implemented interfaces and subinterfaces + method = + getAccessibleMethodFromInterfaceNest(clazz, + methodName, + parameterTypes); + + // Check the superclass chain + if (method == null) { + method = getAccessibleMethodFromSuperclass(clazz, + methodName, + parameterTypes); + } + + return (method); + + } + + + // -------------------------------------------------------- Private Methods + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) by scanning through the superclasses. If no such method + * can be found, return <code>null</code>.</p> + * + * @param clazz Class to be checked + * @param methodName Method name of the method we wish to call + * @param parameterTypes The parameter type signatures + */ + private static Method getAccessibleMethodFromSuperclass + (Class clazz, String methodName, Class[] parameterTypes) { + + Class parentClazz = clazz.getSuperclass(); + while (parentClazz != null) { + if (Modifier.isPublic(parentClazz.getModifiers())) { + try { + return parentClazz.getMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + return null; + } + } + parentClazz = parentClazz.getSuperclass(); + } + return null; + } + + /** + * <p>Return an accessible method (that is, one that can be invoked via + * reflection) that implements the specified method, by scanning through + * all implemented interfaces and subinterfaces. If no such method + * can be found, return <code>null</code>.</p> + * + * <p> There isn't any good reason why this method must be private. + * It is because there doesn't seem any reason why other classes should + * call this rather than the higher level methods.</p> + * + * @param clazz Parent class for the interfaces to be checked + * @param methodName Method name of the method we wish to call + * @param parameterTypes The parameter type signatures + */ + private static Method getAccessibleMethodFromInterfaceNest + (Class clazz, String methodName, Class[] parameterTypes) { + + Method method = null; + + // Search up the superclass chain + for (; clazz != null; clazz = clazz.getSuperclass()) { + + // Check the implemented interfaces of the parent class + Class[] interfaces = clazz.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + + // Is this interface public? + if (!Modifier.isPublic(interfaces[i].getModifiers())) { + continue; + } + + // Does the method exist on this interface? + try { + method = interfaces[i].getDeclaredMethod(methodName, + parameterTypes); + } catch (NoSuchMethodException e) { + /* Swallow, if no method is found after the loop then this + * method returns null. + */ + } + if (method != null) { + return method; + } + + // Recursively check our parent interfaces + method = + getAccessibleMethodFromInterfaceNest(interfaces[i], + methodName, + parameterTypes); + if (method != null) { + return method; + } + + } + + } + + // If we found a method return it + if (method != null) { + return (method); + } + + // We did not find anything + return (null); + + } + + /** + * <p>Find an accessible method that matches the given name and has compatible parameters. + * Compatible parameters mean that every method parameter is assignable from + * the given parameters. + * In other words, it finds a method with the given name + * that will take the parameters given.<p> + * + * <p>This method is slightly undeterminstic since it loops + * through methods names and return the first matching method.</p> + * + * <p>This method is used by + * {@link + * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. + * + * <p>This method can match primitive parameter by passing in wrapper classes. + * For example, a <code>Boolean</code> will match a primitive <code>boolean</code> + * parameter. + * + * @param clazz find method in this class + * @param methodName find method with this name + * @param parameterTypes find method with compatible parameters + * @return The accessible method + */ + public static Method getMatchingAccessibleMethod( + Class clazz, + String methodName, + Class[] parameterTypes) { + // trace logging + Log log = LogFactory.getLog(MethodUtils.class); + if (log.isTraceEnabled()) { + log.trace("Matching name=" + methodName + " on " + clazz); + } + MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false); + + // see if we can find the method directly + // most of the time this works and it's much faster + try { + Method method = clazz.getMethod(methodName, parameterTypes); + if (log.isTraceEnabled()) { + log.trace("Found straight match: " + method); + log.trace("isPublic:" + Modifier.isPublic(method.getModifiers())); + } + + setMethodAccessible(method); // Default access superclass workaround + + return method; + + } catch (NoSuchMethodException e) { /* SWALLOW */ } + + // search through all methods + int paramSize = parameterTypes.length; + Method bestMatch = null; + Method[] methods = clazz.getMethods(); + float bestMatchCost = Float.MAX_VALUE; + float myCost = Float.MAX_VALUE; + for (int i = 0, size = methods.length; i < size ; i++) { + if (methods[i].getName().equals(methodName)) { + // log some trace information + if (log.isTraceEnabled()) { + log.trace("Found matching name:"); + log.trace(methods[i]); + } + + // compare parameters + Class[] methodsParams = methods[i].getParameterTypes(); + int methodParamSize = methodsParams.length; + if (methodParamSize == paramSize) { + boolean match = true; + for (int n = 0 ; n < methodParamSize; n++) { + if (log.isTraceEnabled()) { + log.trace("Param=" + parameterTypes[n].getName()); + log.trace("Method=" + methodsParams[n].getName()); + } + if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { + if (log.isTraceEnabled()) { + log.trace(methodsParams[n] + " is not assignable from " + + parameterTypes[n]); + } + match = false; + break; + } + } + + if (match) { + // get accessible version of method + Method method = getAccessibleMethod(clazz, methods[i]); + if (method != null) { + if (log.isTraceEnabled()) { + log.trace(method + " accessible version of " + + methods[i]); + } + setMethodAccessible(method); // Default access superclass workaround + myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes()); + if ( myCost < bestMatchCost ) { + bestMatch = method; + bestMatchCost = myCost; + } + } + + log.trace("Couldn't find accessible method."); + } + } + } + } + if ( bestMatch == null ){ + // didn't find a match + log.trace("No match found."); + } + + return bestMatch; + } + + public static <T> Constructor<T> getMatchingAccessibleConstructor( + Class<T> clazz, + Class[] parameterTypes) { + // trace logging + Log log = LogFactory.getLog(MethodUtils.class); + MethodDescriptor md = new MethodDescriptor(clazz, "dummy", parameterTypes, false); + + // see if we can find the method directly + // most of the time this works and it's much faster + try { + Constructor<T> constructor = clazz.getConstructor(parameterTypes); + if (log.isTraceEnabled()) { + log.trace("Found straight match: " + constructor); + log.trace("isPublic:" + Modifier.isPublic(constructor.getModifiers())); + } + + setMethodAccessible(constructor); // Default access superclass workaround + + return constructor; + + } catch (NoSuchMethodException e) { /* SWALLOW */ } + + // search through all methods + int paramSize = parameterTypes.length; + Constructor<T> bestMatch = null; + Constructor<?>[] constructors = clazz.getConstructors(); + float bestMatchCost = Float.MAX_VALUE; + float myCost = Float.MAX_VALUE; + for (int i = 0, size = constructors.length; i < size ; i++) { + // compare parameters + Class[] methodsParams = constructors[i].getParameterTypes(); + int methodParamSize = methodsParams.length; + if (methodParamSize == paramSize) { + boolean match = true; + for (int n = 0 ; n < methodParamSize; n++) { + if (log.isTraceEnabled()) { + log.trace("Param=" + parameterTypes[n].getName()); + log.trace("Method=" + methodsParams[n].getName()); + } + if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { + if (log.isTraceEnabled()) { + log.trace(methodsParams[n] + " is not assignable from " + + parameterTypes[n]); + } + match = false; + break; + } + } + + if (match) { + // get accessible version of method + Constructor<T> cons = (Constructor<T>)constructors[i]; + myCost = getTotalTransformationCost(parameterTypes,cons.getParameterTypes()); + if ( myCost < bestMatchCost ) { + bestMatch = cons; + bestMatchCost = myCost; + } + } + } + } + if ( bestMatch == null ){ + // didn't find a match + log.trace("No match found."); + } + + return bestMatch; + } + + /** + * Try to make the method accessible + * @param method The source arguments + */ + private static void setMethodAccessible(Object method) { + try { + // + // XXX Default access superclass workaround + // + // When a public class has a default access superclass + // with public methods, these methods are accessible. + // Calling them from compiled code works fine. + // + // Unfortunately, using reflection to invoke these methods + // seems to (wrongly) to prevent access even when the method + // modifer is public. + // + // The following workaround solves the problem but will only + // work from sufficiently privilages code. + // + // Better workarounds would be greatfully accepted. + // + if (method instanceof Method) { + ((Method)method).setAccessible(true); + } else if (method instanceof Constructor) { + ((Constructor)method).setAccessible(true); + } else { + throw new RuntimeException("invalid parameter"); + } + + } catch (SecurityException se) { + // log but continue just in case the method.invoke works anyway + Log log = LogFactory.getLog(MethodUtils.class); + if (!loggedAccessibleWarning) { + boolean vulnerableJVM = false; + try { + String specVersion = System.getProperty("java.specification.version"); + if (specVersion.charAt(0) == '1' && + (specVersion.charAt(2) == '0' || + specVersion.charAt(2) == '1' || + specVersion.charAt(2) == '2' || + specVersion.charAt(2) == '3')) { + + vulnerableJVM = true; + } + } catch (SecurityException e) { + // don't know - so display warning + vulnerableJVM = true; + } + if (vulnerableJVM) { + log.warn( + "Current Security Manager restricts use of workarounds for reflection bugs " + + " in pre-1.4 JVMs."); + } + loggedAccessibleWarning = true; + } + log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se); + } + } + + /** + * Returns the sum of the object transformation cost for each class in the source + * argument list. + * @param srcArgs The source arguments + * @param destArgs The destination arguments + * @return The total transformation cost + */ + private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) { + + float totalCost = 0.0f; + for (int i = 0; i < srcArgs.length; i++) { + Class srcClass, destClass; + srcClass = srcArgs[i]; + destClass = destArgs[i]; + totalCost += getObjectTransformationCost(srcClass, destClass); + } + + return totalCost; + } + + /** + * Gets the number of steps required needed to turn the source class into the + * destination class. This represents the number of steps in the object hierarchy + * graph. + * @param srcClass The source class + * @param destClass The destination class + * @return The cost of transforming an object + */ + private static float getObjectTransformationCost(Class srcClass, Class destClass) { + float cost = 0.0f; + while (destClass != null && !destClass.equals(srcClass)) { + if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) { + // slight penalty for interface match. + // we still want an exact match to override an interface match, but + // an interface match should override anything where we have to get a + // superclass. + cost += 0.25f; + break; + } + cost++; + destClass = destClass.getSuperclass(); + } + + /* + * If the destination class is null, we've travelled all the way up to + * an Object match. We'll penalize this by adding 1.5 to the cost. + */ + if (destClass == null) { + cost += 1.5f; + } + + return cost; + } + + + /** + * <p>Determine whether a type can be used as a parameter in a method invocation. + * This method handles primitive conversions correctly.</p> + * + * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>, + * a <code>Long</code> to a <code>long</code>, + * a <code>Float</code> to a <code>float</code>, + * a <code>Integer</code> to a <code>int</code>, + * and a <code>Double</code> to a <code>double</code>. + * Now logic widening matches are allowed. + * For example, a <code>Long</code> will not match a <code>int</code>. + * + * @param parameterType the type of parameter accepted by the method + * @param parameterization the type of parameter being tested + * + * @return true if the assignement is compatible. + */ + public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) { + // try plain assignment + if (parameterType.isAssignableFrom(parameterization)) { + return true; + } + + if (parameterType.isPrimitive()) { + // this method does *not* do widening - you must specify exactly + // is this the right behaviour? + Class parameterWrapperClazz = getPrimitiveWrapper(parameterType); + if (parameterWrapperClazz != null) { + return parameterWrapperClazz.equals(parameterization); + } + } + + return false; + } + + /** + * Gets the wrapper object class for the given primitive type class. + * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code> + * @param primitiveType the primitive type class for which a match is to be found + * @return the wrapper type associated with the given primitive + * or null if no match is found + */ + public static Class getPrimitiveWrapper(Class primitiveType) { + // does anyone know a better strategy than comparing names? + if (boolean.class.equals(primitiveType)) { + return Boolean.class; + } else if (float.class.equals(primitiveType)) { + return Float.class; + } else if (long.class.equals(primitiveType)) { + return Long.class; + } else if (int.class.equals(primitiveType)) { + return Integer.class; + } else if (short.class.equals(primitiveType)) { + return Short.class; + } else if (byte.class.equals(primitiveType)) { + return Byte.class; + } else if (double.class.equals(primitiveType)) { + return Double.class; + } else if (char.class.equals(primitiveType)) { + return Character.class; + } else { + + return null; + } + } + + /** + * Gets the class for the primitive type corresponding to the primitive wrapper class given. + * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>. + * @param wrapperType the + * @return the primitive type class corresponding to the given wrapper class, + * null if no match is found + */ + public static Class getPrimitiveType(Class wrapperType) { + // does anyone know a better strategy than comparing names? + if (Boolean.class.equals(wrapperType)) { + return boolean.class; + } else if (Float.class.equals(wrapperType)) { + return float.class; + } else if (Long.class.equals(wrapperType)) { + return long.class; + } else if (Integer.class.equals(wrapperType)) { + return int.class; + } else if (Short.class.equals(wrapperType)) { + return short.class; + } else if (Byte.class.equals(wrapperType)) { + return byte.class; + } else if (Double.class.equals(wrapperType)) { + return double.class; + } else if (Character.class.equals(wrapperType)) { + return char.class; + } else { + Log log = LogFactory.getLog(MethodUtils.class); + if (log.isDebugEnabled()) { + log.debug("Not a known primitive wrapper class: " + wrapperType); + } + return null; + } + } + + /** + * Find a non primitive representation for given primitive class. + * + * @param clazz the class to find a representation for, not null + * @return the original class if it not a primitive. Otherwise the wrapper class. Not null + */ + public static Class toNonPrimitiveClass(Class clazz) { + if (clazz.isPrimitive()) { + Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz); + // the above method returns + if (primitiveClazz != null) { + return primitiveClazz; + } else { + return clazz; + } + } else { + return clazz; + } + } + + + /** + * Represents the key to looking up a Method by reflection. + */ + private static class MethodDescriptor { + private Class cls; + private String methodName; + private Class[] paramTypes; + private boolean exact; + private int hashCode; + + /** + * The sole constructor. + * + * @param cls the class to reflect, must not be null + * @param methodName the method name to obtain + * @param paramTypes the array of classes representing the paramater types + * @param exact whether the match has to be exact. + */ + public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) { + if (cls == null) { + throw new IllegalArgumentException("Class cannot be null"); + } + if (methodName == null) { + throw new IllegalArgumentException("Method Name cannot be null"); + } + if (paramTypes == null) { + paramTypes = EMPTY_CLASS_PARAMETERS; + } + + this.cls = cls; + this.methodName = methodName; + this.paramTypes = paramTypes; + this.exact= exact; + + this.hashCode = methodName.length(); + } + /** + * Checks for equality. + * @param obj object to be tested for equality + * @return true, if the object describes the same Method. + */ + public boolean equals(Object obj) { + if (!(obj instanceof MethodDescriptor)) { + return false; + } + MethodDescriptor md = (MethodDescriptor)obj; + + return ( + exact == md.exact && + methodName.equals(md.methodName) && + cls.equals(md.cls) && + java.util.Arrays.equals(paramTypes, md.paramTypes) + ); + } + /** + * Returns the string length of method name. I.e. if the + * hashcodes are different, the objects are different. If the + * hashcodes are the same, need to use the equals method to + * determine equality. + * @return the string length of method name. + */ + public int hashCode() { + return hashCode; + } + } +}
Modified: poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/SAXHelper.java URL: http://svn.apache.org/viewvc/poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/SAXHelper.java?rev=1617141&r1=1617140&r2=1617141&view=diff ============================================================================== --- poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/SAXHelper.java (original) +++ poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/SAXHelper.java Sun Aug 10 18:25:10 2014 @@ -23,6 +23,9 @@ import java.io.StringReader; import java.lang.reflect.Method; import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.dom4j.Document; import org.dom4j.DocumentException; @@ -89,4 +92,72 @@ public final class SAXHelper { public static Document readSAXDocument(InputStream inp) throws DocumentException { return getSAXReader().read(inp); } + + private static final EntityResolver IGNORING_ENTITY_RESOLVER = new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + return new InputSource(new StringReader("")); + } + }; + + private static void trySetSAXFeature(DocumentBuilderFactory documentBuilderFactory, String feature, boolean enabled) { + try { + documentBuilderFactory.setFeature(feature, enabled); + } catch (Exception e) { + logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e); + } + } + private static void trySetXercesSecurityManager(DocumentBuilderFactory documentBuilderFactory) { + // Try built-in JVM one first, standalone if not + for (String securityManagerClassName : new String[] { + "com.sun.org.apache.xerces.internal.util.SecurityManager", + "org.apache.xerces.util.SecurityManager" + }) { + try { + Object mgr = Class.forName(securityManagerClassName).newInstance(); + Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE); + setLimit.invoke(mgr, 4096); + documentBuilderFactory.setAttribute("http://apache.org/xml/properties/security-manager", mgr); + // Stop once one can be setup without error + return; + } catch (Exception e) { + logger.log(POILogger.INFO, "SAX Security Manager could not be setup", e); + } + } + } + + private static final ThreadLocal<DocumentBuilder> documentBuilder = new ThreadLocal<DocumentBuilder>() { + @Override + protected DocumentBuilder initialValue() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + trySetSAXFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); + trySetXercesSecurityManager(factory); + try { + return factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("cannot create a DocumentBuilder", e); + } + } + + @Override + public DocumentBuilder get() { + DocumentBuilder documentBuilder = super.get(); + documentBuilder.reset(); + documentBuilder.setEntityResolver(IGNORING_ENTITY_RESOLVER); + return documentBuilder; + } + }; + + /** + * Parses the given stream via the default (sensible) + * SAX Reader + * @param inp Stream to read the XML data from + * @return the SAX processed Document + */ + public static org.w3c.dom.Document readSAXDocumentW3C(InputStream inp) throws IOException, SAXException { + return documentBuilder.get().parse(inp); + } } Added: poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/XmlSort.java URL: http://svn.apache.org/viewvc/poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/XmlSort.java?rev=1617141&view=auto ============================================================================== --- poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/XmlSort.java (added) +++ poi/branches/xml_signature/src/ooxml/java/org/apache/poi/util/XmlSort.java Sun Aug 10 18:25:10 2014 @@ -0,0 +1,221 @@ +/* ==================================================================== + 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.poi.util; + +import java.io.File; +import java.io.IOException; +import java.util.Comparator; + +import javax.xml.namespace.QName; + +import org.apache.xmlbeans.XmlCursor; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlObject; + +/** + */ +public final class XmlSort +{ + /** + * Receives an XML element instance and sorts the children of this + * element in lexicographical (by default) order. + * + * @param args An array in which the first item is a + * path to the XML instance file and the second item (optional) is + * an XPath inside the document identifying the element to be sorted + */ + public static void main(String[] args) + { + if (args.length < 1 || args.length > 2) + { + System.out.println(" java XmlSort <XML_File> [<XPath>]"); + return; + } + File f = new File(args[0]); + try + { + XmlObject docInstance = XmlObject.Factory.parse(f); + XmlObject element = null; + if (args.length > 1) + { + String xpath = args[1]; + XmlObject[] result = docInstance.selectPath(xpath); + if (result.length == 0) + { + System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results"); + } + else if (result.length > 1) + { + System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " + + "node (" + result.length + ")"); + } + else + element = result[0]; + } + else + { + // Navigate to the root element + XmlCursor c = docInstance.newCursor(); + c.toFirstChild(); + element = c.getObject(); + c.dispose(); + } + if (element != null) + sort(element, new QNameComparator(QNameComparator.ASCENDING)); + System.out.println(docInstance.xmlText()); + } + catch (IOException ioe) + { + System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " + + ioe.getMessage()); + } + catch (XmlException xe) + { + System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " + + xe.getMessage()); + } + } + + /** + * Sorts the children of <code>element</code> according to the order indicated by the + * comparator. + * @param element the element whose content is to be sorted. Only element children are sorted, + * attributes are not touched. When elements are reordered, all the text, comments and PIs + * follow the element that they come immediately after. + * @param comp a comparator that is to be used when comparing the <code>QName</code>s of two + * elements. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple + * implementation that compares two elements based on the value of their QName, but more + * complicated implementations are possible, for instance, ones that compare two elements based + * on the value of a specifc attribute etc. + * @throws IllegalArgumentException if the input <code>XmlObject</code> does not represent + * an element + */ + public static void sort(XmlObject element, Comparator<XmlCursor> comp) + { + XmlCursor headCursor = element.newCursor(); + if (!headCursor.isStart()) + throw new IllegalStateException("The element parameter must point to a STARTDOC"); + // We use insertion sort to minimize the number of swaps, because each swap means + // moving a part of the document + /* headCursor points to the beginning of the list of the already sorted items and + listCursor points to the beginning of the list of unsorted items + At the beginning, headCursor points to the first element and listCursor points to the + second element. The algorithm ends when listCursor cannot be moved to the "next" + element in the unsorted list, i.e. the unsorted list becomes empty */ + boolean moved = headCursor.toFirstChild(); + if (!moved) + { + // Cursor was not moved, which means that the given element has no children and + // therefore there is nothing to sort + return; + } + XmlCursor listCursor = headCursor.newCursor(); + boolean moreElements = listCursor.toNextSibling(); + while (moreElements) + { + moved = false; + // While we can move the head of the unsorted list, it means that there are still + // items (elements) that need to be sorted + while (headCursor.comparePosition(listCursor) < 0) + { + if (comp.compare(headCursor, listCursor) > 0) + { + // We have found the position in the sorted list, insert the element and the + // text following the element in the current position + /* + * Uncomment this code to cause the text before the element to move along + * with the element, rather than the text after the element. Notice that this + * is more difficult to do, because the cursor's "type" refers to the position + * to the right of the cursor, so to get the type of the token to the left, the + * cursor needs to be first moved to the left (previous token) + * + headCursor.toPrevToken(); + while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText()) + headCursor.toPrevToken(); + headCursor.toNextToken(); + listCursor.toPrevToken(); + while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText()) + listCursor.toPrevToken(); + listCursor.toNextToken(); + while (!listCursor.isStart()) + listCursor.moveXml(headCursor); + listCursor.moveXml(headCursor); + */ + // Move the element + listCursor.moveXml(headCursor); + // Move the text following the element + while (!listCursor.isStart() && !listCursor.isEnd()) + listCursor.moveXml(headCursor); + moreElements = listCursor.isStart(); + moved = true; + break; + } + headCursor.toNextSibling(); + } + if (!moved) + { + // Because during the move of a fragment of XML, the listCursor is also moved, in + // case we didn't need to move XML (the new element to be inserted happened to + // be the last one in order), we need to move this cursor + moreElements = listCursor.toNextSibling(); + } + // Reposition the head of the sorted list + headCursor.toParent(); + headCursor.toFirstChild(); + } + } + + /** + * Implements a <code>java.util.Comparator</code> for comparing <code>QName</code>values. + * The namespace URIs are compared first and if they are equal, the local parts are compared. + * <p/> + * The constructor accepts an argument indicating whether the comparison order is the same as + * the lexicographic order of the strings or the reverse. + */ + public static final class QNameComparator implements Comparator + { + public static final int ASCENDING = 1; + public static final int DESCENDING = 2; + + private int order; + + public QNameComparator(int order) + { + this.order = order; + if (order != ASCENDING && order != DESCENDING) + throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+ + "comparison orders"); + } + + public int compare(Object o, Object o1) + { + XmlCursor cursor1 = (XmlCursor) o; + XmlCursor cursor2 = (XmlCursor) o1; + QName qname1 = cursor1.getName(); + QName qname2 = cursor2.getName(); + int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI()); + if (qnameComparisonRes == 0) + return order == ASCENDING ? + qname1.getLocalPart().compareTo(qname2.getLocalPart()) : + -qname1.getLocalPart().compareTo(qname2.getLocalPart()); + else + return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes; + } + } +} + \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
