001package ca.uhn.fhir.util; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.ConfigurationException; 024import org.apache.commons.lang3.Validate; 025 026import java.lang.reflect.Constructor; 027import java.lang.reflect.Field; 028import java.lang.reflect.Method; 029import java.lang.reflect.Modifier; 030import java.lang.reflect.ParameterizedType; 031import java.lang.reflect.Type; 032import java.lang.reflect.TypeVariable; 033import java.lang.reflect.WildcardType; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Comparator; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.LinkedHashSet; 040import java.util.List; 041import java.util.concurrent.ConcurrentHashMap; 042 043public class ReflectionUtil { 044 045 public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 046 public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0]; 047 private static final ConcurrentHashMap<String, Object> ourFhirServerVersions = new ConcurrentHashMap<>(); 048 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReflectionUtil.class); 049 050 /** 051 * Non instantiable 052 */ 053 private ReflectionUtil() { 054 super(); 055 } 056 057 /** 058 * Returns all methods declared against {@literal theClazz}. This method returns a predictable order, which is 059 * sorted by method name and then by parameters. 060 * <p> 061 * This method does not include superclass methods (see {@link #getDeclaredMethods(Class, boolean)} if you 062 * want to include those. 063 * </p> 064 */ 065 public static List<Method> getDeclaredMethods(Class<?> theClazz) { 066 return getDeclaredMethods(theClazz, false); 067 } 068 069 /** 070 * Returns all methods declared against {@literal theClazz}. This method returns a predictable order, which is 071 * sorted by method name and then by parameters. 072 */ 073 public static List<Method> getDeclaredMethods(Class<?> theClazz, boolean theIncludeMethodsFromSuperclasses) { 074 HashMap<String, Method> foundMethods = new HashMap<>(); 075 076 populateDeclaredMethodsMap(theClazz, foundMethods, theIncludeMethodsFromSuperclasses); 077 078 List<Method> retVal = new ArrayList<>(foundMethods.values()); 079 retVal.sort((Comparator.comparing(ReflectionUtil::describeMethodInSortFriendlyWay))); 080 return retVal; 081 } 082 083 private static void populateDeclaredMethodsMap(Class<?> theClazz, HashMap<String, Method> foundMethods, boolean theIncludeMethodsFromSuperclasses) { 084 Method[] declaredMethods = theClazz.getDeclaredMethods(); 085 for (Method next : declaredMethods) { 086 087 if (Modifier.isAbstract(next.getModifiers()) || 088 Modifier.isStatic(next.getModifiers()) || 089 Modifier.isPrivate(next.getModifiers())) { 090 continue; 091 } 092 093 String description = next.getName() + Arrays.asList(next.getParameterTypes()); 094 095 if (!foundMethods.containsKey(description)) { 096 try { 097 Method method = theClazz.getMethod(next.getName(), next.getParameterTypes()); 098 foundMethods.put(description, method); 099 } catch (NoSuchMethodException | SecurityException e) { 100 foundMethods.put(description, next); 101 } 102 } 103 } 104 105 if (theIncludeMethodsFromSuperclasses && !theClazz.getSuperclass().equals(Object.class)) { 106 populateDeclaredMethodsMap(theClazz.getSuperclass(), foundMethods, theIncludeMethodsFromSuperclasses); 107 } 108 } 109 110 /** 111 * Returns a description like <code>startsWith params(java.lang.String, int) returns(boolean)</code>. 112 * The format is chosen in order to provide a predictable and useful sorting order. 113 */ 114 public static String describeMethodInSortFriendlyWay(Method theMethod) { 115 StringBuilder b = new StringBuilder(); 116 b.append(theMethod.getName()); 117 b.append(" returns("); 118 b.append(theMethod.getReturnType().getName()); 119 b.append(") params("); 120 Class<?>[] parameterTypes = theMethod.getParameterTypes(); 121 for (int i = 0, parameterTypesLength = parameterTypes.length; i < parameterTypesLength; i++) { 122 if (i > 0) { 123 b.append(", "); 124 } 125 Class<?> next = parameterTypes[i]; 126 b.append(next.getName()); 127 } 128 b.append(")"); 129 return b.toString(); 130 } 131 132 public static Class<?> getGenericCollectionTypeOfField(Field next) { 133 ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); 134 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 135 } 136 137 /** 138 * For a field of type List<Enumeration<Foo>>, returns Foo 139 */ 140 public static Class<?> getGenericCollectionTypeOfFieldWithSecondOrderForList(Field next) { 141 if (!List.class.isAssignableFrom(next.getType())) { 142 return getGenericCollectionTypeOfField(next); 143 } 144 145 Class<?> type; 146 ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); 147 Type firstArg = collectionType.getActualTypeArguments()[0]; 148 if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) { 149 ParameterizedType pt = ((ParameterizedType) firstArg); 150 Type pt2 = pt.getActualTypeArguments()[0]; 151 return (Class<?>) pt2; 152 } 153 type = (Class<?>) firstArg; 154 return type; 155 } 156 157 public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) { 158 Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex]; 159 if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) { 160 return null; 161 } 162 ParameterizedType collectionType = (ParameterizedType) genericParameterType; 163 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 164 } 165 166 public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) { 167 Type genericReturnType = theMethod.getGenericReturnType(); 168 if (!(genericReturnType instanceof ParameterizedType)) { 169 return null; 170 } 171 ParameterizedType collectionType = (ParameterizedType) genericReturnType; 172 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 173 } 174 175 @SuppressWarnings({"rawtypes"}) 176 private static Class<?> getGenericCollectionTypeOf(Type theType) { 177 Class<?> type; 178 if (ParameterizedType.class.isAssignableFrom(theType.getClass())) { 179 ParameterizedType pt = ((ParameterizedType) theType); 180 type = (Class<?>) pt.getRawType(); 181 } else if (theType instanceof TypeVariable<?>) { 182 Type decl = ((TypeVariable) theType).getBounds()[0]; 183 return (Class<?>) decl; 184 } else if (theType instanceof WildcardType) { 185 Type decl = ((WildcardType) theType).getUpperBounds()[0]; 186 return (Class<?>) decl; 187 } else { 188 type = (Class<?>) theType; 189 } 190 return type; 191 } 192 193 public static boolean isInstantiable(Class<?> theType) { 194 return !theType.isInterface() && !Modifier.isAbstract(theType.getModifiers()); 195 } 196 197 /** 198 * Instantiate a class by no-arg constructor, throw {@link ConfigurationException} if we fail to do so 199 */ 200 public static <T> T newInstance(Class<T> theType) { 201 Validate.notNull(theType, "theType must not be null"); 202 try { 203 return theType.getConstructor().newInstance(); 204 } catch (Exception e) { 205 throw new ConfigurationException("Failed to instantiate " + theType.getName(), e); 206 } 207 } 208 209 public static <T> T newInstance(Class<T> theType, Class<?> theArgumentType, Object theArgument) { 210 Validate.notNull(theType, "theType must not be null"); 211 try { 212 Constructor<T> constructor = theType.getConstructor(theArgumentType); 213 return constructor.newInstance(theArgument); 214 } catch (Exception e) { 215 throw new ConfigurationException("Failed to instantiate " + theType.getName(), e); 216 } 217 } 218 219 public static Object newInstanceOfFhirServerType(String theType) { 220 String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!"; 221 String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer"; 222 return newInstanceOfType(theType, theType, errorMessage, wantedType, new Class[0], new Object[0]); 223 } 224 225 private static Object newInstanceOfType(String theKey, String theType, String errorMessage, String wantedType, Class<?>[] theParameterArgTypes, Object[] theConstructorArgs) { 226 Object fhirServerVersion = ourFhirServerVersions.get(theKey); 227 if (fhirServerVersion == null) { 228 try { 229 Class<?> type = Class.forName(theType); 230 Class<?> serverType = Class.forName(wantedType); 231 Validate.isTrue(serverType.isAssignableFrom(type)); 232 fhirServerVersion = type.getConstructor(theParameterArgTypes).newInstance(theConstructorArgs); 233 } catch (Exception e) { 234 throw new ConfigurationException(errorMessage, e); 235 } 236 237 ourFhirServerVersions.put(theKey, fhirServerVersion); 238 } 239 return fhirServerVersion; 240 } 241 242 public static <T> T newInstanceOrReturnNull(String theClassName, Class<T> theType) { 243 return newInstanceOrReturnNull(theClassName, theType, EMPTY_CLASS_ARRAY, EMPTY_OBJECT_ARRAY); 244 } 245 246 @SuppressWarnings("unchecked") 247 public static <T> T newInstanceOrReturnNull(String theClassName, Class<T> theType, Class<?>[] theArgTypes, Object[] theArgs) { 248 try { 249 Class<?> clazz = Class.forName(theClassName); 250 if (!theType.isAssignableFrom(clazz)) { 251 throw new ConfigurationException(theClassName + " is not assignable to " + theType); 252 } 253 return (T) clazz.getConstructor(theArgTypes).newInstance(theArgs); 254 } catch (ConfigurationException e) { 255 throw e; 256 } catch (Exception e) { 257 ourLog.info("Failed to instantiate {}: {}", theClassName, e.toString()); 258 return null; 259 } 260 } 261 262 public static boolean typeExists(String theName) { 263 try { 264 Class.forName(theName); 265 return true; 266 } catch (ClassNotFoundException theE) { 267 return false; 268 } 269 } 270}