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}