-
Notifications
You must be signed in to change notification settings - Fork 73
/
Instantiator.java
148 lines (129 loc) · 5.14 KB
/
Instantiator.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
package nl.jqno.equalsverifier.internal.reflection;
import static nl.jqno.equalsverifier.internal.reflection.Util.classForName;
import static nl.jqno.equalsverifier.internal.reflection.Util.classes;
import static nl.jqno.equalsverifier.internal.reflection.Util.objects;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.function.UnaryOperator;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import nl.jqno.equalsverifier.internal.util.ObjenesisWrapper;
/**
* Instantiates objects of a given class.
*
* @param <T> {@link Instantiator} instantiates objects of this class, or of an anonymous subclass
* of this class.
*/
public final class Instantiator<T> {
private static final List<String> FORBIDDEN_PACKAGES = Arrays.asList(
"java.",
"javax.",
"sun.",
"com.sun.",
"org.w3c.dom."
);
private static final String FALLBACK_PACKAGE_NAME = getPackageName(Instantiator.class);
private final Class<T> type;
/** Private constructor. Call {@link #of(Class)} to instantiate. */
private Instantiator(Class<T> type) {
this.type = type;
}
/**
* Factory method.
*
* @param <T> The class on which {@link Instantiator} operates.
* @param type The class on which {@link Instantiator} operates. Should be the same as T.
* @return An {@link Instantiator} for {@link #type}.
*/
public static <T> Instantiator<T> of(Class<T> type) {
if (Modifier.isAbstract(type.getModifiers())) {
return new Instantiator<>(giveDynamicSubclass(type, "", b -> b));
}
return new Instantiator<>(type);
}
/**
* Instantiates an object of type T.
*
* <p>All fields will be initialized to their initial values. I.e., 0 for ints, null for
* objects, etc.
*
* @return An object of type T.
*/
public T instantiate() {
return ObjenesisWrapper.getObjenesis().newInstance(type);
}
/**
* Instantiates an anonymous subclass of T. The subclass is generated dynamically.
*
* @return An instance of an anonymous subclass of T.
*/
public T instantiateAnonymousSubclass() {
Class<T> proxyClass = giveDynamicSubclass(type, "", b -> b);
return ObjenesisWrapper.getObjenesis().newInstance(proxyClass);
}
@SuppressWarnings("unchecked")
public static synchronized <S> Class<S> giveDynamicSubclass(
Class<S> superclass,
String nameSuffix,
UnaryOperator<DynamicType.Builder<S>> modify
) {
boolean isSystemClass = isSystemClass(superclass.getName());
String namePrefix = isSystemClass ? FALLBACK_PACKAGE_NAME : getPackageName(superclass);
String name =
namePrefix +
(namePrefix.isEmpty() ? "" : ".") +
superclass.getSimpleName() +
"$$DynamicSubclass$" +
Integer.toHexString(superclass.hashCode()) +
"$" +
nameSuffix;
Class<?> context = isSystemClass ? Instantiator.class : superclass;
ClassLoader classLoader = context.getClassLoader();
// `mvn quarkus:dev` does strange classloader stuff. We need to make sure that we
// check existence with the correct classloader. I don't know how to unit test this.
Class<S> existsAlready = (Class<S>) classForName(classLoader, name);
if (existsAlready != null) {
return existsAlready;
}
ClassLoadingStrategy<ClassLoader> cs = getClassLoadingStrategy(context);
DynamicType.Builder<S> builder = new ByteBuddy()
.with(TypeValidation.DISABLED)
.subclass(superclass)
.name(name);
builder = modify.apply(builder);
return (Class<S>) builder.make().load(classLoader, cs).getLoaded();
}
private static String getPackageName(Class<?> type) {
String cn = type.getName();
int dot = cn.lastIndexOf('.');
return (dot != -1) ? cn.substring(0, dot).intern() : "";
}
private static <S> ClassLoadingStrategy<ClassLoader> getClassLoadingStrategy(Class<S> context) {
if (System.getProperty("java.version").startsWith("1.")) {
return ClassLoadingStrategy.Default.INJECTION.with(context.getProtectionDomain());
} else {
ConditionalInstantiator ci = new ConditionalInstantiator(
"java.lang.invoke.MethodHandles$Lookup"
);
Object lookup = ci.callFactory(
"java.lang.invoke.MethodHandles",
"privateLookupIn",
classes(Class.class, MethodHandles.Lookup.class),
objects(context, MethodHandles.lookup())
);
return ClassLoadingStrategy.UsingLookup.of(lookup);
}
}
private static boolean isSystemClass(String className) {
for (String prefix : FORBIDDEN_PACKAGES) {
if (className.startsWith(prefix)) {
return true;
}
}
return false;
}
}