/
ClassLoaderHelper.java
251 lines (235 loc) · 10.4 KB
/
ClassLoaderHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.engine.environment.classpath.spi;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.search.engine.logging.impl.Log;
import org.hibernate.search.util.SearchException;
import org.hibernate.search.util.impl.common.LoggerFactory;
import org.hibernate.search.util.impl.common.StringHelper;
import org.hibernate.search.util.impl.common.Throwables;
/**
* Utility class to load instances of other classes by using a fully qualified name,
* or from a class type.
* Uses reflection and throws SearchException(s) with proper descriptions of the error,
* such as the target class is missing a proper constructor, is an interface, is not found...
*
* @author Sanne Grinovero
* @author Hardy Ferentschik
* @author Ales Justin
*/
public class ClassLoaderHelper {
private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
private ClassLoaderHelper() {
}
/**
* Creates an instance of a target class specified by the fully qualified class name using a {@link ClassLoader}
* as fallback when the class cannot be found in the context one.
*
* @param <T> matches the type of targetSuperType: defines the return type
* @param targetSuperType the return type of the function, the classNameToLoad will be checked
* to be assignable to this type.
* @param classNameToLoad a fully qualified class name, whose type is assignable to targetSuperType
* @param componentDescription a meaningful description of the role the instance will have,
* used to enrich error messages to describe the context of the error
* @param classResolver the {@link ClassResolver} to use to load classes
*
* @return a new instance of the type given by {@code classNameToLoad}
*
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* classNotFound, missing proper constructor, wrong type, security errors.
*/
public static <T> T instanceFromName(Class<T> targetSuperType,
String classNameToLoad,
String componentDescription,
ClassResolver classResolver) {
final Class<?> clazzDef = classForName( classNameToLoad, componentDescription, classResolver );
return instanceFromClass( targetSuperType, clazzDef, componentDescription );
}
/**
* Creates an instance of target class
*
* @param <T> the type of targetSuperType: defines the return type
* @param targetSuperType the created instance will be checked to be assignable to this type
* @param classToLoad the class to be instantiated
* @param componentDescription a role name/description to contextualize error messages
*
* @return a new instance of classToLoad
*
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* missing proper constructor, wrong type, securitymanager errors.
*/
public static <T> T instanceFromClass(Class<T> targetSuperType, Class<?> classToLoad, String componentDescription) {
checkClassType( classToLoad, componentDescription );
final Object instance = untypedInstanceFromClass( classToLoad, componentDescription );
return verifySuperTypeCompatibility( targetSuperType, instance, classToLoad, componentDescription );
}
/**
* Creates an instance of target class. Similar to {@link #instanceFromClass(Class, Class, String)} but not checking
* the created instance will be of any specific type: using {@link #instanceFromClass(Class, Class, String)} should
* be preferred whenever possible.
*
* @param <T> the type of targetSuperType: defines the return type
* @param classToLoad the class to be instantiated
* @param componentDescription a role name/description to contextualize error messages. Ideally should be provided, but it can handle null.
*
* @return a new instance of classToLoad
*
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* missing proper constructor, securitymanager errors.
*/
public static <T> T untypedInstanceFromClass(final Class<T> classToLoad, final String componentDescription) {
checkClassType( classToLoad, componentDescription );
Constructor<?> constructor = getNoArgConstructor( classToLoad, componentDescription );
try {
return (T) constructor.newInstance();
}
catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
if ( StringHelper.isEmpty( componentDescription ) ) {
throw log.unableToInstantiateClass( classToLoad, Throwables.getFirstNonNullMessage( e ), e );
}
else {
throw log.unableToInstantiateComponent(
componentDescription, classToLoad, Throwables.getFirstNonNullMessage( e ), e
);
}
}
}
/**
* Verifies that an object instance is implementing a specific interface, or extending a type.
*
* @param targetSuperType the type to extend, or the interface it should implement
* @param instance the object instance to be verified
* @param classToLoad the Class of the instance
* @param componentDescription a user friendly description of the component represented by the verified instance
*
* @return the same instance
*/
@SuppressWarnings("unchecked")
private static <T> T verifySuperTypeCompatibility(Class<T> targetSuperType, Object instance, Class<?> classToLoad, String componentDescription) {
if ( !targetSuperType.isInstance( instance ) ) {
// have a proper error message according to interface implementation or subclassing
if ( targetSuperType.isInterface() ) {
throw log.interfaceImplementedExpected( componentDescription, classToLoad, targetSuperType );
}
else {
throw log.subtypeExpected( componentDescription, classToLoad, targetSuperType );
}
}
else {
return (T) instance;
}
}
/**
* Creates an instance of target class having a Map of strings as constructor parameter.
* Most of the Analyzer SPIs provided by Lucene have such a constructor.
*
* @param <T> the type of targetSuperType: defines the return type
* @param targetSuperType the created instance will be checked to be assignable to this type
* @param classToLoad the class to be instantiated
* @param componentDescription a role name/description to contextualize error messages
* @param constructorParameter a Map to be passed to the constructor. The loaded type must have such a constructor.
*
* @return a new instance of classToLoad
*
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* missing proper constructor, wrong type, security errors.
*/
public static <T> T instanceFromClass(Class<T> targetSuperType, Class<?> classToLoad, String componentDescription,
Map<String, String> constructorParameter) {
checkClassType( classToLoad, componentDescription );
Constructor<?> singleMapConstructor = getSingleMapConstructor( classToLoad, componentDescription );
if ( constructorParameter == null ) {
constructorParameter = new HashMap<>( 0 );//can't use the emptyMap singleton as it needs to be mutable
}
final Object instance;
try {
instance = singleMapConstructor.newInstance( constructorParameter );
}
catch (Exception e) {
throw log.unableToInstantiateComponent(
componentDescription, classToLoad, Throwables.getFirstNonNullMessage( e ), e
);
}
return verifySuperTypeCompatibility( targetSuperType, instance, classToLoad, componentDescription );
}
private static void checkClassType(Class<?> classToLoad, String componentDescription) {
if ( classToLoad.isInterface() ) {
throw log.implementationRequired( componentDescription, classToLoad );
}
}
/**
* Verifies if target class has a no-args constructor, and that it is
* accessible in current security manager.
* If checks are succesfull, return the constructor; otherwise appropriate exceptions are thrown.
* @param classToLoad the class type to check
* @param componentDescription adds a meaningful description to the type to describe in the error messsage
*/
private static <T> Constructor<T> getNoArgConstructor(Class<T> classToLoad, String componentDescription) {
try {
return classToLoad.getConstructor();
}
catch (SecurityException e) {
throw log.securityManagerLoadingError( componentDescription, classToLoad, e );
}
catch (NoSuchMethodException e) {
throw log.noPublicNoArgConstructor( componentDescription, classToLoad );
}
}
private static Constructor<?> getSingleMapConstructor(Class<?> classToLoad, String componentDescription) {
try {
return classToLoad.getConstructor( Map.class );
}
catch (SecurityException e) {
throw log.securityManagerLoadingError( componentDescription, classToLoad, e );
}
catch (NoSuchMethodException e) {
throw log.missingConstructor( componentDescription, classToLoad );
}
}
public static Class<?> classForName(String classNameToLoad, String componentDescription, ClassResolver classResolver) {
Class<?> clazz;
try {
clazz = classResolver.classForName( classNameToLoad );
}
catch (ClassLoadingException e) {
throw log.unableToFindComponentImplementation( componentDescription, classNameToLoad, e );
}
return clazz;
}
public static <T> Class<? extends T> classForName(Class<T> targetSuperType,
String classNameToLoad,
String componentDescription,
ClassResolver classResolver) {
final Class<?> clazzDef = classForName( classNameToLoad, componentDescription, classResolver );
try {
return clazzDef.asSubclass( targetSuperType );
}
catch (ClassCastException cce) {
throw log.notAssignableImplementation( componentDescription, classNameToLoad, targetSuperType );
}
}
/**
* Perform resolution of a class name.
* <p>
* Here we first check the context classloader, if one, before delegating to
* {@link Class#forName(String, boolean, ClassLoader)} using the caller's classloader
*
* @param classNameToLoad The class name
* @param classResolver The {@link ClassResolver} to use to load classes
*
* @return The class reference.
*
* @throws ClassLoadingException From {@link Class#forName(String, boolean, ClassLoader)}.
*/
public static Class classForName(String classNameToLoad, ClassResolver classResolver) {
return classResolver.classForName( classNameToLoad );
}
}