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