001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 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 org.apache.commons.lang3.Validate; 026 027import javax.annotation.Nonnull; 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(Class<?> theClazz, HashMap<String, Method> foundMethods, boolean theIncludeMethodsFromSuperclasses) { 085 Method[] declaredMethods = theClazz.getDeclaredMethods(); 086 for (Method next : declaredMethods) { 087 088 if (Modifier.isAbstract(next.getModifiers()) || 089 Modifier.isStatic(next.getModifiers()) || 090 Modifier.isPrivate(next.getModifiers())) { 091 continue; 092 } 093 094 String description = next.getName() + Arrays.asList(next.getParameterTypes()); 095 096 if (!foundMethods.containsKey(description)) { 097 try { 098 Method method = theClazz.getMethod(next.getName(), next.getParameterTypes()); 099 foundMethods.put(description, method); 100 } catch (NoSuchMethodException | SecurityException e) { 101 foundMethods.put(description, next); 102 } 103 } 104 } 105 106 if (theIncludeMethodsFromSuperclasses && !theClazz.getSuperclass().equals(Object.class)) { 107 populateDeclaredMethodsMap(theClazz.getSuperclass(), foundMethods, theIncludeMethodsFromSuperclasses); 108 } 109 } 110 111 /** 112 * Returns a description like <code>startsWith params(java.lang.String, int) returns(boolean)</code>. 113 * The format is chosen in order to provide a predictable and useful sorting order. 114 */ 115 public static String describeMethodInSortFriendlyWay(Method theMethod) { 116 StringBuilder b = new StringBuilder(); 117 b.append(theMethod.getName()); 118 b.append(" returns("); 119 b.append(theMethod.getReturnType().getName()); 120 b.append(") params("); 121 Class<?>[] parameterTypes = theMethod.getParameterTypes(); 122 for (int i = 0, parameterTypesLength = parameterTypes.length; i < parameterTypesLength; i++) { 123 if (i > 0) { 124 b.append(", "); 125 } 126 Class<?> next = parameterTypes[i]; 127 b.append(next.getName()); 128 } 129 b.append(")"); 130 return b.toString(); 131 } 132 133 public static Class<?> getGenericCollectionTypeOfField(Field next) { 134 ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); 135 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 136 } 137 138 /** 139 * For a field of type List<Enumeration<Foo>>, returns Foo 140 */ 141 public static Class<?> getGenericCollectionTypeOfFieldWithSecondOrderForList(Field next) { 142 if (!List.class.isAssignableFrom(next.getType())) { 143 return getGenericCollectionTypeOfField(next); 144 } 145 146 Class<?> type; 147 ParameterizedType collectionType = (ParameterizedType) next.getGenericType(); 148 Type firstArg = collectionType.getActualTypeArguments()[0]; 149 if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) { 150 ParameterizedType pt = ((ParameterizedType) firstArg); 151 Type pt2 = pt.getActualTypeArguments()[0]; 152 return (Class<?>) pt2; 153 } 154 type = (Class<?>) firstArg; 155 return type; 156 } 157 158 public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) { 159 Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex]; 160 if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) { 161 return null; 162 } 163 ParameterizedType collectionType = (ParameterizedType) genericParameterType; 164 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 165 } 166 167 public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) { 168 Type genericReturnType = theMethod.getGenericReturnType(); 169 if (!(genericReturnType instanceof ParameterizedType)) { 170 return null; 171 } 172 ParameterizedType collectionType = (ParameterizedType) genericReturnType; 173 return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]); 174 } 175 176 @SuppressWarnings({"rawtypes"}) 177 private static Class<?> getGenericCollectionTypeOf(Type theType) { 178 Class<?> type; 179 if (ParameterizedType.class.isAssignableFrom(theType.getClass())) { 180 ParameterizedType pt = ((ParameterizedType) theType); 181 type = (Class<?>) pt.getRawType(); 182 } else if (theType instanceof TypeVariable<?>) { 183 Type decl = ((TypeVariable) theType).getBounds()[0]; 184 return (Class<?>) decl; 185 } else if (theType instanceof WildcardType) { 186 Type decl = ((WildcardType) theType).getUpperBounds()[0]; 187 return (Class<?>) decl; 188 } else { 189 type = (Class<?>) theType; 190 } 191 return type; 192 } 193 194 public static boolean isInstantiable(Class<?> theType) { 195 return !theType.isInterface() && !Modifier.isAbstract(theType.getModifiers()); 196 } 197 198 /** 199 * Instantiate a class by no-arg constructor, throw {@link ConfigurationException} if we fail to do so 200 */ 201 public static <T> T newInstance(Class<T> theType) { 202 Validate.notNull(theType, "theType must not be null"); 203 try { 204 return theType.getConstructor().newInstance(); 205 } catch (Exception e) { 206 throw new ConfigurationException(Msg.code(1784) + "Failed to instantiate " + theType.getName(), e); 207 } 208 } 209 210 public static <T> T newInstance(Class<T> theType, Class<?> theArgumentType, Object theArgument) { 211 Validate.notNull(theType, "theType must not be null"); 212 try { 213 Constructor<T> constructor = theType.getConstructor(theArgumentType); 214 return constructor.newInstance(theArgument); 215 } catch (Exception e) { 216 throw new ConfigurationException(Msg.code(1785) + "Failed to instantiate " + theType.getName(), e); 217 } 218 } 219 220 public static Object newInstanceOfFhirServerType(String theType) { 221 String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!"; 222 String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer"; 223 return newInstanceOfType(theType, theType, errorMessage, wantedType, new Class[0], new Object[0]); 224 } 225 226 private static Object newInstanceOfType(String theKey, String theType, String errorMessage, String wantedType, Class<?>[] theParameterArgTypes, Object[] theConstructorArgs) { 227 Object fhirServerVersion = ourFhirServerVersions.get(theKey); 228 if (fhirServerVersion == null) { 229 try { 230 Class<?> type = Class.forName(theType); 231 Class<?> serverType = Class.forName(wantedType); 232 Validate.isTrue(serverType.isAssignableFrom(type)); 233 fhirServerVersion = type.getConstructor(theParameterArgTypes).newInstance(theConstructorArgs); 234 } catch (Exception e) { 235 throw new ConfigurationException(Msg.code(1786) + errorMessage, e); 236 } 237 238 ourFhirServerVersions.put(theKey, fhirServerVersion); 239 } 240 return fhirServerVersion; 241 } 242 243 public static <T> T newInstanceOrReturnNull(String theClassName, Class<T> theType) { 244 return newInstanceOrReturnNull(theClassName, theType, EMPTY_CLASS_ARRAY, EMPTY_OBJECT_ARRAY); 245 } 246 247 @SuppressWarnings("unchecked") 248 public static <T> T newInstanceOrReturnNull(String theClassName, Class<T> theType, Class<?>[] theArgTypes, Object[] theArgs) { 249 try { 250 return newInstance(theClassName, theType, theArgTypes, theArgs); 251 } catch (ConfigurationException e) { 252 throw e; 253 } catch (Exception e) { 254 ourLog.info("Failed to instantiate {}: {}", theClassName, e.toString()); 255 return null; 256 } 257 } 258 259 @Nonnull 260 public static <T> T newInstance(String theClassName, Class<T> theType, Class<?>[] theArgTypes, Object[] theArgs) { 261 try { 262 Class<?> clazz = Class.forName(theClassName); 263 if (!theType.isAssignableFrom(clazz)) { 264 throw new ConfigurationException(Msg.code(1787) + theClassName + " is not assignable to " + theType); 265 } 266 return (T) clazz.getConstructor(theArgTypes).newInstance(theArgs); 267 } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | 268 InvocationTargetException e) { 269 throw new InternalErrorException(Msg.code(2330) + e.getMessage(), e); 270 } 271 } 272 273 public static boolean typeExists(String theName) { 274 try { 275 Class.forName(theName); 276 return true; 277 } catch (ClassNotFoundException theE) { 278 return false; 279 } 280 } 281}