// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.util; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * TYPE Utilities. * Provides various static utiltiy methods for manipulating types and their * string representations. * * @since Jetty 4.1 */ public class TypeUtil { private static final Logger LOG = Log.getLogger(TypeUtil.class); public static final Class<?>[] NO_ARGS = new Class[]{}; public static final int CR = '\015'; public static final int LF = '\012'; /* ------------------------------------------------------------ */ private static final HashMap<String, Class<?>> name2Class=new HashMap<>(); static { name2Class.put("boolean",java.lang.Boolean.TYPE); name2Class.put("byte",java.lang.Byte.TYPE); name2Class.put("char",java.lang.Character.TYPE); name2Class.put("double",java.lang.Double.TYPE); name2Class.put("float",java.lang.Float.TYPE); name2Class.put("int",java.lang.Integer.TYPE); name2Class.put("long",java.lang.Long.TYPE); name2Class.put("short",java.lang.Short.TYPE); name2Class.put("void",java.lang.Void.TYPE); name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE); name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE); name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE); name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE); name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE); name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE); name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE); name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE); name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE); name2Class.put("java.lang.Boolean",java.lang.Boolean.class); name2Class.put("java.lang.Byte",java.lang.Byte.class); name2Class.put("java.lang.Character",java.lang.Character.class); name2Class.put("java.lang.Double",java.lang.Double.class); name2Class.put("java.lang.Float",java.lang.Float.class); name2Class.put("java.lang.Integer",java.lang.Integer.class); name2Class.put("java.lang.Long",java.lang.Long.class); name2Class.put("java.lang.Short",java.lang.Short.class); name2Class.put("Boolean",java.lang.Boolean.class); name2Class.put("Byte",java.lang.Byte.class); name2Class.put("Character",java.lang.Character.class); name2Class.put("Double",java.lang.Double.class); name2Class.put("Float",java.lang.Float.class); name2Class.put("Integer",java.lang.Integer.class); name2Class.put("Long",java.lang.Long.class); name2Class.put("Short",java.lang.Short.class); name2Class.put(null,java.lang.Void.TYPE); name2Class.put("string",java.lang.String.class); name2Class.put("String",java.lang.String.class); name2Class.put("java.lang.String",java.lang.String.class); } /* ------------------------------------------------------------ */ private static final HashMap<Class<?>, String> class2Name=new HashMap<>(); static { class2Name.put(java.lang.Boolean.TYPE,"boolean"); class2Name.put(java.lang.Byte.TYPE,"byte"); class2Name.put(java.lang.Character.TYPE,"char"); class2Name.put(java.lang.Double.TYPE,"double"); class2Name.put(java.lang.Float.TYPE,"float"); class2Name.put(java.lang.Integer.TYPE,"int"); class2Name.put(java.lang.Long.TYPE,"long"); class2Name.put(java.lang.Short.TYPE,"short"); class2Name.put(java.lang.Void.TYPE,"void"); class2Name.put(java.lang.Boolean.class,"java.lang.Boolean"); class2Name.put(java.lang.Byte.class,"java.lang.Byte"); class2Name.put(java.lang.Character.class,"java.lang.Character"); class2Name.put(java.lang.Double.class,"java.lang.Double"); class2Name.put(java.lang.Float.class,"java.lang.Float"); class2Name.put(java.lang.Integer.class,"java.lang.Integer"); class2Name.put(java.lang.Long.class,"java.lang.Long"); class2Name.put(java.lang.Short.class,"java.lang.Short"); class2Name.put(null,"void"); class2Name.put(java.lang.String.class,"java.lang.String"); } /* ------------------------------------------------------------ */ private static final HashMap<Class<?>, Method> class2Value=new HashMap<>(); static { try { Class<?>[] s ={java.lang.String.class}; class2Value.put(java.lang.Boolean.TYPE, java.lang.Boolean.class.getMethod("valueOf",s)); class2Value.put(java.lang.Byte.TYPE, java.lang.Byte.class.getMethod("valueOf",s)); class2Value.put(java.lang.Double.TYPE, java.lang.Double.class.getMethod("valueOf",s)); class2Value.put(java.lang.Float.TYPE, java.lang.Float.class.getMethod("valueOf",s)); class2Value.put(java.lang.Integer.TYPE, java.lang.Integer.class.getMethod("valueOf",s)); class2Value.put(java.lang.Long.TYPE, java.lang.Long.class.getMethod("valueOf",s)); class2Value.put(java.lang.Short.TYPE, java.lang.Short.class.getMethod("valueOf",s)); class2Value.put(java.lang.Boolean.class, java.lang.Boolean.class.getMethod("valueOf",s)); class2Value.put(java.lang.Byte.class, java.lang.Byte.class.getMethod("valueOf",s)); class2Value.put(java.lang.Double.class, java.lang.Double.class.getMethod("valueOf",s)); class2Value.put(java.lang.Float.class, java.lang.Float.class.getMethod("valueOf",s)); class2Value.put(java.lang.Integer.class, java.lang.Integer.class.getMethod("valueOf",s)); class2Value.put(java.lang.Long.class, java.lang.Long.class.getMethod("valueOf",s)); class2Value.put(java.lang.Short.class, java.lang.Short.class.getMethod("valueOf",s)); } catch(Exception e) { throw new Error(e); } } /* ------------------------------------------------------------ */ /** Array to List. * <p> * Works like {@link Arrays#asList(Object...)}, but handles null arrays. * @param a the array to convert to a list * @return a list backed by the array. * @param <T> the array and list entry type */ public static <T> List<T> asList(T[] a) { if (a==null) return Collections.emptyList(); return Arrays.asList(a); } /* ------------------------------------------------------------ */ /** Class from a canonical name for a type. * @param name A class or type name. * @return A class , which may be a primitive TYPE field.. */ public static Class<?> fromName(String name) { return name2Class.get(name); } /* ------------------------------------------------------------ */ /** Canonical name for a type. * @param type A class , which may be a primitive TYPE field. * @return Canonical name. */ public static String toName(Class<?> type) { return class2Name.get(type); } /* ------------------------------------------------------------ */ /** Convert String value to instance. * @param type The class of the instance, which may be a primitive TYPE field. * @param value The value as a string. * @return The value as an Object. */ public static Object valueOf(Class<?> type, String value) { try { if (type.equals(java.lang.String.class)) return value; Method m = class2Value.get(type); if (m!=null) return m.invoke(null, value); if (type.equals(java.lang.Character.TYPE) || type.equals(java.lang.Character.class)) return value.charAt(0); Constructor<?> c = type.getConstructor(java.lang.String.class); return c.newInstance(value); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException x) { LOG.ignore(x); } catch (InvocationTargetException x) { if (x.getTargetException() instanceof Error) throw (Error)x.getTargetException(); LOG.ignore(x); } return null; } /* ------------------------------------------------------------ */ /** Convert String value to instance. * @param type classname or type (eg int) * @param value The value as a string. * @return The value as an Object. */ public static Object valueOf(String type, String value) { return valueOf(fromName(type),value); } /* ------------------------------------------------------------ */ /** Parse an int from a substring. * Negative numbers are not handled. * @param s String * @param offset Offset within string * @param length Length of integer or -1 for remainder of string * @param base base of the integer * @return the parsed integer * @throws NumberFormatException if the string cannot be parsed */ public static int parseInt(String s, int offset, int length, int base) throws NumberFormatException { int value=0; if (length<0) length=s.length()-offset; for (int i=0;i<length;i++) { char c=s.charAt(offset+i); int digit=convertHexDigit((int)c); if (digit<0 || digit>=base) throw new NumberFormatException(s.substring(offset,offset+length)); value=value*base+digit; } return value; } /* ------------------------------------------------------------ */ /** Parse an int from a byte array of ascii characters. * Negative numbers are not handled. * @param b byte array * @param offset Offset within string * @param length Length of integer or -1 for remainder of string * @param base base of the integer * @return the parsed integer * @throws NumberFormatException if the array cannot be parsed into an integer */ public static int parseInt(byte[] b, int offset, int length, int base) throws NumberFormatException { int value=0; if (length<0) length=b.length-offset; for (int i=0;i<length;i++) { char c=(char)(0xff&b[offset+i]); int digit=c-'0'; if (digit<0 || digit>=base || digit>=10) { digit=10+c-'A'; if (digit<10 || digit>=base) digit=10+c-'a'; } if (digit<0 || digit>=base) throw new NumberFormatException(new String(b,offset,length)); value=value*base+digit; } return value; } /* ------------------------------------------------------------ */ public static byte[] parseBytes(String s, int base) { byte[] bytes=new byte[s.length()/2]; for (int i=0;i<s.length();i+=2) bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base); return bytes; } /* ------------------------------------------------------------ */ public static String toString(byte[] bytes, int base) { StringBuilder buf = new StringBuilder(); for (byte b : bytes) { int bi=0xff&b; int c='0'+(bi/base)%base; if (c>'9') c= 'a'+(c-'0'-10); buf.append((char)c); c='0'+bi%base; if (c>'9') c= 'a'+(c-'0'-10); buf.append((char)c); } return buf.toString(); } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static byte convertHexDigit( byte c ) { byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (b<0 || b>15) throw new NumberFormatException("!hex "+c); return b; } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static int convertHexDigit( char c ) { int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (d<0 || d>15) throw new NumberFormatException("!hex "+c); return d; } /* ------------------------------------------------------------ */ /** * @param c An ASCII encoded character 0-9 a-f A-F * @return The byte value of the character 0-16. */ public static int convertHexDigit( int c ) { int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10); if (d<0 || d>15) throw new NumberFormatException("!hex "+c); return d; } /* ------------------------------------------------------------ */ public static void toHex(byte b,Appendable buf) { try { int d=0xf&((0xF0&b)>>4); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&b; buf.append((char)((d>9?('A'-10):'0')+d)); } catch(IOException e) { throw new RuntimeException(e); } } /* ------------------------------------------------------------ */ public static void toHex(int value,Appendable buf) throws IOException { int d=0xf&((0xF0000000&value)>>28); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x0F000000&value)>>24); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x00F00000&value)>>20); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x000F0000&value)>>16); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x0000F000&value)>>12); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x00000F00&value)>>8); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&((0x000000F0&value)>>4); buf.append((char)((d>9?('A'-10):'0')+d)); d=0xf&value; buf.append((char)((d>9?('A'-10):'0')+d)); Integer.toString(0,36); } /* ------------------------------------------------------------ */ public static void toHex(long value,Appendable buf) throws IOException { toHex((int)(value>>32),buf); toHex((int)value,buf); } /* ------------------------------------------------------------ */ public static String toHexString(byte b) { return toHexString(new byte[]{b}, 0, 1); } /* ------------------------------------------------------------ */ public static String toHexString(byte[] b) { return toHexString(b, 0, b.length); } /* ------------------------------------------------------------ */ public static String toHexString(byte[] b,int offset,int length) { StringBuilder buf = new StringBuilder(); for (int i=offset;i<offset+length;i++) { int bi=0xff&b[i]; int c='0'+(bi/16)%16; if (c>'9') c= 'A'+(c-'0'-10); buf.append((char)c); c='0'+bi%16; if (c>'9') c= 'a'+(c-'0'-10); buf.append((char)c); } return buf.toString(); } /* ------------------------------------------------------------ */ public static byte[] fromHexString(String s) { if (s.length()%2!=0) throw new IllegalArgumentException(s); byte[] array = new byte[s.length()/2]; for (int i=0;i<array.length;i++) { int b = Integer.parseInt(s.substring(i*2,i*2+2),16); array[i]=(byte)(0xff&b); } return array; } public static void dump(Class<?> c) { System.err.println("Dump: "+c); dump(c.getClassLoader()); } public static void dump(ClassLoader cl) { System.err.println("Dump Loaders:"); while(cl!=null) { System.err.println(" loader "+cl); cl = cl.getParent(); } } public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(oClass,"Class cannot be null"); Objects.requireNonNull(methodName,"Method name cannot be null"); if (StringUtil.isBlank(methodName)) { throw new IllegalArgumentException("Method name cannot be blank"); } // Lets just try all methods for now for (Method method : oClass.getMethods()) { if (!method.getName().equals(methodName)) continue; if (method.getParameterCount() != arg.length) continue; if (Modifier.isStatic(method.getModifiers()) != (obj == null)) continue; if ((obj == null) && method.getDeclaringClass() != oClass) continue; try { return method.invoke(obj, arg); } catch (IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } // Lets look for a method with optional arguments Object[] args_with_opts=null; for (Method method : oClass.getMethods()) { if (!method.getName().equals(methodName)) continue; if (method.getParameterCount() != arg.length+1) continue; if (!method.getParameterTypes()[arg.length].isArray()) continue; if (Modifier.isStatic(method.getModifiers()) != (obj == null)) continue; if ((obj == null) && method.getDeclaringClass() != oClass) continue; if (args_with_opts==null) args_with_opts=ArrayUtil.addToArray(arg,new Object[]{},Object.class); try { return method.invoke(obj, args_with_opts); } catch (IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException(methodName); } public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(klass,"Class cannot be null"); for (Constructor<?> constructor : klass.getConstructors()) { if (arguments == null) { // null arguments in .newInstance() is allowed if (constructor.getParameterCount() != 0) continue; } else if (constructor.getParameterCount() != arguments.length) continue; try { return constructor.newInstance(arguments); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException("<init>"); } public static Object construct(Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException { Objects.requireNonNull(klass,"Class cannot be null"); Objects.requireNonNull(namedArgMap,"Named Argument Map cannot be null"); for (Constructor<?> constructor : klass.getConstructors()) { if (arguments == null) { // null arguments in .newInstance() is allowed if (constructor.getParameterCount() != 0) continue; } else if (constructor.getParameterCount() != arguments.length) continue; try { Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); if (arguments == null || arguments.length == 0) { if (LOG.isDebugEnabled()) LOG.debug("Constructor has no arguments"); return constructor.newInstance(arguments); } else if (parameterAnnotations == null || parameterAnnotations.length == 0) { if (LOG.isDebugEnabled()) LOG.debug("Constructor has no parameter annotations"); return constructor.newInstance(arguments); } else { Object[] swizzled = new Object[arguments.length]; int count = 0; for ( Annotation[] annotations : parameterAnnotations ) { for ( Annotation annotation : annotations) { if ( annotation instanceof Name ) { Name param = (Name)annotation; if (namedArgMap.containsKey(param.value())) { if (LOG.isDebugEnabled()) LOG.debug("placing named {} in position {}", param.value(), count); swizzled[count] = namedArgMap.get(param.value()); } else { if (LOG.isDebugEnabled()) LOG.debug("placing {} in position {}", arguments[count], count); swizzled[count] = arguments[count]; } ++count; } else { if (LOG.isDebugEnabled()) LOG.debug("passing on annotation {}", annotation); } } } return constructor.newInstance(swizzled); } } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { LOG.ignore(e); } } throw new NoSuchMethodException("<init>"); } /* ------------------------------------------------------------ */ /** * @param o Object to test for true * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true. */ public static boolean isTrue(Object o) { if (o==null) return false; if (o instanceof Boolean) return ((Boolean)o).booleanValue(); return Boolean.parseBoolean(o.toString()); } /* ------------------------------------------------------------ */ /** * @param o Object to test for false * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false. */ public static boolean isFalse(Object o) { if (o==null) return false; if (o instanceof Boolean) return !((Boolean)o).booleanValue(); return "false".equalsIgnoreCase(o.toString()); } /* ------------------------------------------------------------ */ public static URI getLocationOfClass(Class<?> clazz) { try { ProtectionDomain domain = clazz.getProtectionDomain(); if (domain != null) { CodeSource source = domain.getCodeSource(); if (source != null) { URL location = source.getLocation(); if (location != null) return location.toURI(); } } String resourceName = clazz.getName().replace('.', '/') + ".class"; ClassLoader loader = clazz.getClassLoader(); URL url = (loader == null ? ClassLoader.getSystemClassLoader() : loader).getResource(resourceName); if (url != null) { return URIUtil.getJarSource(url.toURI()); } } catch (URISyntaxException e) { LOG.debug(e); } return null; } }