org.glassfish.hk2.utilities.reflection.ReflectionHelper Maven / Gradle / Ivy
/*
* Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.hk2.utilities.reflection;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.inject.Named;
import jakarta.inject.Qualifier;
import jakarta.inject.Scope;
import org.glassfish.hk2.utilities.general.GeneralUtilities;
import org.glassfish.hk2.utilities.reflection.internal.MethodWrapperImpl;
/**
* @author jwells
*
*/
public final class ReflectionHelper {
private final static HashSet ESCAPE_CHARACTERS = new HashSet();
private final static char ILLEGAL_CHARACTERS[] = {
'{' , '}', '[', ']', ':', ';', '=', ',', '\\'
};
private final static HashMap REPLACE_CHARACTERS = new HashMap();
static {
for (char illegal : ILLEGAL_CHARACTERS) {
ESCAPE_CHARACTERS.add(illegal);
}
REPLACE_CHARACTERS.put('\n', 'n');
REPLACE_CHARACTERS.put('\r', 'r');
}
private final static String EQUALS_STRING = "=";
private final static String COMMA_STRING = ",";
private final static String QUOTE_STRING = "\"";
/**
* Given the type parameter gets the raw type represented
* by the type, or null if this has no associated raw class
* @param type The type to find the raw class on
* @return The raw class associated with this type
*/
public static Class> getRawClass(Type type) {
if (type == null) return null;
if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
if (!(componentType instanceof ParameterizedType) && !(componentType instanceof Class)) {
// type variable is not supported
return null;
}
Class> rawComponentClass = getRawClass(componentType);
String forNameName = "[L" + rawComponentClass.getName() + ";";
try {
return Class.forName(forNameName);
}
catch (Throwable th) {
// ignore, but return null
return null;
}
}
if (type instanceof Class) {
return (Class>) type;
}
if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class) {
return (Class>) rawType;
}
}
return null;
}
/**
* Resolves the generic type of a field given the actual class being instantiated
*
* @param topclass The instantiation class. Must not be null
* @param field The non-null field whose type to resolve
* @return The resolved field type by way of its subclasses. Will not return
* null, but may return the original fields generic type
*/
public static Type resolveField(Class> topclass, Field field) {
return resolveMember(topclass, field.getGenericType(), field.getDeclaringClass());
}
/**
* Resolves the generic type of a type and declaring class given the actual class being instantiated
*
* @param topclass The instantiation class. Must not be null
* @param lookingForType The type to resolve. Must not be null
* @param declaringClass The class of the entity declaring the lookingForType. Must not be null
* @return The resolved type by way of its subclasses. Will not return null but may
* return lookingForType if it could not be further resolved
*/
public static Type resolveMember(Class> topclass, Type lookingForType, Class> declaringClass) {
Map typeArguments = typesFromSubClassToDeclaringClass(topclass, declaringClass);
if (typeArguments == null) return lookingForType;
if (lookingForType instanceof ParameterizedType) {
return fixTypeVariables((ParameterizedType) lookingForType, typeArguments);
}
if (lookingForType instanceof GenericArrayType) {
return fixGenericArrayTypeVariables((GenericArrayType) lookingForType, typeArguments);
}
if (!(lookingForType instanceof TypeVariable)) {
return lookingForType;
}
TypeVariable> tv = (TypeVariable>) lookingForType;
String typeVariableName = tv.getName();
Type retVal = typeArguments.get(typeVariableName);
if (retVal == null) return lookingForType;
if (retVal instanceof Class) return retVal;
if (retVal instanceof ParameterizedType) {
return fixTypeVariables((ParameterizedType) retVal, typeArguments);
}
if (retVal instanceof GenericArrayType) {
return fixGenericArrayTypeVariables((GenericArrayType) retVal, typeArguments);
}
return retVal;
}
/**
* If you have a class that declares type variables (knownDeclaringClass) and the type the user has told
* us it should be (knownType) then return the replaced type for the given userType. Returns null if
* there is no match for the userType
*
* @param userType The user type to replace. May not be null
* @param knownType The user defined known final type of the knownDeclaringClass. May not be null
* @param knownDeclaringClass The declaringClass for which knownType is the resolved ParameterizedType for it.
* May not be null
* @return null if userType is not related, or the given hardened parameterized type from the knownType list
*/
public static Type resolveKnownType(TypeVariable> userType, ParameterizedType knownType, Class> knownDeclaringClass) {
TypeVariable> knownTypeVariables[] = knownDeclaringClass.getTypeParameters();
for (int lcv = 0; lcv < knownTypeVariables.length; lcv++) {
TypeVariable> knownTypeVariable = knownTypeVariables[lcv];
if (GeneralUtilities.safeEquals(knownTypeVariable.getName(), userType.getName())) {
return knownType.getActualTypeArguments()[lcv];
}
}
return null;
}
private static Map typesFromSubClassToDeclaringClass(Class> topClass, Class> declaringClass) {
if (topClass.equals(declaringClass)) {
return null;
}
Type superType = topClass.getGenericSuperclass();
Class> superClass = getRawClass(superType);
while (superType != null && superClass != null) {
if (!(superType instanceof ParameterizedType)) {
// superType MUST be a Class in this case
if (superClass.equals(declaringClass)) return null;
superType = superClass.getGenericSuperclass();
superClass = ReflectionHelper.getRawClass(superType);
continue;
}
ParameterizedType superPT = (ParameterizedType) superType;
Map typeArguments = getTypeArguments(superClass, superPT);
if (superClass.equals(declaringClass)) {
return typeArguments;
}
superType = superClass.getGenericSuperclass();
superClass = ReflectionHelper.getRawClass(superType);
if (superType instanceof ParameterizedType) {
superType = fixTypeVariables((ParameterizedType) superType, typeArguments);
}
}
throw new AssertionError(topClass.getName() + " is not the same as or a subclass of " + declaringClass.getName());
}
/**
* Gets the first type argument if this is a parameterized
* type, otherwise it returns Object.class
*
* @param type The type to find the first type argument on
* @return If this is a class, Object.class. If this is a parameterized
* type, the type of the first actual argument
*/
public static Type getFirstTypeArgument(Type type) {
if (type instanceof Class) {
return Object.class;
}
if (!(type instanceof ParameterizedType)) return Object.class;
ParameterizedType pt = (ParameterizedType) type;
Type arguments[] = pt.getActualTypeArguments();
if (arguments.length <= 0) return Object.class;
return arguments[0];
}
private static String getNamedName(Named named, Class> implClass) {
String name = named.value();
if (name != null && !name.equals("")) return name;
String cn = implClass.getName();
int index = cn.lastIndexOf(".");
if (index < 0) return cn;
return cn.substring(index + 1);
}
/**
* Returns the name that should be associated with this class
*
* @param implClass The class to evaluate
* @return The name this class should have
*/
public static String getName(Class> implClass) {
Named named = implClass.getAnnotation(Named.class);
String namedName = (named != null) ? getNamedName(named, implClass) : null ;
if (namedName != null) return namedName;
return null;
}
/**
* Gets all the interfaces on this particular class (but not any
* superclasses of this class).
*/
private static void addAllGenericInterfaces(Class> rawClass ,
Type type,
Set closures) {
Map typeArgumentsMap = null;
for (Type currentType : rawClass.getGenericInterfaces()) {
if (type instanceof ParameterizedType &&
currentType instanceof ParameterizedType) {
if (typeArgumentsMap == null ) {
typeArgumentsMap = getTypeArguments(rawClass, (ParameterizedType) type);
}
currentType = fixTypeVariables((ParameterizedType) currentType, typeArgumentsMap);
}
closures.add(currentType);
rawClass = ReflectionHelper.getRawClass(currentType);
if (rawClass != null) {
addAllGenericInterfaces(rawClass, currentType, closures);
}
}
}
/**
* Replace any TypeVariables in the given type's arguments with
* the actual argument types. Return the given type if no replacing
* is required.
*/
private static Type fixTypeVariables(ParameterizedType type,
Map typeArgumentsMap) {
Type[] newTypeArguments = getNewTypeArguments(type, typeArgumentsMap);
if (newTypeArguments != null) {
return new ParameterizedTypeImpl(type.getRawType(), newTypeArguments);
}
return type;
}
/**
* Replace any TypeVariables in the given type's arguments with
* the actual argument types. Return the given type if no replacing
* is required.
*/
private static Type fixGenericArrayTypeVariables(GenericArrayType type,
Map typeArgumentsMap) {
Type newTypeArgument = getNewTypeArrayArguments(type, typeArgumentsMap);
if (newTypeArgument != null) {
if (newTypeArgument instanceof Class>) {
return getArrayOfType((Class>) newTypeArgument);
}
if (newTypeArgument instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) newTypeArgument;
if (pt.getRawType() instanceof Class>) {
return getArrayOfType((Class>) pt.getRawType());
}
}
return new GenericArrayTypeImpl(newTypeArgument);
}
return type;
}
private static Class> getArrayOfType(Class> type) {
return Array.newInstance(type, 0).getClass();
}
/**
* Get a new array of type arguments for the given ParameterizedType, replacing any TypeVariables with
* actual types. The types should be found in the given arguments map, keyed by variable name. Return
* null if no arguments needed to be replaced.
*/
private static Type[] getNewTypeArguments(final ParameterizedType type,
final Map typeArgumentsMap) {
Type[] typeArguments = type.getActualTypeArguments();
Type[] newTypeArguments = new Type[typeArguments.length];
boolean newArgsNeeded = false;
int i = 0;
for (Type argType : typeArguments) {
if (argType instanceof TypeVariable) {
newTypeArguments[i] = typeArgumentsMap.get(((TypeVariable>) argType).getName());
newArgsNeeded = true;
}
else if (argType instanceof ParameterizedType) {
ParameterizedType original = (ParameterizedType) argType;
Type[] internalTypeArgs = getNewTypeArguments(original, typeArgumentsMap);
if (internalTypeArgs != null) {
newTypeArguments[i] = new ParameterizedTypeImpl(original.getRawType(), internalTypeArgs);
newArgsNeeded = true;
}
else {
newTypeArguments[i] = argType;
}
}
else if (argType instanceof GenericArrayType) {
GenericArrayType gat = (GenericArrayType) argType;
Type internalTypeArg = getNewTypeArrayArguments(gat, typeArgumentsMap);
if (internalTypeArg != null) {
if (internalTypeArg instanceof Class>) {
newTypeArguments[i] = getArrayOfType((Class>) internalTypeArg);
newArgsNeeded = true;
}
else if ((internalTypeArg instanceof ParameterizedType) &&
(((ParameterizedType) internalTypeArg).getRawType() instanceof Class>)) {
ParameterizedType pt = (ParameterizedType) internalTypeArg;
newTypeArguments[i] = getArrayOfType((Class>) pt.getRawType());
newArgsNeeded = true;
}
else {
newTypeArguments[i] = new GenericArrayTypeImpl(internalTypeArg);
newArgsNeeded = true;
}
}
else {
newTypeArguments[i] = argType;
}
}
else {
newTypeArguments[i] = argType;
}
i++;
}
return newArgsNeeded ? newTypeArguments : null;
}
/**
* Get a new Type for a GenericArrayType, replacing any TypeVariables with
* actual types. The types should be found in the given arguments map, keyed by variable name. Return
* null if no arguments needed to be replaced.
*/
private static Type getNewTypeArrayArguments(final GenericArrayType gat,
final Map typeArgumentsMap) {
Type typeArgument = gat.getGenericComponentType();
if (typeArgument instanceof TypeVariable) {
return typeArgumentsMap.get(((TypeVariable>) typeArgument).getName());
}
if (typeArgument instanceof ParameterizedType) {
ParameterizedType original = (ParameterizedType) typeArgument;
Type[] internalTypeArgs = getNewTypeArguments(original, typeArgumentsMap);
if (internalTypeArgs != null) {
return new ParameterizedTypeImpl(original.getRawType(), internalTypeArgs);
}
return original;
}
if (typeArgument instanceof GenericArrayType) {
GenericArrayType original = (GenericArrayType) typeArgument;
Type internalTypeArg = getNewTypeArrayArguments(original, typeArgumentsMap);
if (internalTypeArg != null) {
if (internalTypeArg instanceof Class>) {
return getArrayOfType((Class>) internalTypeArg);
}
if (internalTypeArg instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) internalTypeArg;
if (pt.getRawType() instanceof Class>) {
return getArrayOfType((Class>) pt.getRawType());
}
}
return new GenericArrayTypeImpl(internalTypeArg);
}
return null;
}
return null;
}
/**
* Gets a mapping of type variable names of the raw class to type arguments of the
* parameterized type.
*/
private static Map getTypeArguments(Class> rawClass,
ParameterizedType type) {
Map typeMap = new HashMap();
Type[] typeArguments = type.getActualTypeArguments();
int i = 0;
for (TypeVariable> typeVariable : rawClass.getTypeParameters() ) {
typeMap.put(typeVariable.getName(), typeArguments[i++]);
}
return typeMap;
}
/**
* Returns the type closure of the given class
*
* @param ofType The full type closure of the given class
* with nothing omitted (normal case). May not be null
* @return The non-null (and never empty) set of classes
* that this class can be assigned to
*/
private static Set getTypeClosure(Type ofType) {
Set retVal = new HashSet();
Class> rawClass = ReflectionHelper.getRawClass(ofType);
if (rawClass != null) {
Map typeArgumentsMap = null;
Type currentType = ofType;
while (currentType != null && rawClass != null) {
retVal.add(currentType);
addAllGenericInterfaces(rawClass, currentType, retVal);
if (typeArgumentsMap == null && currentType instanceof ParameterizedType){
typeArgumentsMap = getTypeArguments(rawClass, (ParameterizedType) currentType);
}
currentType = rawClass.getGenericSuperclass();
if (currentType != null) {
rawClass = ReflectionHelper.getRawClass(currentType);
if (typeArgumentsMap != null && currentType instanceof ParameterizedType){
currentType = fixTypeVariables((ParameterizedType) currentType, typeArgumentsMap);
}
}
}
}
return retVal;
}
/**
* Returns the type closure, as restricted by the classes listed in the
* set of contracts implemented
*
* @param ofType The type to check
* @param contracts The contracts this type is allowed to handle
* @return The type closure restricted to the contracts
*/
public static Set getTypeClosure(Type ofType, Set contracts) {
Set closure = getTypeClosure(ofType);
HashSet retVal = new HashSet();
for (Type t : closure) {
Class> rawClass = ReflectionHelper.getRawClass(t);
if (rawClass == null) continue;
if (contracts.contains(rawClass.getName())) {
retVal.add(t);
}
}
return retVal;
}
/**
* Returns the set of types this class advertises
* @param type The outer type to analyze
* @param markerAnnotation The annotation to use to discover the advertised types
* @return The type itself and the contracts it implements
*/
public static Set getAdvertisedTypesFromClass(Type type, Class extends Annotation> markerAnnotation) {
Set retVal = new LinkedHashSet();
if (type == null) return retVal;
retVal.add(type);
Class> originalRawClass = getRawClass(type);
if (originalRawClass == null) return retVal;
Type genericSuperclass = originalRawClass.getGenericSuperclass();
while (genericSuperclass != null) {
Class> rawClass = getRawClass(genericSuperclass);
if (rawClass == null) break;
if (rawClass.isAnnotationPresent(markerAnnotation)) {
retVal.add(genericSuperclass);
}
genericSuperclass = rawClass.getGenericSuperclass();
}
Set> alreadyHandled = new HashSet>();
while (originalRawClass != null) {
getAllContractsFromInterfaces(originalRawClass,
markerAnnotation,
retVal,
alreadyHandled);
originalRawClass = originalRawClass.getSuperclass();
}
return retVal;
}
private static void getAllContractsFromInterfaces(Class> clazzOrInterface,
Class extends Annotation> markerAnnotation,
Set addToMe,
Set> alreadyHandled) {
Type interfacesAsType[] = clazzOrInterface.getGenericInterfaces();
for (Type interfaceAsType : interfacesAsType) {
Class> interfaceAsClass = getRawClass(interfaceAsType);
if (interfaceAsClass == null) continue;
if (alreadyHandled.contains(interfaceAsClass)) continue;
alreadyHandled.add(interfaceAsClass);
if (interfaceAsClass.isAnnotationPresent(markerAnnotation)) {
addToMe.add(interfaceAsType);
}
getAllContractsFromInterfaces(interfaceAsClass, markerAnnotation, addToMe, alreadyHandled);
}
}
/**
* Returns the set of types this class advertises
* @param t the object we are analyzing
* @param markerAnnotation The annotation to use to discover the advertised types
* @return The type itself and the contracts it implements
*/
public static Set getAdvertisedTypesFromObject(Object t, Class extends Annotation> markerAnnotation) {
if (t == null) return Collections.emptySet();
return getAdvertisedTypesFromClass(t.getClass(), markerAnnotation);
}
/**
* Returns the set of types this class advertises
* @param clazz the class we are analyzing
* @param markerAnnotation The annotation to use to discover annotated types
* @return The type itself and the contracts it implements
*/
public static Set getContractsFromClass(
final Class> clazz,
final Class extends Annotation> markerAnnotation) {
final Set contractSet = new LinkedHashSet<>();
if (clazz == null) {
return contractSet;
}
contractSet.add(clazz.getName());
for (Class extendsClasses = clazz; extendsClasses != null;
extendsClasses = extendsClasses.getSuperclass()) {
if (extendsClasses.isAnnotationPresent(markerAnnotation)) {
contractSet.add(extendsClasses.getName());
}
recursiveAddContractsFromInterfacesToMap(
extendsClasses.getInterfaces(), contractSet, markerAnnotation);
}
return contractSet;
}
private static void recursiveAddContractsFromInterfacesToMap(
final Class[] interfaces,
final Set contractSet,
final Class extends Annotation> markerAnnotation) {
for (final Class anInterface : interfaces) {
if (anInterface.isAnnotationPresent(markerAnnotation)) {
contractSet.add(anInterface.getName());
}
recursiveAddContractsFromInterfacesToMap
(anInterface.getInterfaces(), contractSet, markerAnnotation);
}
}
/**
* Gets the scope annotation from the object
* @param t The object to analyze
* @return The class of the scope annotation
*/
public static Annotation getScopeAnnotationFromObject(Object t) {
if (t == null) throw new IllegalArgumentException();
return getScopeAnnotationFromClass(t.getClass());
}
/**
* Gets the scope annotation from the object
* @param clazz The class to analyze
* @return The class of the scope annotation
*/
public static Annotation getScopeAnnotationFromClass(Class> clazz) {
if (clazz == null) throw new IllegalArgumentException();
for (Annotation annotation : clazz.getAnnotations()) {
Class extends Annotation> annoClass = annotation.annotationType();
if (annoClass.isAnnotationPresent(Scope.class)) {
return annotation;
}
}
return null;
}
/**
* Gets the scope annotation from the object
* @param t The object to analyze
* @param annoDefault The default that this should have if no scope could be found
* @return The class of the scope annotation
*/
public static Annotation getScopeFromObject(Object t, Annotation annoDefault) {
if (t == null) return annoDefault;
return getScopeFromClass(t.getClass(), annoDefault);
}
/**
* Gets the scope annotation from the object
* @param clazz The class to analyze
* @param annoDefault The scope that should be returned if no scope could be found
* @return The class of the scope annotation
*/
public static Annotation getScopeFromClass(Class> clazz, Annotation annoDefault) {
if (clazz == null) return annoDefault;
for (Annotation annotation : clazz.getAnnotations()) {
Class extends Annotation> annoClass = annotation.annotationType();
if (annoClass.isAnnotationPresent(Scope.class)) {
return annotation;
}
}
return annoDefault;
}
/**
* Returns true if the given annotation is a qualifier
* @param anno The annotation to check
* @return true if this is an annotation
*/
public static boolean isAnnotationAQualifier(Annotation anno) {
Class extends Annotation> annoType = anno.annotationType();
return annoType.isAnnotationPresent(Qualifier.class);
}
/**
* Gets all the qualifiers from the object
*
* @param t The object to analyze
* @return The set of qualifiers. Will not return null but may return an empty set
*/
public static Set getQualifiersFromObject(Object t) {
if (t == null) return Collections.emptySet();
return getQualifierAnnotations(t.getClass());
}
/**
* Gets all the qualifiers from the object
*
* @param clazz The class to analyze
* @return The set of qualifiers. Will not return null but may return an empty set
*/
public static Set getQualifiersFromClass(Class> clazz) {
Set retVal = new LinkedHashSet();
if (clazz == null) return retVal;
for (Annotation annotation : clazz.getAnnotations()) {
if (isAnnotationAQualifier(annotation)) {
retVal.add(annotation.annotationType().getName());
}
}
while (clazz != null) {
for (Class> iFace : clazz.getInterfaces()) {
for (Annotation annotation : iFace.getAnnotations()) {
if (isAnnotationAQualifier(annotation)) {
retVal.add(annotation.annotationType().getName());
}
}
}
clazz = clazz.getSuperclass();
}
return retVal;
}
private static Set internalGetQualifierAnnotations(AnnotatedElement annotatedGuy) {
Set retVal = new LinkedHashSet();
if (annotatedGuy == null) return retVal;
for (Annotation annotation : annotatedGuy.getAnnotations()) {
if (isAnnotationAQualifier(annotation)) {
if ((annotatedGuy instanceof Field) &&
Named.class.equals(annotation.annotationType())) {
Named n = (Named) annotation;
if (n.value() == null || "".equals(n.value())) {
// Because we do not have access to AnnotationLiteral
// we cannot "fix" a Named annotation that has no explicit
// value here, and we must rely on the caller of this
// method to "fix" that case for us
continue;
}
}
retVal.add(annotation);
}
}
if (!(annotatedGuy instanceof Class)) return retVal;
Class> clazz = (Class>) annotatedGuy;
while (clazz != null) {
for (Class> iFace : clazz.getInterfaces()) {
for (Annotation annotation : iFace.getAnnotations()) {
if (isAnnotationAQualifier(annotation)) {
retVal.add(annotation);
}
}
}
clazz = clazz.getSuperclass();
}
return retVal;
}
/**
* Gets all the qualifier annotations from the object
*
* A strange behavior of this method is that if the annotatedGuy is
* a field and that field has the Named annotation on it with no
* value, then that Named annotation will NOT be added to the return
* list. This is because we have no access at this level to
* AnnotationLiteral, and hence cannot create a NamedImpl with which
* to fix the annotation. It is the responsibility of the caller
* of this method to add in the NamedImpl in that circumstance
*
* @param annotatedGuy The thing to analyze
* @return The set of qualifiers. Will not return null but may return an empty set
*/
public static Set getQualifierAnnotations(final AnnotatedElement annotatedGuy) {
Set retVal = AccessController.doPrivileged(new PrivilegedAction>() {
@Override
public Set run() {
return internalGetQualifierAnnotations(annotatedGuy);
}
});
return retVal;
}
/**
* Writes a set in a way that can be read from an input stream as well
*
* @param set The set to write
* @return a representation of a list
*/
public static String writeSet(Set> set) {
return writeSet(set, null);
}
/**
* Writes a set in a way that can be read from an input stream as well
*
* @param set The set to write
* @param excludeMe An object to exclude from the list of things written
* @return a representation of a list
*/
public static String writeSet(Set> set, Object excludeMe) {
if (set == null) return "{}";
StringBuffer sb = new StringBuffer("{");
boolean first = true;
for (Object writeMe : set) {
if (excludeMe != null && excludeMe.equals(writeMe)) {
// Excluded
continue;
}
if (first) {
first = false;
sb.append(escapeString(writeMe.toString()));
}
else {
sb.append("," + escapeString(writeMe.toString()));
}
}
sb.append("}");
return sb.toString();
}
/**
* Writes a set in a way that can be read from an input stream as well. The values in
* the set may not contain the characters "{},"
*
* @param line The line to read
* @param addToMe The set to add the strings to
* @throws IOException On a failure
*/
public static void readSet(String line, Collection addToMe) throws IOException {
char asChars[] = new char[line.length()];
line.getChars(0, line.length(), asChars, 0);
internalReadSet(asChars, 0, addToMe);
}
/**
* Writes a set in a way that can be read from an input stream as well. The values in
* the set may not contain the characters "{},"
*
* @param asChars The line to read
* @param addToMe The set to add the strings to
* @return The number of characters read until the end of the set
* @throws IOException On a failure
*/
private static int internalReadSet(char asChars[], int startIndex, Collection addToMe) throws IOException {
int dot = startIndex;
int startOfSet = -1;
while (dot < asChars.length) {
if (asChars[dot] == '{') {
startOfSet = dot;
dot++;
break;
}
dot++;
}
if (startOfSet == -1) {
throw new IOException("Unknown set format, no initial { character : " + new String(asChars));
}
StringBuffer elementBuffer = new StringBuffer();
int endOfSet = -1;
while (dot < asChars.length) {
char dotChar = asChars[dot];
if (dotChar == '}') {
addToMe.add(elementBuffer.toString());
endOfSet = dot;
break; // Done!
}
if (dotChar == ',') {
// Terminating a single element
addToMe.add(elementBuffer.toString());
elementBuffer = new StringBuffer();
}
else {
// This character is either an escape character or a real character
if (dotChar != '\\') {
elementBuffer.append(dotChar);
}
else {
// This is an escape character
if (dot + 1 >= asChars.length) {
// This is an error, escape at end of buffer
break;
}
dot++; // Moves it forward
dotChar = asChars[dot];
if (dotChar == 'n') {
elementBuffer.append('\n');
}
else if (dotChar == 'r') {
elementBuffer.append('\r');
}
else {
elementBuffer.append(dotChar);
}
}
}
dot++;
}
if (endOfSet == -1) {
throw new IOException("Unknown set format, no ending } character : " + new String(asChars));
}
return dot - startIndex;
}
private static int readKeyStringListLine(char asChars[], int startIndex, Map> addToMe) throws IOException {
int dot = startIndex;
int equalsIndex = -1;
while (dot < asChars.length) {
char dotChar = asChars[dot];
if (dotChar == '=') {
equalsIndex = dot;
break;
}
dot++;
}
if (equalsIndex < 0) {
throw new IOException("Unknown key-string list format, no equals: " + new String(asChars));
}
String key = new String(asChars, startIndex, (equalsIndex - startIndex)); // Does not include the =
dot++; // Move it past the equals
if (dot >= asChars.length) {
// Key with no values, this is illegal
throw new IOException("Found a key with no value, " + key + " in line " + new String(asChars));
}
LinkedList listValues = new LinkedList();
int addOn = internalReadSet(asChars, dot, listValues);
if (!listValues.isEmpty()) {
addToMe.put(key, listValues);
}
dot += addOn + 1;
if (dot < asChars.length) {
char skipComma = asChars[dot];
if (skipComma == ',') {
dot++;
}
}
return dot - startIndex; // The +1 gets us to the next character in the stream
}
/**
* Writes a set in a way that can be read from an input stream as well
* @param line The line to read
* @param addToMe The set to add the strings to
* @throws IOException On a failure
*/
public static void readMetadataMap(String line, Map> addToMe) throws IOException {
char asChars[] = new char[line.length()];
line.getChars(0, line.length(), asChars, 0);
int dot = 0;
while (dot < asChars.length) {
int addMe = readKeyStringListLine(asChars, dot, addToMe);
dot += addMe;
}
}
private static String escapeString(String escapeMe) {
char asChars[] = new char[escapeMe.length()];
escapeMe.getChars(0, escapeMe.length(), asChars, 0);
StringBuffer sb = new StringBuffer();
for (int lcv = 0; lcv < asChars.length; lcv++) {
char candidateChar = asChars[lcv];
if (ESCAPE_CHARACTERS.contains(candidateChar)) {
sb.append('\\');
sb.append(candidateChar);
}
else if (REPLACE_CHARACTERS.containsKey(candidateChar)) {
char replaceWithMe = REPLACE_CHARACTERS.get(candidateChar);
sb.append('\\');
sb.append(replaceWithMe);
}
else {
sb.append(candidateChar);
}
}
return sb.toString();
}
private static String writeList(List list) {
StringBuffer sb = new StringBuffer("{");
boolean first = true;
for (String writeMe : list) {
if (first) {
first = false;
sb.append(escapeString(writeMe));
}
else {
sb.append("," + escapeString(writeMe));
}
}
sb.append("}");
return sb.toString();
}
/**
* Used to write the metadata out
*
* @param metadata The metadata to externalize
* @return The metadata in an externalizable format
*/
public static String writeMetadata(Map> metadata) {
StringBuffer sb = new StringBuffer();
boolean first = true;
for (Map.Entry> entry : metadata.entrySet()) {
if (first) {
first = false;
sb.append(entry.getKey() + '=');
}
else {
sb.append("," + entry.getKey() + '=');
}
sb.append(writeList(entry.getValue()));
}
return sb.toString();
}
/**
* Adds a value to the list of values associated with this key
*
* @param metadatas The base metadata object
* @param key The key to which to add the value. May not be null
* @param value The value to add. May not be null
*/
public static void addMetadata(Map> metadatas, String key, String value) {
if (key == null || value == null) return;
if (key.indexOf('=') >= 0) {
throw new IllegalArgumentException("The key field may not have an = in it:" + key);
}
List inner = metadatas.get(key);
if (inner == null) {
inner = new LinkedList();
metadatas.put(key, inner);
}
inner.add(value);
}
/**
* Removes the given value from the given key
*
* @param metadatas The base metadata object
* @param key The key of the value to remove. May not be null
* @param value The value to remove. May not be null
* @return true if the value was removed
*/
public static boolean removeMetadata(Map> metadatas, String key, String value) {
if (key == null || value == null) return false;
List inner = metadatas.get(key);
if (inner == null) return false;
boolean retVal = inner.remove(value);
if (inner.size() <= 0) metadatas.remove(key);
return retVal;
}
/**
* Removes all the metadata values associated with key
*
* @param metadatas The base metadata object
* @param key The key of the metadata values to remove
* @return true if any value was removed
*/
public static boolean removeAllMetadata(Map> metadatas, String key) {
List values = metadatas.remove(key);
return (values != null && values.size() > 0);
}
/**
* This method does a deep copy of the incoming meta-data, (which basically means we will
* also make copies of the value list)
*
* @param copyMe The guy to copy (if null, null will be returned)
* @return A deep copy of the metadata
*/
public static Map> deepCopyMetadata(Map> copyMe) {
if (copyMe == null) return null;
Map> retVal = new LinkedHashMap>();
for (Map.Entry> entry : copyMe.entrySet()) {
String key = entry.getKey();
if (key.indexOf('=') >= 0) {
throw new IllegalArgumentException("The key field may not have an = in it:" + key);
}
List values = entry.getValue();
LinkedList valuesCopy = new LinkedList();
for (String value : values) {
valuesCopy.add(value);
}
retVal.put(key, valuesCopy);
}
return retVal;
}
/**
* Sets the given field to the given value
*
* @param field The non-null field to set
* @param instance The non-null instance to set into
* @param value The value to which the field should be set
* @throws Throwable If there was some exception while setting the field
*/
public static void setField(Field field, Object instance, Object value) throws Throwable {
setAccessible(field);
try {
field.set(instance, value);
}
catch (IllegalArgumentException e) {
Logger.getLogger().debug(field.getDeclaringClass().getName(), field.getName(), e);
throw e;
}
catch (IllegalAccessException e) {
Logger.getLogger().debug(field.getDeclaringClass().getName(), field.getName(), e);
throw e;
}
}
/**
* This version of invoke is CCL neutral (it will return with the
* same CCL as what it went in with)
*
* @param m the method to invoke
* @param o the object on which to invoke it
* @param args The arguments to invoke (may not be null)
* @param neutralCCL true if the ContextClassLoader shoult remain null with this call
* @return The return from the invocation
* @throws Throwable The unwrapped throwable thrown by the method
*/
public static Object invoke(Object o, Method m, Object args[], boolean neutralCCL)
throws Throwable {
if (isStatic(m)) {
o = null;
}
setAccessible(m);
ClassLoader currentCCL = null;
if (neutralCCL) {
currentCCL = getCurrentContextClassLoader();
}
try {
return m.invoke(o, args);
}
catch (InvocationTargetException ite) {
Throwable targetException = ite.getTargetException();
Logger.getLogger().debug(m.getDeclaringClass().getName(), m.getName(), targetException);
throw targetException;
}
catch (Throwable th) {
Logger.getLogger().debug(m.getDeclaringClass().getName(), m.getName(), th);
throw th;
}
finally {
if (neutralCCL) {
setContextClassLoader(Thread.currentThread(), currentCCL);
}
}
}
/**
* Returns true if the underlying member is static
*
* @param member The non-null member to test
* @return true if the member is static
*/
public static boolean isStatic(Member member) {
int modifiers = member.getModifiers();
return ((modifiers & Modifier.STATIC) != 0);
}
/**
* Sets the context classloader under the privileged of this class
* @param t The thread on which to set the classloader
* @param l The classloader to set
*/
private static void setContextClassLoader(final Thread t, final ClassLoader l) {
AccessController.doPrivileged(new PrivilegedAction