001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2020 University Health Network
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 */
022import java.lang.reflect.*;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.apache.commons.lang3.Validate;
028
029import ca.uhn.fhir.context.ConfigurationException;
030import ca.uhn.fhir.context.support.IContextValidationSupport;
031
032public class ReflectionUtil {
033
034        private static final ConcurrentHashMap<String, Object> ourFhirServerVersions = new ConcurrentHashMap<String, Object>();
035
036        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReflectionUtil.class);
037
038        public static LinkedHashSet<Method> getDeclaredMethods(Class<?> theClazz) {
039                LinkedHashSet<Method> retVal = new LinkedHashSet<Method>();
040                for (Method next : theClazz.getDeclaredMethods()) {
041                        try {
042                                Method method = theClazz.getMethod(next.getName(), next.getParameterTypes());
043                                retVal.add(method);
044                        } catch (NoSuchMethodException e) {
045                                retVal.add(next);
046                        } catch (SecurityException e) {
047                                retVal.add(next);
048                        }
049                }
050                return retVal;
051        }
052
053        public static Class<?> getGenericCollectionTypeOfField(Field next) {
054                ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
055                return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
056        }
057
058        /**
059         * For a field of type List<Enumeration<Foo>>, returns Foo
060         */
061        public static Class<?> getGenericCollectionTypeOfFieldWithSecondOrderForList(Field next) {
062                if (!List.class.isAssignableFrom(next.getType())) {
063                        return getGenericCollectionTypeOfField(next);
064                }
065
066                Class<?> type;
067                ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
068                Type firstArg = collectionType.getActualTypeArguments()[0];
069                if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
070                        ParameterizedType pt = ((ParameterizedType) firstArg);
071                        Type pt2 = pt.getActualTypeArguments()[0];
072                        return (Class<?>) pt2;
073                }
074                type = (Class<?>) firstArg;
075                return type;
076        }
077
078        public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
079                Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
080                if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
081                        return null;
082                }
083                ParameterizedType collectionType = (ParameterizedType) genericParameterType;
084                return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
085        }
086
087        public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) {
088                Type genericReturnType = theMethod.getGenericReturnType();
089                if (!(genericReturnType instanceof ParameterizedType)) {
090                        return null;
091                }
092                ParameterizedType collectionType = (ParameterizedType) genericReturnType;
093                return getGenericCollectionTypeOf(collectionType.getActualTypeArguments()[0]);
094        }
095
096        @SuppressWarnings({ "rawtypes" })
097        private static Class<?> getGenericCollectionTypeOf(Type theType) {
098                Class<?> type;
099                if (ParameterizedType.class.isAssignableFrom(theType.getClass())) {
100                        ParameterizedType pt = ((ParameterizedType) theType);
101                        type = (Class<?>) pt.getRawType();
102                } else if (theType instanceof TypeVariable<?>) {
103                        Type decl = ((TypeVariable) theType).getBounds()[0];
104                        return (Class<?>) decl;
105                } else if (theType instanceof WildcardType) {
106                        Type decl = ((WildcardType) theType).getUpperBounds()[0];
107                        return (Class<?>) decl;
108                } else {
109                        type = (Class<?>) theType;
110                }
111                return type;
112        }
113
114        public static boolean isInstantiable(Class<?> theType) {
115                return !theType.isInterface() && !Modifier.isAbstract(theType.getModifiers());
116        }
117
118        /**
119         * Instantiate a class by no-arg constructor, throw {@link ConfigurationException} if we fail to do so
120         */
121        public static <T> T newInstance(Class<T> theType) {
122                Validate.notNull(theType, "theType must not be null");
123                try {
124                        return theType.getConstructor().newInstance();
125                } catch (Exception e) {
126                        throw new ConfigurationException("Failed to instantiate " + theType.getName(), e);
127                }
128        }
129
130        public static <T> T newInstance(Class<T> theType, Class<?> theArgumentType, Object theArgument) {
131                Validate.notNull(theType, "theType must not be null");
132                try {
133                        Constructor<T> constructor = theType.getConstructor(theArgumentType);
134                        return constructor.newInstance(theArgument);
135                } catch (Exception e) {
136                        throw new ConfigurationException("Failed to instantiate " + theType.getName(), e);
137                }
138        }
139
140        public static Object newInstanceOfFhirServerType(String theType) {
141                String errorMessage = "Unable to instantiate server framework. Please make sure that hapi-fhir-server library is on your classpath!";
142                String wantedType = "ca.uhn.fhir.rest.api.server.IFhirVersionServer";
143                return newInstanceOfType(theType, errorMessage, wantedType);
144        }
145
146        @SuppressWarnings("unchecked")
147        public static <EVS_IN, EVS_OUT, SDT, CST, CDCT, IST> ca.uhn.fhir.context.support.IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST> newInstanceOfFhirProfileValidationSupport(
148                        String theType) {
149                String errorMessage = "Unable to instantiate validation support! Please make sure that hapi-fhir-validation and the appropriate structures JAR are on your classpath!";
150                String wantedType = "ca.uhn.fhir.context.support.IContextValidationSupport";
151                Object fhirServerVersion = newInstanceOfType(theType, errorMessage, wantedType);
152                return (IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>) fhirServerVersion;
153        }
154
155        private static Object newInstanceOfType(String theType, String errorMessage, String wantedType) {
156                Object fhirServerVersion = ourFhirServerVersions.get(theType);
157                if (fhirServerVersion == null) {
158                        try {
159                                Class<?> type = Class.forName(theType);
160                                Class<?> serverType = Class.forName(wantedType);
161                                Validate.isTrue(serverType.isAssignableFrom(type));
162                                fhirServerVersion = type.newInstance();
163                        } catch (Exception e) {
164                                throw new ConfigurationException(errorMessage, e);
165                        }
166
167                        ourFhirServerVersions.put(theType, fhirServerVersion);
168                }
169                return fhirServerVersion;
170        }
171
172        @SuppressWarnings("unchecked")
173        public static <T> T newInstanceOrReturnNull(String theClassName, Class<T> theType) {
174                try {
175                        Class<?> clazz = Class.forName(theClassName);
176                        if (!theType.isAssignableFrom(clazz)) {
177                                throw new ConfigurationException(theClassName + " is not assignable to " + theType);
178                        }
179                        return (T) clazz.newInstance();
180                } catch (ConfigurationException e) {
181                        throw e;
182                } catch (Exception e) {
183                        ourLog.info("Failed to instantiate {}: {}", theClassName, e.toString());
184                        return null;
185                }
186        }
187
188}