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