package fr.imag.adele.apam.declarations.instrumentation; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.Vector; import fr.imag.adele.apam.declarations.instrumentation.InstrumentedClass; /** * A utility class to obtain information about declared fields and methods. * * @author vega * */ public class LoadedClassMetadata implements InstrumentedClass { /** * The loaded class */ private final Class<?> loadedClass; public LoadedClassMetadata(Class<?> loadedClass) { this.loadedClass = loadedClass; } /** * The list of supported collections for aggregate dependencies */ private final static Class<?>[] supportedCollections = new Class<?>[] { Collection.class, List.class, Vector.class, Set.class }; /** * The list of supported types for push message queues */ private final static Class<?>[] supportedMessageQueues = new Class<?>[] { Queue.class, }; /** * Utility method to get the associated box class name for a primitive type */ private final static Map<String, Class<?>> box = new HashMap<String, Class<?>>(); static { box.put(Boolean.TYPE.getName(), Boolean.class); box.put(Character.TYPE.getName(), Character.class); box.put(Byte.TYPE.getName(), Byte.class); box.put(Short.TYPE.getName(), Short.class); box.put(Integer.TYPE.getName(), Integer.class); box.put(Float.TYPE.getName(), Float.class); box.put(Long.TYPE.getName(), Long.class); box.put(Double.TYPE.getName(), Double.class); } /** * If the type of the specified field is one of the supported collections returns the type of the elements * in the collection, otherwise return null. * * May return {@link #UNKNOWN_TYPE} if the type of the elements in the collection cannot be determined. */ private static String getCollectionType(Field field) { Type fieldType = field.getGenericType(); Class<?> fieldClass = getRawClass(fieldType); if (fieldClass == null) { return null; } /* * First try to see if the field is an array declaration */ if (fieldType instanceof Class && fieldClass.isArray()) { return boxed(fieldClass.getComponentType().getCanonicalName()); } if (fieldType instanceof GenericArrayType) { Type elementType = ((GenericArrayType) fieldType).getGenericComponentType(); if (elementType instanceof Class) { return ((Class<?>) elementType).getCanonicalName(); } else { return UNKNOWN_TYPE; } } /* * Verify if the class of the field is one of the supported collections and get the element type */ for (Class<?> supportedCollection : supportedCollections) { if (supportedCollection.equals(fieldClass)) { Class<?> element = getSingleTypeArgument(fieldType); return element != null ? boxed(element.getCanonicalName()) : UNKNOWN_TYPE; } } /* * If it is not an array or one of the supported collections just return null */ return null; } /** * If the type of the specified field is one of the supported message queues returns the type of the message * data, otherwise return null. * * May return {@link UNKNOWN_TYPE} if the type of the data in the queue cannot be determined. */ private static String getMessageType(Field field) { Type fieldType = field.getGenericType(); Class<?> fieldClass = getRawClass(fieldType); if (fieldClass == null) { return null; } /* * Verify if the class of the field is one of the supported message queues and get the element type */ for (Class<?> supportedMessageQueue : supportedMessageQueues) { if (supportedMessageQueue.equals(fieldClass)) { Class<?> element = getSingleTypeArgument(fieldType); return element != null ? boxed(element.getCanonicalName()) : UNKNOWN_TYPE; } } /* * If it is not one of the supported message queues just return null */ return null; } /** * Utility method to get the raw class of a possibly parameterized type */ private static final Class<?> getRawClass(Type type) { if (type instanceof Class) { return (Class<?>) type; } if (type instanceof ParameterizedType) { return (Class<?>) ((ParameterizedType) type).getRawType(); } return null; } /** * Utility method to get the single type argument of a parameterized type */ private static final Class<?> getSingleTypeArgument(Type type) { if (!(type instanceof ParameterizedType)) { return null; } ParameterizedType parameterizedType = (ParameterizedType) type; Type[] arguments = parameterizedType.getActualTypeArguments(); if ((arguments.length == 1) && (arguments[0] instanceof Class)) { return (Class<?>) arguments[0]; } else { return null; } } private static String boxed(String type) { Class<?> boxed = box.get(type); return boxed != null ? boxed.getCanonicalName() : type; } @Override public String getName() { return loadedClass.getCanonicalName(); } @Override public String getDeclaredFieldType(String fieldName) throws NoSuchFieldException { /* * Try to get reflection information if available,. */ Field fieldReflectionMetadata = null; try { fieldReflectionMetadata = loadedClass.getDeclaredField(fieldName); } catch (Exception e) { } /* * Try to use reflection information */ if (fieldReflectionMetadata != null) { return fieldReflectionMetadata.getType().getCanonicalName(); } throw new NoSuchFieldException("unavailable field " + fieldName); } @Override public String getFieldType(String fieldName) throws NoSuchFieldException { /* * Try to get reflection information if available,. */ Field fieldReflectionMetadata = null; try { fieldReflectionMetadata = loadedClass.getDeclaredField(fieldName); } catch (Exception e) { } /* * Try to use reflection information */ if (fieldReflectionMetadata != null) { /* * Verify if it is a collection */ String collectionType = getCollectionType(fieldReflectionMetadata); if (collectionType != null) { return collectionType; } /* * Verify if it is a message */ String messageType = getMessageType(fieldReflectionMetadata); if (messageType != null) { return messageType; } /* * Otherwise we just get the raw type */ return fieldReflectionMetadata.getType().getCanonicalName(); } throw new NoSuchFieldException("unavailable field " + fieldName); } @Override public int getMethodParameterNumber(String methodName, boolean includeInherited) throws NoSuchMethodException { for (Method method : includeInherited ? loadedClass.getMethods() : loadedClass.getDeclaredMethods()) { if (!method.getName().equals(methodName)) { continue; } return method.getParameterTypes().length; } throw new NoSuchMethodException("unavailable metadata for method " + methodName); } @Override public String getMethodParameterType(String methodName, boolean includeInherited) throws NoSuchMethodException { Method methodReflectionMetadata = null; for (Method method : includeInherited ? loadedClass.getMethods() : loadedClass.getDeclaredMethods()) { if (!method.getName().equals(methodName)) { continue; } Class<?> parameters[] = method.getParameterTypes(); boolean match = (1 == parameters.length); if (match) { methodReflectionMetadata = method; } } if (methodReflectionMetadata != null) { return boxed(methodReflectionMetadata.getParameterTypes()[0].getCanonicalName()); } throw new NoSuchMethodException("unavailable metadata for method " + methodName); } @Override public String[] getMethodParameterTypes(String methodName, boolean includeInherited) throws NoSuchMethodException { List<String> signature = new ArrayList<String>(); for (Method method : includeInherited ? loadedClass.getMethods() : loadedClass.getDeclaredMethods()) { if (!method.getName().equals(methodName)) { continue; } for (Class<?> parameterType : method.getParameterTypes()) { signature.add(boxed(parameterType.getCanonicalName())); } return signature.toArray(new String[0]); } throw new NoSuchMethodException("unavailable metadata for method " + methodName); } @Override public String getMethodReturnType(String methodName, String methodSignature, boolean includeInherited) throws NoSuchMethodException { Method methodReflectionMetadata = null; for (Method method : includeInherited ? loadedClass.getMethods() : loadedClass.getDeclaredMethods()) { if (!method.getName().equals(methodName)) { continue; } if (methodSignature == null) { methodReflectionMetadata = method; break; } String signature[] = methodSignature.split(","); Class<?> parameters[] = method.getParameterTypes(); boolean match = (signature.length == parameters.length); for (int i = 0; match && i < signature.length; i++) { if (!signature[i].equals(parameters[i].getCanonicalName())) { match = false; } } if (match) { methodReflectionMetadata = method; break; } } if (methodReflectionMetadata != null) { return boxed(methodReflectionMetadata.getReturnType().getCanonicalName()); } throw new NoSuchMethodException("unavailable metadata for method " + methodName + "(" + methodSignature != null ? methodSignature : "" + ")"); } @Override public boolean isCollectionField(String fieldName) throws NoSuchFieldException { /* * Try to get reflection information if available,. */ Field fieldReflectionMetadata = null; try { fieldReflectionMetadata = loadedClass.getDeclaredField(fieldName); } catch (Exception ignored) { } if (fieldReflectionMetadata != null) { return getCollectionType(fieldReflectionMetadata) != null; } throw new NoSuchFieldException("unavailable metadata for field " + fieldName); } @Override public boolean isMessageQueueField(String fieldName) throws NoSuchFieldException { /* * Try to get reflection information if available,. */ Field fieldReflectionMetadata = null; try { fieldReflectionMetadata = loadedClass.getDeclaredField(fieldName); } catch (Exception ignored) { } if (fieldReflectionMetadata != null) { return getMessageType(fieldReflectionMetadata) != null; } throw new NoSuchFieldException("unavailable metadata for field " + fieldName); } }