http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java new file mode 100644 index 0000000..decc1f0 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java @@ -0,0 +1,788 @@ +/* + * 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.brooklyn.util.javalang; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +/** + * Reflection utilities ("borrowed" from cloudsoft monterey). + * + * @author aled + */ +public class Reflections { + + private static final Logger LOG = LoggerFactory.getLogger(Reflections.class); + + public static class ReflectionNotFoundException extends RuntimeException { + private static final long serialVersionUID = 9032835250796708037L; + public ReflectionNotFoundException(String message, Throwable cause) { + super(message, cause); + } + public ReflectionNotFoundException(String message) { + super(message); + } + } + + public static class ReflectionAccessException extends RuntimeException { + private static final long serialVersionUID = 6569605861192432009L; + + public ReflectionAccessException(String message, Throwable cause) { + super(message, cause); + } + } + + private final ClassLoader classLoader; + + public Reflections(ClassLoader classLoader) { + this.classLoader = checkNotNull(classLoader); + } + + public Object loadInstance(String classname, Object...argValues) throws ReflectionNotFoundException, ReflectionAccessException { + Class<?> clazz = loadClass(classname); + Optional<?> v = null; + try { + v = invokeConstructorWithArgs(clazz, argValues); + if (v.isPresent()) return v.get(); + } catch (Exception e) { + throw new IllegalStateException("Error invoking constructor for "+clazz+Arrays.toString(argValues) + ": " + Exceptions.collapseText(e)); + } + throw new IllegalStateException("No suitable constructor for "+clazz+Arrays.toString(argValues)); + } + public Object loadInstance(String classname, Class<?>[] argTypes, Object[] argValues) throws ReflectionNotFoundException, ReflectionAccessException { + Class<?> clazz = loadClass(classname); + Constructor<?> constructor = loadConstructor(clazz, argTypes); + return loadInstance(constructor, argValues); + } + + public Object loadInstance(String classname) throws ReflectionNotFoundException, ReflectionAccessException { + Class<?> clazz = loadClass(classname); + try { + return clazz.newInstance(); + } catch (InstantiationException e) { + throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e); + } catch (IllegalAccessException e) { + throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e); + } + } + + /** instantiates the given class from its binary name */ + public Class<?> loadClass(String classname) throws ReflectionNotFoundException { + try { + return classLoader.loadClass(classname); + } catch (ClassNotFoundException e) { + throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e); + } catch (NoClassDefFoundError e) { + throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e); + } catch (UnsupportedClassVersionError e) { + throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e); + } + } + + @SuppressWarnings("unchecked") + public <T> Class<? extends T> loadClass(String classname, Class<T> superType) throws ReflectionNotFoundException { + return (Class<? extends T>) loadClass(classname); + } + + /** given a nested part, e.g. Inner$VeryInner, this will recurse through clazz.Inner, looking for VeryInner, + * then looking in each supertype (interface) of clazz for Inner.VeryInner; + * <p> + * so it will find Clazz.Inner.VeryInner wherever in the hierarchy it is defined + * <p> + * (as opposed to ClassLoader which requires Inner.VeryInner to be _declared_ in clazz, not in any supertype + * <p> + * returns null if not found + */ + public static Class<?> loadInnerClassPossiblyInheritted(Class<?> clazz, String nestedPart) throws ReflectionNotFoundException { + Set<String> visited = new HashSet<String>(); + Class<?> result = loadInnerClassPossiblyInheritted(visited, clazz, nestedPart); + if (result!=null) return result; + throw new ReflectionNotFoundException("Inner class " + nestedPart + " could not be found in " + clazz + " or any of its super-types"); + } + + /** as 2-arg, but maintains set of visited elements, and returns null if not found */ + private static Class<?> loadInnerClassPossiblyInheritted(Set<String> visited, Class<?> clazz, String nestedPart) throws ReflectionNotFoundException { + if (clazz==null) return null; + if (nestedPart==null || nestedPart.length()==0) return clazz; + + int i1 = nestedPart.indexOf('$'); + int i2 = nestedPart.indexOf('.'); + int idx = (i2 > -1 && (i2 < i1 || i1==-1) ? i2 : i1); + String thisClassToFind = nestedPart; + String nextClassesToFind = ""; + if (idx>=0) { + thisClassToFind = nestedPart.substring(0, idx); + nextClassesToFind = nestedPart.substring(idx+1); + } + + if (!visited.add(clazz.getCanonicalName()+"!"+nestedPart)) { + //already visited + return null; + } + + Class<?>[] members = clazz.getClasses(); + for (int i = 0; i < members.length; i++) { + if (members[i].getSimpleName().equals(thisClassToFind)) { + Class<?> clazzI = loadInnerClassPossiblyInheritted(visited, members[i], nextClassesToFind); + if (clazzI!=null) return clazzI; + } + } + + //look in supertype first (not sure if necessary) + Class<?> result = loadInnerClassPossiblyInheritted(visited, clazz.getSuperclass(), nestedPart); + if (result!=null) return result; + + for (Class<?> iface : clazz.getInterfaces()) { + result = loadInnerClassPossiblyInheritted(visited, iface, nestedPart); + if (result!=null) return result; + } + return null; + } + + /** does not look through ancestors of outer class */ + public Class<?> loadInnerClassNotInheritted(String outerClassname, String innerClassname) throws ReflectionNotFoundException { + return loadClass(outerClassname + "$" + innerClassname); + } + + /** does not look through ancestors of outer class + * <p> + * uses the classloader set in this class, not in the clazz supplied */ + public Class<?> loadInnerClassNotInheritted(Class<?> outerClazz, String innerClassname) throws ReflectionNotFoundException { + return loadClass(outerClazz.getName() + "$" + innerClassname); + } + + public Constructor<?> loadConstructor(Class<?> clazz, Class<?>[] argTypes) throws ReflectionAccessException { + try { + return clazz.getConstructor(argTypes); + } catch (SecurityException e) { + throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e); + } catch (NoSuchMethodException e) { + throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e); + } + } + + /** Invokes a suitable constructor, supporting varargs and primitives */ + public static <T> Optional<T> invokeConstructorWithArgs(ClassLoader classLoader, String className, Object...argsArray) { + Reflections reflections = new Reflections(classLoader); + @SuppressWarnings("unchecked") + Class<T> clazz = (Class<T>) reflections.loadClass(className); + return invokeConstructorWithArgs(reflections, clazz, argsArray, false); + } + + /** Invokes a suitable constructor, supporting varargs and primitives */ + public static <T> Optional<T> invokeConstructorWithArgs(ClassLoader classLoader, Class<T> clazz, Object[] argsArray, boolean setAccessible) { + Reflections reflections = new Reflections(classLoader); + return invokeConstructorWithArgs(reflections, clazz, argsArray, setAccessible); + } + + /** Invokes a suitable constructor, supporting varargs and primitives */ + public static <T> Optional<T> invokeConstructorWithArgs(Class<T> clazz, Object...argsArray) { + return invokeConstructorWithArgs(clazz, argsArray, false); + } + + /** Invokes a suitable constructor, supporting varargs and primitives */ + public static <T> Optional<T> invokeConstructorWithArgs(Class<T> clazz, Object[] argsArray, boolean setAccessible) { + Reflections reflections = new Reflections(clazz.getClassLoader()); + return invokeConstructorWithArgs(reflections, clazz, argsArray, setAccessible); + } + + /** Invokes a suitable constructor, supporting varargs and primitives, additionally supporting setAccessible */ + @SuppressWarnings("unchecked") + public static <T> Optional<T> invokeConstructorWithArgs(Reflections reflections, Class<T> clazz, Object[] argsArray, boolean setAccessible) { + for (Constructor<?> constructor : clazz.getConstructors()) { + Class<?>[] parameterTypes = constructor.getParameterTypes(); + if (constructor.isVarArgs()) { + if (typesMatchUpTo(argsArray, parameterTypes, parameterTypes.length-1)) { + Class<?> varargType = parameterTypes[parameterTypes.length-1].getComponentType(); + boolean varargsMatch = true; + for (int i=parameterTypes.length-1; i<argsArray.length; i++) { + if (!Boxing.boxedType(varargType).isInstance(argsArray[i]) || + (varargType.isPrimitive() && argsArray[i]==null)) { + varargsMatch = false; + break; + } + } + if (varargsMatch) { + Object varargs = Array.newInstance(varargType, argsArray.length+1 - parameterTypes.length); + for (int i=parameterTypes.length-1; i<argsArray.length; i++) { + Boxing.setInArray(varargs, i+1-parameterTypes.length, argsArray[i], varargType); + } + Object[] newArgsArray = new Object[parameterTypes.length]; + System.arraycopy(argsArray, 0, newArgsArray, 0, parameterTypes.length-1); + newArgsArray[parameterTypes.length-1] = varargs; + if (setAccessible) constructor.setAccessible(true); + return (Optional<T>) Optional.of(reflections.loadInstance(constructor, newArgsArray)); + } + } + } + if (typesMatch(argsArray, parameterTypes)) { + if (setAccessible) constructor.setAccessible(true); + return (Optional<T>) Optional.of(reflections.loadInstance(constructor, argsArray)); + } + } + return Optional.absent(); + } + + + /** returns a single constructor in a given class, or throws an exception */ + public Constructor<?> loadSingleConstructor(Class<?> clazz) { + Constructor<?>[] constructors = clazz.getConstructors(); + if (constructors.length == 1) { + return constructors[0]; + } + throw new IllegalArgumentException("Class " + clazz + " has more than one constructor"); + } + + public <T> T loadInstance(Constructor<T> constructor, Object...argValues) throws IllegalArgumentException, ReflectionAccessException { + try { + try { + return constructor.newInstance(argValues); + } catch (IllegalArgumentException e) { + try { + LOG.warn("Failure passing provided arguments ("+getIllegalArgumentsErrorMessage(constructor, argValues)+"; "+e+"); attempting to reconstitute"); + argValues = (Object[]) updateFromNewClassLoader(argValues); + return constructor.newInstance(argValues); + } catch (Throwable e2) { + LOG.warn("Reconstitution attempt failed (will rethrow original excaption): "+e2, e2); + throw e; + } + } + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(constructor, argValues)+": " + Exceptions.collapseText(e), e); + } catch (InstantiationException e) { + throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e); + } catch (IllegalAccessException e) { + throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e); + } catch (InvocationTargetException e) { + throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e); + } + } + + public Method loadMethod(Class<?> clazz, String methodName, Class<?>[] argTypes) throws ReflectionNotFoundException, ReflectionAccessException { + try { + return clazz.getMethod(methodName, argTypes); + } catch (NoClassDefFoundError e) { + throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ", using class loader " + clazz.getClassLoader() + ": " + Exceptions.collapseText(e), e); + } catch (NoSuchMethodException e) { + throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e); + } catch (SecurityException e) { + throw new ReflectionAccessException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e); + } + } + + /** returns the first method matching the given name */ + public Method loadMethod(Class<?> clazz, String methodName) throws ReflectionNotFoundException, ReflectionAccessException { + try { + Method[] allmethods = clazz.getMethods(); + for (int i = 0; i < allmethods.length; i++) { + if (allmethods[i].getName().equals(methodName)) { + return allmethods[i]; + } + } + throw new ReflectionNotFoundException("Cannot find method " + methodName + " on class " + clazz); + + } catch (SecurityException e) { + throw new ReflectionAccessException("Failed to invoke method '" + methodName + " on class " + clazz + ": " + Exceptions.collapseText(e), e); + } + } + + /** + * + * @throws ReflectionAccessException If invocation failed due to illegal access or the invoked method failed + * @throws IllegalArgumentException If the arguments were invalid + */ + public Object invokeMethod(Method method, Object obj, Object... argValues) throws ReflectionAccessException { + try { + return method.invoke(obj, argValues); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e); + } catch (IllegalAccessException e) { + throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e); + } catch (InvocationTargetException e) { + throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e); + } + } + + public Object invokeStaticMethod(Method method, Object... argValues) throws IllegalArgumentException, ReflectionAccessException { + try { + return method.invoke(null, argValues); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e); + } catch (IllegalAccessException e) { + throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e); + } catch (InvocationTargetException e) { + throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e); + } + } + + public Object loadStaticField(Class<?> clazz, String fieldname) throws ReflectionAccessException { + return loadStaticFields(clazz, new String[] {fieldname}, null)[0]; + } + + public Object[] loadStaticFields(Class<?> clazz, String[] fieldnamesArray, Object[] defaults) throws ReflectionAccessException { + Object[] result = new Object[fieldnamesArray.length]; + if (defaults!=null) { + for (int i = 0; i < defaults.length; i++) { + result[i] = defaults[i]; + } + } + + List<String> fieldnames = Arrays.asList(fieldnamesArray); + Field[] classFields = clazz.getDeclaredFields(); + + for (int i = 0; i < classFields.length; i++) { + Field field = classFields[i]; + int index = fieldnames.indexOf(field.getName()); + if (index >= 0) { + try { + result[index] = field.get(null); + } catch (IllegalArgumentException e) { + throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e); + } catch (IllegalAccessException e) { + throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e); + } + } + } + return result; + } + + private static String getIllegalArgumentsErrorMessage(Method method, Object[] argValues) { + return method.toGenericString() + " not applicable for the parameters of type " + argumentTypesToString(argValues); + } + + private static String getIllegalArgumentsErrorMessage(Constructor<?> constructor, Object[] argValues) { + return constructor.toGenericString() + " not applicable for the parameters of type " + argumentTypesToString(argValues); + } + + private static String argumentTypesToString(Object[] argValues) { + StringBuffer msg = new StringBuffer("("); + for (int i = 0; i < argValues.length; i++) { + if (i != 0) msg.append(", "); + msg.append(argValues[i] != null ? argValues[i].getClass().getName() : "null"); + } + msg.append(")"); + return msg.toString(); + } + + /** copies all fields from the source to target; very little compile-time safety checking, so use with care + * @throws IllegalAccessException + * @throws IllegalArgumentException */ + public static <T> void copyFields(T source, T target) throws IllegalArgumentException, IllegalAccessException { + Class<? extends Object> clazz = source.getClass(); + while (clazz!=null) { + Field[] fields = clazz.getDeclaredFields(); + for (Field f : fields) { + f.setAccessible(true); + Object vs = f.get(source); + Object vt = f.get(target); + if ((vs==null && vt!=null) || (vs!=null && !vs.equals(vt))) { + f.set(target, vs); + } + } + clazz = clazz.getSuperclass(); + } + } + + /** + * Loads class given its canonical name format (e.g. com.acme.Foo.Inner), + * using iterative strategy (trying com.acme.Foo$Inner, then com.acme$Foo$Inner, etc). + * @throws ReflectionNotFoundException + */ + public Class<?> loadClassFromCanonicalName(String canonicalName) throws ClassNotFoundException, ReflectionNotFoundException { + ClassNotFoundException err = null; + String name = canonicalName; + do { + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException e) { + if (err == null) err = e; + int lastIndexOf = name.lastIndexOf("."); + if (lastIndexOf >= 0) { + name = name.substring(0, lastIndexOf) + "$" + name.substring(lastIndexOf+1); + } + } + } while (name.contains(".")); + throw err; + } + + /** finds the resource in the classloader, if it exists; inserts or replaces leading slash as necessary + * (i believe it should _not_ have one, but there is some inconsistency) + * + * Will return null if no resource is found. + */ + @Nullable + public URL getResource(String r) { + URL u = null; + u = classLoader.getResource(r); + if (u!=null) return u; + + if (r.startsWith("/")) r = r.substring(1); + else r = "/"+r; + return classLoader.getResource(r); + } + + /** + * Serialize the given object, then reload using the current class loader; + * this removes linkages to instances with classes loaded by an older class loader. + * <p> + * (like a poor man's clone) + * <p> + * aka "reconstitute(Object)" + */ + public final Object updateFromNewClassLoader(Object data) throws IOException, ClassNotFoundException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + new ObjectOutputStream(bytes).writeObject(data); + Object reconstituted = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())).readObject(); + if (LOG.isDebugEnabled()) LOG.debug("Reconstituted data: " + reconstituted + ", class loader: " + classLoader); + return reconstituted; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + @SuppressWarnings("unchecked") + public static <T> Class<? super T> findSuperType(T impl, String typeName) { + Set<Class<?>> toinspect = new LinkedHashSet<Class<?>>(); + Set<Class<?>> inspected = new HashSet<Class<?>>(); + toinspect.add(impl.getClass()); + + while (toinspect.size() > 0) { + Class<?> clazz = toinspect.iterator().next(); // get and remove the first element + if (clazz.getName().equals(typeName)) { + return (Class<? super T>) clazz; + } + inspected.add(clazz); + List<Class<?>> toAdd = Arrays.asList(clazz.getInterfaces()); + toinspect.addAll( toAdd ); + if (clazz.getSuperclass() != null) toinspect.add(clazz.getSuperclass()); + toinspect.removeAll(inspected); + } + + return null; + } + + /** whereas Class.getInterfaces() only returns interfaces directly implemented by a class, + * this walks the inheritance hierarchy to include interfaces implemented by superclass/ancestors; + * (note it does not include superinterfaces) + */ + public static Set<Class<?>> getInterfacesIncludingClassAncestors(Class<?> clazz) { + Set<Class<?>> result = new LinkedHashSet<Class<?>>(); + while (clazz!=null) { + for (Class<?> iface: clazz.getInterfaces()) + result.add(iface); + clazz = clazz.getSuperclass(); + } + return result; + } + + public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException { + if (clazz == null || name == null) { + throw new NullPointerException("Must not be null: clazz="+clazz+"; name="+name); + } + Class<?> clazzToInspect = clazz; + NoSuchMethodException toThrowIfFails = null; + + while (clazzToInspect != null) { + try { + return clazzToInspect.getDeclaredMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + if (toThrowIfFails == null) toThrowIfFails = e; + clazzToInspect = clazzToInspect.getSuperclass(); + } + } + throw toThrowIfFails; + } + + public static Field findField(Class<?> clazz, String name) throws NoSuchFieldException { + if (clazz == null || name == null) { + throw new NullPointerException("Must not be null: clazz="+clazz+"; name="+name); + } + Class<?> clazzToInspect = clazz; + NoSuchFieldException toThrowIfFails = null; + + while (clazzToInspect != null) { + try { + return clazzToInspect.getDeclaredField(name); + } catch (NoSuchFieldException e) { + if (toThrowIfFails == null) toThrowIfFails = e; + clazzToInspect = clazzToInspect.getSuperclass(); + } + } + throw toThrowIfFails; + } + + public static List<Field> findPublicFieldsOrderedBySuper(Class<?> clazz) { + checkNotNull(clazz, "clazz"); + MutableList.Builder<Field> result = MutableList.<Field>builder(); + Stack<Class<?>> tovisit = new Stack<Class<?>>(); + Set<Class<?>> visited = Sets.newLinkedHashSet(); + tovisit.push(clazz); + + while (!tovisit.isEmpty()) { + Class<?> nextclazz = tovisit.pop(); + if (!visited.add(nextclazz)) { + continue; // already visited + } + if (nextclazz.getSuperclass() != null) tovisit.add(nextclazz.getSuperclass()); + tovisit.addAll(Arrays.asList(nextclazz.getInterfaces())); + + result.addAll(Iterables.filter(Arrays.asList(nextclazz.getDeclaredFields()), new Predicate<Field>() { + @Override public boolean apply(Field input) { + return Modifier.isPublic(input.getModifiers()); + }})); + + } + + List<Field> resultList = result.build(); + Collections.sort(resultList, new Comparator<Field>() { + @Override public int compare(Field f1, Field f2) { + Field fsubbest = inferSubbestField(f1, f2); + return (fsubbest == null) ? 0 : (fsubbest == f1 ? 1 : -1); + }}); + + return resultList; + } + + // TODO I've seen strange behaviour where class.getMethods() does not include methods from interfaces. + // Also the ordering guarantees here are useful... + public static List<Method> findPublicMethodsOrderedBySuper(Class<?> clazz) { + checkNotNull(clazz, "clazz"); + MutableList.Builder<Method> result = MutableList.<Method>builder(); + Stack<Class<?>> tovisit = new Stack<Class<?>>(); + Set<Class<?>> visited = Sets.newLinkedHashSet(); + tovisit.push(clazz); + + while (!tovisit.isEmpty()) { + Class<?> nextclazz = tovisit.pop(); + if (!visited.add(nextclazz)) { + continue; // already visited + } + if (nextclazz.getSuperclass() != null) tovisit.add(nextclazz.getSuperclass()); + tovisit.addAll(Arrays.asList(nextclazz.getInterfaces())); + + result.addAll(Iterables.filter(Arrays.asList(nextclazz.getDeclaredMethods()), new Predicate<Method>() { + @Override public boolean apply(Method input) { + return Modifier.isPublic(input.getModifiers()); + }})); + + } + + List<Method> resultList = result.build(); + Collections.sort(resultList, new Comparator<Method>() { + @Override public int compare(Method m1, Method m2) { + Method msubbest = inferSubbestMethod(m1, m2); + return (msubbest == null) ? 0 : (msubbest == m1 ? 1 : -1); + }}); + + return resultList; + } + + /** + * Gets the field that is in the sub-class; or null if one field does not come from a sub-class of the other field's class + */ + public static Field inferSubbestField(Field f1, Field f2) { + Class<?> c1 = f1.getDeclaringClass(); + Class<?> c2 = f2.getDeclaringClass(); + boolean isSuper1 = c1.isAssignableFrom(c2); + boolean isSuper2 = c2.isAssignableFrom(c1); + return (isSuper1) ? (isSuper2 ? null : f2) : (isSuper2 ? f1 : null); + } + + /** + * Gets the method that is in the sub-class; or null if one method does not come from a sub-class of the other method's class + */ + public static Method inferSubbestMethod(Method m1, Method m2) { + Class<?> c1 = m1.getDeclaringClass(); + Class<?> c2 = m2.getDeclaringClass(); + boolean isSuper1 = c1.isAssignableFrom(c2); + boolean isSuper2 = c2.isAssignableFrom(c1); + return (isSuper1) ? (isSuper2 ? null : m2) : (isSuper2 ? m1 : null); + } + + /** + * Gets the class that is in the sub-class; or null if neither is a sub-class of the other. + */ + public static Class<?> inferSubbest(Class<?> c1, Class<?> c2) { + boolean isSuper1 = c1.isAssignableFrom(c2); + boolean isSuper2 = c2.isAssignableFrom(c1); + return (isSuper1) ? (isSuper2 ? null : c2) : (isSuper2 ? c1 : null); + } + + /** convenience for casting the given candidate to the given type (without any coercion, and allowing candidate to be null) */ + @SuppressWarnings("unchecked") + public static <T> T cast(Object candidate, Class<? extends T> type) { + if (candidate==null) return null; + if (!type.isAssignableFrom(candidate.getClass())) + throw new IllegalArgumentException("Requires a "+type+", but had a "+candidate.getClass()+" ("+candidate+")"); + return (T)candidate; + } + + /** invokes the given method on the given clazz or instance, doing reasonably good matching on args etc + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws IllegalArgumentException */ + public static Optional<Object> invokeMethodWithArgs(Object clazzOrInstance, String method, List<Object> args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + return invokeMethodWithArgs(clazzOrInstance, method, args, false); + } + public static Optional<Object> invokeMethodWithArgs(Object clazzOrInstance, String method, List<Object> args, boolean setAccessible) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + Preconditions.checkNotNull(clazzOrInstance, "clazz or instance"); + Preconditions.checkNotNull(method, "method"); + Preconditions.checkNotNull(args, "args to "+method); + + Class<?> clazz; + Object instance; + if (clazzOrInstance instanceof Class) { + clazz = (Class<?>)clazzOrInstance; + instance = null; + } else { + clazz = clazzOrInstance.getClass(); + instance = clazzOrInstance; + } + + Object[] argsArray = args.toArray(); + + for (Method m: clazz.getMethods()) { + if (method.equals(m.getName())) { + Class<?>[] parameterTypes = m.getParameterTypes(); + if (m.isVarArgs()) { + if (typesMatchUpTo(argsArray, parameterTypes, parameterTypes.length-1)) { + Class<?> varargType = parameterTypes[parameterTypes.length-1].getComponentType(); + boolean varargsMatch = true; + for (int i=parameterTypes.length-1; i<argsArray.length; i++) { + if (!Boxing.boxedType(varargType).isInstance(argsArray[i]) || + (varargType.isPrimitive() && argsArray[i]==null)) { + varargsMatch = false; + break; + } + } + if (varargsMatch) { + Object varargs = Array.newInstance(varargType, argsArray.length+1 - parameterTypes.length); + for (int i=parameterTypes.length-1; i<argsArray.length; i++) { + Boxing.setInArray(varargs, i+1-parameterTypes.length, argsArray[i], varargType); + } + Object[] newArgsArray = new Object[parameterTypes.length]; + System.arraycopy(argsArray, 0, newArgsArray, 0, parameterTypes.length-1); + newArgsArray[parameterTypes.length-1] = varargs; + if (setAccessible) m.setAccessible(true); + return Optional.of(m.invoke(instance, newArgsArray)); + } + } + } + if (typesMatch(argsArray, parameterTypes)) { + if (setAccessible) m.setAccessible(true); + return Optional.of(m.invoke(instance, argsArray)); + } + } + } + + return Optional.absent(); + } + + /** true iff all args match the corresponding types */ + public static boolean typesMatch(Object[] argsArray, Class<?>[] parameterTypes) { + if (argsArray.length != parameterTypes.length) + return false; + return typesMatchUpTo(argsArray, parameterTypes, argsArray.length); + } + + /** true iff the initial N args match the corresponding types */ + public static boolean typesMatchUpTo(Object[] argsArray, Class<?>[] parameterTypes, int lengthRequired) { + if (argsArray.length < lengthRequired || parameterTypes.length < lengthRequired) + return false; + for (int i=0; i<lengthRequired; i++) { + if (argsArray[i]==null) continue; + if (Boxing.boxedType(parameterTypes[i]).isInstance(argsArray[i])) continue; + return false; + } + return true; + } + + /** + * Gets all the interfaces implemented by the given type, including its parent classes. + * + * @param type the class to look up + * @return an immutable list of the interface classes + */ + public static List<Class<?>> getAllInterfaces(@Nullable Class<?> type) { + Set<Class<?>> found = Sets.newLinkedHashSet(); + findAllInterfaces(type, found); + return ImmutableList.copyOf(found); + } + + /** Recurse through the class hierarchies of the type and its interfaces. */ + private static void findAllInterfaces(@Nullable Class<?> type, Set<Class<?>> found) { + if (type == null) return; + for (Class<?> i : type.getInterfaces()) { + if (found.add(i)) { // not seen before + findAllInterfaces(i, found); + } + } + findAllInterfaces(type.getSuperclass(), found); + } + + public static boolean hasNoArgConstructor(Class<?> clazz) { + try { + clazz.getConstructor(new Class[0]); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + public static boolean hasNoNonObjectFields(Class<? extends Object> clazz) { + if (Object.class.equals(clazz)) return true; + if (clazz.getDeclaredFields().length>0) return false; + return hasNoNonObjectFields(clazz.getSuperclass()); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Serializers.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Serializers.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Serializers.java new file mode 100644 index 0000000..d084674 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Serializers.java @@ -0,0 +1,121 @@ +/* + * 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.brooklyn.util.javalang; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +import org.apache.brooklyn.util.stream.Streams; + +public class Serializers { + + public interface ObjectReplacer { + public static final ObjectReplacer NOOP = new ObjectReplacer() { + @Override public Object replace(Object toserialize) { + return toserialize; + } + @Override public Object resolve(Object todeserialize) { + return todeserialize; + } + }; + + public Object replace(Object toserialize); + public Object resolve(Object todeserialize); + } + + public static <T> T reconstitute(T object) throws IOException, ClassNotFoundException { + return reconstitute(object, ObjectReplacer.NOOP); + } + + public static <T> T reconstitute(T object, ObjectReplacer replacer) throws IOException, ClassNotFoundException { + if (object == null) return null; + return reconstitute(object, object.getClass().getClassLoader(), replacer); + } + + public static <T> T reconstitute(T object, ClassLoader classLoader) throws IOException, ClassNotFoundException { + return reconstitute(object, classLoader, ObjectReplacer.NOOP); + } + + @SuppressWarnings("unchecked") + public static <T> T reconstitute(T object, ClassLoader classLoader, final ObjectReplacer replacer) throws IOException, ClassNotFoundException { + if (object == null) return null; + + class ReconstitutingObjectOutputStream extends ObjectOutputStream { + public ReconstitutingObjectOutputStream(OutputStream outputStream) throws IOException { + super(outputStream); + enableReplaceObject(true); + } + @Override + protected Object replaceObject(Object obj) throws IOException { + return replacer.replace(obj); + } + }; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ReconstitutingObjectOutputStream(baos); + oos.writeObject(object); + oos.close(); + + class ReconstitutingObjectInputStream extends ClassLoaderObjectInputStream { + public ReconstitutingObjectInputStream(InputStream inputStream, ClassLoader classLoader) throws IOException { + super(inputStream, classLoader); + super.enableResolveObject(true); + } + @Override protected Object resolveObject(Object obj) throws IOException { + return replacer.resolve(obj); + } + }; + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ReconstitutingObjectInputStream ois = new ReconstitutingObjectInputStream(bais, classLoader); + try { + return (T) ois.readObject(); + } finally { + Streams.closeQuietly(ois); + } + } + + /** + * Follows pattern in org.apache.commons.io.input.ClassLoaderObjectInputStream + */ + public static class ClassLoaderObjectInputStream extends ObjectInputStream { + + private final ClassLoader classLoader; + + public ClassLoaderObjectInputStream(InputStream inputStream, ClassLoader classLoader) throws IOException { + super(inputStream); + this.classLoader = classLoader; + } + + @Override + protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException { + Class<?> clazz = Class.forName(objectStreamClass.getName(), false, classLoader); + + if (clazz != null) { + return clazz; + } else { + return super.resolveClass(objectStreamClass); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java new file mode 100644 index 0000000..cdfbe1e --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/StackTraceSimplifier.java @@ -0,0 +1,202 @@ +/* + * 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.brooklyn.util.javalang; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableSet; + +/** + * Utility class for cleaning up stacktraces. + */ +public class StackTraceSimplifier { + + private static final Logger log = LoggerFactory.getLogger(StackTraceSimplifier.class); + + /** comma-separated prefixes (not regexes) */ + public static final String DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME = "brooklyn.util.javalang.StackTraceSimplifier.blacklist"; + + /** @deprecated since 0.6.0 use {@link #DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME} */ @Deprecated + public static final String LEGACY_DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME = "groovy.sanitized.stacktraces"; + + private static final Collection<String> DEFAULT_BLACKLIST; + + static { + ImmutableSet.Builder<String> blacklist = ImmutableSet.builder(); + blacklist.addAll(Arrays.asList( + System.getProperty(DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME, + "java.," + + "javax.," + + "sun.," + + "groovy.," + + "org.codehaus.groovy.," + + "gjdk.groovy.," + ).split("(\\s|,)+"))); + + String legacyDefaults = System.getProperty(LEGACY_DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME); + if (Strings.isNonBlank(legacyDefaults)) { + log.warn("Detected ude of legacy system property "+LEGACY_DEFAULT_BLACKLIST_SYSTEM_PROPERTY_NAME); + blacklist.addAll(Arrays.asList(legacyDefaults.split("(\\s|,)+"))); + } + + DEFAULT_BLACKLIST = blacklist.build(); + } + + private static final StackTraceSimplifier DEFAULT_INSTACE = newInstance(); + + private final Collection<String> blacklist; + + protected StackTraceSimplifier() { + this(true); + } + + protected StackTraceSimplifier(boolean includeDefaultBlacklist, String ...packages) { + ImmutableSet.Builder<String> blacklistB = ImmutableSet.builder(); + if (includeDefaultBlacklist) + blacklistB.addAll(DEFAULT_BLACKLIST); + blacklistB.add(packages); + blacklist = blacklistB.build(); + } + + public static StackTraceSimplifier newInstance() { + return new StackTraceSimplifier(); + } + + public static StackTraceSimplifier newInstance(String ...additionalBlacklistPackagePrefixes) { + return new StackTraceSimplifier(true, additionalBlacklistPackagePrefixes); + } + + public static StackTraceSimplifier newInstanceExcludingOnly(String ...blacklistPackagePrefixes) { + return new StackTraceSimplifier(false, blacklistPackagePrefixes); + } + + /** @return whether the given element is useful, that is, not in the blacklist */ + public boolean isUseful(StackTraceElement el) { + for (String s: blacklist){ + if (el.getClassName().startsWith(s)) return false;; + // gets underscores in some contexts ? + if (el.getClassName().replace('_', '.').startsWith(s)) return false; + } + + return true; + } + + /** @return new list containing just the {@link #isUseful(StackTraceElement)} stack trace elements */ + public List<StackTraceElement> clean(Iterable<StackTraceElement> st) { + List<StackTraceElement> result = new LinkedList<StackTraceElement>(); + for (StackTraceElement element: st){ + if (isUseful(element)){ + result.add(element); + } + } + + return result; + } + + /** @return new array containing just the {@link #isUseful(StackTraceElement)} stack trace elements */ + public StackTraceElement[] clean(StackTraceElement[] st) { + List<StackTraceElement> result = clean(Arrays.asList(st)); + return result.toArray(new StackTraceElement[result.size()]); + } + + /** @return first {@link #isUseful(StackTraceElement)} stack trace elements, or null */ + public StackTraceElement firstUseful(StackTraceElement[] st) { + return nthUseful(0, st); + } + + /** @return (n+1)th {@link #isUseful(StackTraceElement)} stack trace elements (ie 0 is {@link #firstUseful(StackTraceElement[])}), or null */ + public StackTraceElement nthUseful(int n, StackTraceElement[] st) { + for (StackTraceElement element: st){ + if (isUseful(element)) { + if (n==0) + return element; + n--; + } + } + return null; + } + + /** {@link #clean(StackTraceElement[])} the given throwable instance, returning the same instance for convenience */ + public <T extends Throwable> T cleaned(T t) { + t.setStackTrace(clean(t.getStackTrace())); + return t; + } + + // ---- statics + + /** static convenience for {@link #isUseful(StackTraceElement)} */ + public static boolean isStackTraceElementUseful(StackTraceElement el) { + return DEFAULT_INSTACE.isUseful(el); + } + + /** static convenience for {@link #clean(Iterable)} */ + public static List<StackTraceElement> cleanStackTrace(Iterable<StackTraceElement> st) { + return DEFAULT_INSTACE.clean(st); + } + + /** static convenience for {@link #clean(StackTraceElement[])} */ + public static StackTraceElement[] cleanStackTrace(StackTraceElement[] st) { + return DEFAULT_INSTACE.clean(st); + } + + /** static convenience for {@link #cleaned(Throwable)} */ + public static <T extends Throwable> T cleanedStackTrace(T t) { + return DEFAULT_INSTACE.cleaned(t); + } + + public static String toString(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + /** returns the number of times the calling method occurs elsewhere in the stack trace; + * 0 if no recursion, 1 if it has cycled three times, etc. */ + @Beta // useful to track down things like https://github.com/apache/incubator-brooklyn/pull/489 + public static int getRecursiveCallCount() { + StackTraceElement[] t = cleanStackTrace(new Throwable().getStackTrace()); + Iterator<StackTraceElement> ti = Arrays.asList(t).iterator(); + ti.next(); + if (!ti.hasNext()) return 0; + // t0 is the caller + StackTraceElement t0 = ti.next(); + String l0 = t0.getClassName()+"."+t0.getMethodName()+"("+t0.getFileName()+")"; + int count = 0; + while (ti.hasNext()) { + StackTraceElement ta = ti.next(); + String li = ta.getClassName()+"."+ta.getMethodName()+"("+ta.getFileName()+")"; + // if we have something in a different method, then something back in the method + // from which the recursive check came, then return true + if (li.equals(l0)) count++; + } + return count; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Threads.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Threads.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Threads.java new file mode 100644 index 0000000..9b5eb79 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Threads.java @@ -0,0 +1,61 @@ +/* + * 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.brooklyn.util.javalang; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Threads { + + private static final Logger log = LoggerFactory.getLogger(Threads.class); + + public static Thread addShutdownHook(final Runnable task) { + Thread t = new Thread("shutdownHookThread") { + public void run() { + try { + task.run(); + } catch (Exception e) { + log.error("Failed to execute shutdownhook", e); + } + } + }; + Runtime.getRuntime().addShutdownHook(t); + return t; + } + + public static boolean removeShutdownHook(Thread hook) { + try { + return Runtime.getRuntime().removeShutdownHook(hook); + } catch (IllegalStateException e) { + // probably shutdown in progress + String text = Exceptions.collapseText(e); + if (text.contains("Shutdown in progress")) { + if (log.isTraceEnabled()) { + log.trace("Could not remove shutdown hook "+hook+": "+text); + } + } else { + log.warn("Could not remove shutdown hook "+hook+": "+text); + log.debug("Shutdown hook removal details: "+e, e); + } + return false; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/logging/LoggingSetup.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/logging/LoggingSetup.java b/utils/common/src/main/java/org/apache/brooklyn/util/logging/LoggingSetup.java new file mode 100644 index 0000000..35467bc --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/logging/LoggingSetup.java @@ -0,0 +1,39 @@ +/* + * 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.brooklyn.util.logging; + +import org.slf4j.bridge.SLF4JBridgeHandler; + +public class LoggingSetup { + + /** bridge java.util.logging messages to slf4j + * <p> + * without this, we get ugly java.util.logging messages on the console and _not_ in the file; + * with this, the excludes rules (which route the common j.u.l categories to the file _only_) + * will apply to j.u.l loggers + * <p> + * typically this is invoked in a static block on a class (in tests and in BrooklynWebServer) + * or could be done on app startup */ + public static void installJavaUtilLoggingBridge() { + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/logging/SimpleOneLineLogFormatter.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/logging/SimpleOneLineLogFormatter.java b/utils/common/src/main/java/org/apache/brooklyn/util/logging/SimpleOneLineLogFormatter.java new file mode 100644 index 0000000..43faf4b --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/logging/SimpleOneLineLogFormatter.java @@ -0,0 +1,140 @@ +/* + * 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.brooklyn.util.logging; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import com.google.common.base.Throwables; + +//useful for java.util.logger, which by default puts every message on two lines (horrid) +//however we don't typically use java.util.logger... +public class SimpleOneLineLogFormatter extends Formatter { + + public SimpleOneLineLogFormatter() { + this(true, false, false); + } + + public SimpleOneLineLogFormatter(boolean showLevel, boolean showThread, boolean showCaller) { + this.showLevel = showLevel; + this.showThread = showThread; + this.showCaller = showCaller; + } + + + public final boolean showLevel; + public final boolean showThread; + public final boolean showCaller; + + // use shared date and formatter to minimize memory/time overhead + protected final Date date = new Date(); + protected DateFormat dateFormat = new SimpleDateFormat(getDateFormat()); + + public String getDateFormat() { + return "yyyy-MM-dd HH:mm:ss.SSSZ"; + } + + /** uses "YYYY-DD-MM hh:mm:ss.SSS message" format */ + public String format(LogRecord record) { + StringBuffer sb = new StringBuffer(); + appendDate(record, sb); + appendLevel(record, sb); + sb.append(" "); + sb.append(formatMessage(record)); + appendThreadAndCaller(record, sb); + appendDetailsWithNewLine(sb, record); + return sb.toString(); + } + + protected void appendLevel(LogRecord record, StringBuffer sb) { + if (showLevel) { + sb.append(" [").append(record.getLevel()).append("]"); + } + } + + protected void appendDate(LogRecord record, StringBuffer sb) { + synchronized (date) { + date.setTime(record.getMillis()); + sb.append(dateFormat.format(date)); + } + } + + protected void appendThreadAndCaller(LogRecord record, StringBuffer sb) { + if (showThread || showCaller) { + sb.append(" ["); + if (showThread) + sb.append(getThreadName(record)); + if (showThread && showCaller) sb.append(", "); + if (showCaller) { + if (record.getSourceClassName() != null) { + sb.append(record.getSourceClassName()); + } else { + sb.append(record.getLoggerName()); + } + if (record.getSourceMethodName() != null) { + sb.append(" "); + sb.append(record.getSourceMethodName()); + } + } + sb.append("]"); + } + } + + protected void appendDetailsWithNewLine(StringBuffer sb, LogRecord record) { + if (record.getThrown() != null) { + try { + sb.append('\n'); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw); + pw.close(); + sb.append(sw.toString()); + } catch (Exception ex) { + //shouldn't happen with printwriter + throw Throwables.propagate(ex); + } + } else { + sb.append('\n'); + } + } + + protected String getThreadName(LogRecord record) { + //try to get the thread's name + //only possible if we are the thread (unless do something messy like cache or access private fields) + //fortunately we typically are the thread + LogRecord lr = new LogRecord(Level.INFO, ""); + if (lr.getThreadID()==record.getThreadID()) + return Thread.currentThread().getName() + " ("+record.getThreadID()+")"; + //otherwise just say the number + return "thread ("+record.getThreadID()+")"; + } + + public static class LogFormatterWithThreadAndCaller extends SimpleOneLineLogFormatter { + public LogFormatterWithThreadAndCaller() { + super(true, true, true); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/math/BitList.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/math/BitList.java b/utils/common/src/main/java/org/apache/brooklyn/util/math/BitList.java new file mode 100644 index 0000000..4784f77 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/math/BitList.java @@ -0,0 +1,271 @@ +/* + * 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.brooklyn.util.math; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import com.google.common.collect.Lists; +import com.google.common.primitives.Bytes; + +/** represents an immutable ordered collection of bits with a known length + * <p> + * when converting to and from bytes and larger numbers, this representation + * uses the least-significant first convention both for bits and for bytes (little endian) + * <p> + * (i.e. least significant byte is first, as is the least significant bit; + * ninth element in this list is the least significant bit in the second byte, + * so a list {0,0,0,0,0,0,0,0,1,0} represents 256) + **/ +public class BitList { + + private final BitSet bits; + protected final int length; + + protected BitList(BitSet bits, int length) { + assert length >= bits.length(); + this.bits = bits; + this.length = length; + } + + public static BitList newInstance(BitSet bits, int length) { + return new BitList(bits, length); + } + + public int length() { + return length; + } + + public boolean get(int index) { + if (index<0 || index>=length) + throw new IndexOutOfBoundsException("Index "+index+" in "+this); + return bits.get(index); + } + + public static BitList newInstance(byte ...bytes) { + BitSet bits = new BitSet(); + for (int i=0; i < bytes.length*8; i++) + if ((bytes[i/8] & (1 << (i%8))) > 0) + bits.set(i); + return newInstance(bits, bytes.length*8); + } + + /** as {@link #newInstance(byte...)}, but accepting ints for convenience; + * only the least significant 8 bits of the parameters are considered */ + public static BitList newInstanceFromBytes(int ...bytes) { + BitSet bits = new BitSet(); + for (int i=0; i < bytes.length*8; i++) + if ((bytes[i/8] & (1 << (i%8))) > 0) + bits.set(i); + return newInstance(bits, bytes.length*8); + } + + public static BitList newInstance(List<Boolean> l) { + BitSet bs = new BitSet(); + for (int i=0; i<l.size(); i++) + bs.set(i, l.get(i)); + return new BitList(bs, l.size()); + } + + public static BitList newInstance(boolean ...l) { + BitSet bs = new BitSet(); + for (int i=0; i<l.length; i++) + bs.set(i, l[i]); + return new BitList(bs, l.length); + } + + public static BitList newInstance(BigInteger x) { + BitSet bs = new BitSet(); + for (int i=0; i<x.bitLength(); i++) + if (x.testBit(i)) bs.set(i); + return new BitList(bs, x.bitLength()); + } + + /** + * returns the bits converted to bytes, with least significant bit first + * *and* first 8 bits in the first byte + * <p> + * NB this may be different to BitSet.valueOf available since java 7 (as late as that!) + * which reverses the order of the bytes */ + public byte[] asBytes() { + byte[] bytes = new byte[(length+7)/8]; + for (int i=0; i<bits.length(); i++) + if (bits.get(i)) + bytes[i/8] |= 1 << (i%8); + return bytes; + } + + public int[] asUnsignedBytes() { + int[] bytes = new int[(length+7)/8]; + for (int i=0; i<bits.length(); i++) + if (bits.get(i)) + bytes[i/8] |= 1 << (i%8); + return bytes; + } + + /** nb: BitSet forgets the length */ + public BitSet asBitSet() { + return (BitSet) bits.clone(); + } + + public List<Boolean> asList() { + List<Boolean> list = new ArrayList<Boolean>(); + for (int i=0; i<length(); i++) { + list.add(get(i)); + } + return list; + } + + /** represents the result of this bit list logically ORred with the other */ + public BitList orred(BitList other) { + BitSet result = asBitSet(); + result.or(other.asBitSet()); + return new BitList(result, Math.max(length, other.length)); + } + + /** represents the result of this bit list logically ANDed with the other */ + public BitList anded(BitList other) { + BitSet result = asBitSet(); + result.and(other.asBitSet()); + return new BitList(result, Math.max(length, other.length)); + } + + /** represents the result of this bit list logically XORred with the other */ + public BitList xorred(BitList other) { + BitSet result = asBitSet(); + result.xor(other.asBitSet()); + return new BitList(result, Math.max(length, other.length)); + } + + /** represents the result of this bit list logically notted */ + public BitList notted() { + BitSet result = asBitSet(); + result.flip(0, length); + return new BitList(result, length); + } + + /** creates a new instance with the given length, either reducing the list or padding it with 0's + * (at the end, in both cases) + */ + public BitList resized(int length) { + BitSet b2 = asBitSet(); + if (b2.length()>length) + b2.clear(length, b2.length()); + return newInstance(b2, length); + } + + public BitList reversed() { + BitSet b = new BitSet(); + for (int from=bits.length()-1, to=length-bits.length(); from>=0; from--, to++) { + if (get(from)) b.set(to); + } + return new BitList(b, length); + } + + public int commonPrefixLength(BitList other) { + int i=0; + while (i<length && i<other.length) { + if (get(i)!=other.get(i)) return i; + i++; + } + return i; + } + + /** true iff the length is 0; see also isZero */ + public boolean isEmpty() { + return length==0; + } + + /** true iff all bits are 0 */ + public boolean isZero() { + return bits.cardinality()==0; + } + + public BigInteger asBigInteger() { + if (length==0) return BigInteger.ZERO; + return new BigInteger(Bytes.toArray(Lists.reverse(asByteList()))); + } + + public boolean[] asArray() { + boolean[] result = new boolean[length]; + for (int i=0; i<length; i++) + result[i] = get(i); + return result; + } + + public List<Byte> asByteList() { + return Bytes.asList(asBytes()); + } + + /** returns value of this as a byte(ignoring any too-high bits) */ + public byte byteValue() { + return asBigInteger().byteValue(); + } + + /** returns value of this as an integer (ignoring any too-high bits) */ + public int intValue() { + return asBigInteger().intValue(); + } + + /** returns value of this as a long (ignoring any too-high bits) */ + public long longValue() { + return asBigInteger().longValue(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((bits == null) ? 0 : bits.hashCode()); + result = prime * result + length; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BitList other = (BitList) obj; + if (bits == null) { + if (other.bits != null) + return false; + } else if (!bits.equals(other.bits)) + return false; + if (length != other.length) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i=0; i<length; i++) { + if (i%8==0 && i>0) sb.append(":"); //for readability + sb.append(get(i) ? '1' : '0'); + } + return sb.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/math/BitUtils.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/math/BitUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/math/BitUtils.java new file mode 100644 index 0000000..9585124 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/math/BitUtils.java @@ -0,0 +1,70 @@ +/* + * 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.brooklyn.util.math; + +public class BitUtils { + + /** reverses the bits in a byte, i.e. 128 = 0b1000000 = bit list {0,0,0,0,0,0,0,1}, + * reversed yields 1 = 0b00000001 = bit list {1,0,0,0,0,0,0,0} */ + public static byte reverseBitSignificance(byte b) { + int result = 0; + for (int i=0; i<8; i++) { + result <<= 1; + if ((b&1)==1) result++; + b >>= 1; + } + return (byte)result; + } + + /** as {@link #reverseBitSignificance(byte)} but accepting int for convenience */ + public static byte reverseBitSignificanceInByte(int b) { + return reverseBitSignificance((byte)b); + } + + /** returns an array of bytes where the bits in each byte have been reversed; + * note however the order of the arguments is not reversed; + * useful e.g. in working with IP address CIDR's */ + public static byte[] reverseBitSignificance(byte ...bytes) { + byte[] result = new byte[bytes.length]; + for (int i=0; i<bytes.length; i++) + result[i] = reverseBitSignificance(bytes[i]); + return result; + } + + /** as {@link #reverseBitSignificance(byte...)}, but taking ints for convenience (ignoring high bits) */ + public static byte[] reverseBitSignificanceInBytes(int ...bytes) { + byte[] result = new byte[bytes.length]; + for (int i=0; i<bytes.length; i++) + result[i] = reverseBitSignificance((byte)bytes[i]); + return result; + } + + /** why oh why are bytes signed! */ + public static int unsigned(byte b) { + if (b<0) return b+256; + return b; + } + + /** returns the value in 0..255 which is equivalent mod 256 */ + public static int unsignedByte(int b) { + if (b<0) return (b%256)+256; + return (b%256); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/math/MathFunctions.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/math/MathFunctions.java b/utils/common/src/main/java/org/apache/brooklyn/util/math/MathFunctions.java new file mode 100644 index 0000000..92dbe36 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/math/MathFunctions.java @@ -0,0 +1,123 @@ +/* + * 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.brooklyn.util.math; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Function; + +public class MathFunctions { + + public static Function<Number, Integer> plus(final int addend) { + return new Function<Number, Integer>() { + public Integer apply(@Nullable Number input) { + if (input==null) return null; + return input.intValue() + addend; + } + }; + } + + public static Function<Number, Long> plus(final long addend) { + return new Function<Number, Long>() { + public Long apply(@Nullable Number input) { + if (input==null) return null; + return input.longValue() + addend; + } + }; + } + + public static Function<Number, Double> plus(final double addend) { + return new Function<Number, Double>() { + public Double apply(@Nullable Number input) { + if (input==null) return null; + return input.doubleValue() + addend; + } + }; + } + + public static Function<Number, Integer> times(final int multiplicand) { + return new Function<Number, Integer>() { + public Integer apply(@Nullable Number input) { + if (input==null) return null; + return input.intValue() * multiplicand; + } + }; + } + + public static Function<Number, Long> times(final long multiplicand) { + return new Function<Number, Long>() { + public Long apply(@Nullable Number input) { + if (input==null) return null; + return input.longValue() * multiplicand; + } + }; + } + + public static Function<Number, Double> times(final double multiplicand) { + return new Function<Number, Double>() { + public Double apply(@Nullable Number input) { + if (input==null) return null; + return input.doubleValue() * multiplicand; + } + }; + } + + public static Function<Number, Double> divide(final double divisor) { + return new Function<Number, Double>() { + public Double apply(@Nullable Number input) { + if (input==null) return null; + return input.doubleValue() / divisor; + } + }; + } + + public static <T> Function<T, Double> divide(final Function<T, ? extends Number> input, final double divisor) { + return new Function<T, Double>() { + public Double apply(@Nullable T input2) { + if (input==null) return null; + Number n = input.apply(input2); + if (n==null) return null; + return n.doubleValue() / divisor; + } + }; + } + + /** returns a string of up to maxLen length (longer in extreme cases) also capped at significantDigits significantDigits */ + public static Function<Number, String> readableString(final int significantDigits, final int maxLen) { + return new Function<Number, String>() { + public String apply(@Nullable Number input) { + if (input==null) return null; + return Strings.makeRealString(input.doubleValue(), maxLen, significantDigits, 0); + } + }; + } + + /** returns a string where the input number is expressed as percent, with given number of significant digits */ + public static Function<Number, String> percent(final int significantDigits) { + return new Function<Number, String>() { + public String apply(@Nullable Number input) { + if (input==null) return null; + return readableString(significantDigits, significantDigits+3).apply(input.doubleValue() * 100d)+"%"; + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/math/MathPredicates.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/math/MathPredicates.java b/utils/common/src/main/java/org/apache/brooklyn/util/math/MathPredicates.java new file mode 100644 index 0000000..e03c379 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/math/MathPredicates.java @@ -0,0 +1,106 @@ +/* + * 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.brooklyn.util.math; + +import javax.annotation.Nullable; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; + +public class MathPredicates { + + /** + * Creates a predicate comparing a given number with {@code val}. + * A number of {@code null} passed to the predicate will always return false. + */ + public static <T extends Number> Predicate<T> greaterThan(final double val) { + return new Predicate<T>() { + public boolean apply(@Nullable T input) { + return (input == null) ? false : input.doubleValue() > val; + } + }; + } + + /** + * Creates a predicate comparing a given number with {@code val}. + * A number of {@code null} passed to the predicate will always return false. + */ + public static <T extends Number> Predicate<T> greaterThanOrEqual(final double val) { + return new Predicate<T>() { + public boolean apply(@Nullable T input) { + return (input == null) ? false : input.doubleValue() >= val; + } + }; + } + + /** + * Creates a predicate comparing a given number with {@code val}. + * A number of {@code null} passed to the predicate will always return false. + */ + public static <T extends Number> Predicate<T> lessThan(final double val) { + return new Predicate<T>() { + public boolean apply(@Nullable T input) { + return (input == null) ? false : input.doubleValue() < val; + } + }; + } + + /** + * Creates a predicate comparing a given number with {@code val}. + * A number of {@code null} passed to the predicate will always return false. + */ + public static <T extends Number> Predicate<T> lessThanOrEqual(final double val) { + return new Predicate<T>() { + public boolean apply(@Nullable T input) { + return (input == null) ? false : input.doubleValue() <= val; + } + }; + } + + /** + * Creates a predicate comparing a given number with {@code val}. + * A number of {@code null} passed to the predicate will always return false. + */ + public static <T extends Number> Predicate<T> equalsApproximately(final Number val, final double delta) { + return new EqualsApproximately<T>(val, delta); + } + /** Convenience for {@link #equalsApproximately(double,double)} with a delta of 10^{-6}. */ + public static <T extends Number> Predicate<T> equalsApproximately(final Number val) { + return equalsApproximately(val, 0.0000001); + } + + private static final class EqualsApproximately<T extends Number> implements Predicate<T> { + private final double val; + private final double delta; + private EqualsApproximately(Number val, double delta) { + this.val = val.doubleValue(); + Preconditions.checkArgument(delta>=0, "delta must be non-negative"); + this.delta = delta; + } + public boolean apply(@Nullable T input) { + return (input == null) ? false : Math.abs(input.doubleValue() - val) <= delta; + } + @Override + public String toString() { + return "equals-approximately("+val+" +- "+delta+")"; + } + } + + +}