1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.reflect;
20
21 import static java.util.Arrays.stream;
22 import static java.util.Collections.unmodifiableMap;
23 import static java.util.stream.Collectors.toMap;
24 import static java.util.stream.Stream.concat;
25
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Type;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.stream.Stream;
33
34 import io.earcam.utilitarian.charstar.CharSequences;
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 public final class Names {
62
63 private static final String CLASS_SUFFIX = ".class";
64 private static final char L = 'L';
65 private static final Map<Character, String> PRIMITIVE_DESCRIPTORS;
66 private static final Map<String, String> PRIMITIVE_TO_DESCRIPTOR;
67 static {
68 Map<Character, String> map = new HashMap<>(8);
69 map.put('Z', "boolean");
70 map.put('B', "byte");
71 map.put('C', "char");
72 map.put('D', "double");
73 map.put('F', "float");
74 map.put('I', "int");
75 map.put('J', "long");
76 map.put('S', "short");
77 map.put('V', "void");
78
79 PRIMITIVE_DESCRIPTORS = unmodifiableMap(map);
80
81 PRIMITIVE_TO_DESCRIPTOR = unmodifiableMap(map.entrySet().stream()
82 .collect(toMap(Map.Entry::getValue, e -> Character.toString(e.getKey()))));
83 }
84
85
86 private Names()
87 {}
88
89
90
91
92
93
94 public static String internalToTypeName(CharSequence internalName)
95 {
96 if(CharSequences.endsWith(internalName, "package-info") || CharSequences.endsWith(internalName, "module-info")) {
97 return CharSequences.replace(internalName, '/', '.').toString();
98 }
99
100 int depth = 0;
101 while(CharSequences.indexOf(internalName, '[', depth) != -1) {
102 ++depth;
103 }
104 StringBuilder typeName = new StringBuilder();
105 if(depth > 0) {
106 if(internalName.charAt(depth) == 'L') {
107 typeName.append(internalName, depth + 1, internalName.length() - 1);
108 } else {
109 typeName.append(PRIMITIVE_DESCRIPTORS.get(internalName.charAt(depth)));
110 }
111 while(depth-- > 0) {
112 typeName.append('[').append(']');
113 }
114 } else {
115 typeName.append(internalName);
116 }
117 while((depth = typeName.indexOf("/", depth)) != -1) {
118 typeName.setCharAt(depth, '.');
119 }
120 return typeName.toString();
121 }
122
123
124
125
126
127
128 public static String typeToInternalName(CharSequence type)
129 {
130 return CharSequences.replace(type, '.', '/').toString();
131 }
132
133
134
135
136
137
138 public static String typeToInternalName(Type type)
139 {
140 return typeToInternalName(type.getTypeName());
141 }
142
143
144
145
146
147
148 public static String typeToResourceName(Type type)
149 {
150 return typeToResourceName(type.getTypeName());
151 }
152
153
154
155
156
157
158 public static String typeToResourceName(String type)
159 {
160 return typeToInternalName(type) + CLASS_SUFFIX;
161 }
162
163
164
165
166
167
168
169 public static String descriptorToTypeName(String desc)
170 {
171 List<String> binaryNames = descriptorsToTypeNames(desc);
172 if(binaryNames.size() != 1) {
173 throw new IllegalArgumentException("No single type name in description: " + binaryNames);
174 }
175 return binaryNames.get(0);
176 }
177
178
179 private static String primitiveDescriptor(char descriptorChar)
180 {
181 return PRIMITIVE_DESCRIPTORS.get(descriptorChar);
182 }
183
184
185
186
187
188
189 public static List<String> descriptorsToTypeNames(String desc)
190 {
191 int pos = 0;
192 List<String> types = new ArrayList<>();
193
194 while(pos < desc.length()) {
195 StringBuilder arraySuffix = new StringBuilder();
196 while(desc.charAt(pos) == '[') {
197 arraySuffix.append("[]");
198 ++pos;
199 }
200 char current = desc.charAt(pos);
201 if(current != L) {
202 types.add(primitiveDescriptor(current) + arraySuffix);
203 ++pos;
204 continue;
205 }
206 int end = desc.indexOf(';', pos);
207
208 if(end == -1) {
209 throw new IllegalArgumentException("Expecting object end marker ';'. "
210 + "Could not parse next descriptor in: '" + desc + "' with: '" + desc.substring(pos) + "' remaining");
211 }
212 types.add(desc.substring(pos + 1, end).replace('/', '.') + arraySuffix);
213 pos = end + 1;
214 }
215 return types;
216 }
217
218
219
220
221
222
223 public static CharSequence typeToDescriptor(Type c)
224 {
225 return typeToDescriptor(c.getTypeName());
226 }
227
228
229
230
231
232
233 public static CharSequence typeToDescriptor(CharSequence type)
234 {
235 StringBuilder name = new StringBuilder();
236 CharSequence t = type;
237 while(CharSequences.endsWith(t, "[]")) {
238 name.append('[');
239 t = t.subSequence(0, t.length() - 2);
240 }
241 name.append(PRIMITIVE_TO_DESCRIPTOR.getOrDefault(t, classTypeToDescriptor(t)));
242 return name;
243 }
244
245
246 private static String classTypeToDescriptor(CharSequence type)
247 {
248 return "L" + typeToInternalName(type) + ";";
249 }
250
251
252
253
254
255
256 public static CharSequence descriptorFor(Method method)
257 {
258 StringBuilder builder = new StringBuilder().append('(');
259 for(Class<?> type : method.getParameterTypes()) {
260 builder.append(typeToDescriptor(type));
261 }
262 return builder
263 .append(')')
264 .append(typeToDescriptor(method.getReturnType()));
265 }
266
267
268
269
270
271
272 public static Stream<String> declaredInternalNamesOf(Class<?> type)
273 {
274 return concat(
275 concat(
276 stream(type.getDeclaredClasses()).map(Names::typeToInternalName),
277 stream(type.getDeclaredClasses()).flatMap(Names::declaredInternalNamesOf)),
278 anonymousClassesOf(type));
279 }
280
281
282 private static Stream<String> anonymousClassesOf(Class<?> type)
283 {
284 List<String> anonymousClasses = new ArrayList<>();
285 int i = 1;
286 String baseName = typeToInternalName(type);
287 String name = anonymousName(baseName, i);
288 while(type.getClassLoader().getResource(name + CLASS_SUFFIX) != null) {
289 anonymousClasses.add(name);
290 name = anonymousName(baseName, ++i);
291 }
292 return anonymousClasses.stream();
293 }
294
295
296 private static String anonymousName(String baseName, int i)
297 {
298 return baseName + '$' + i;
299 }
300 }