View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.reflect
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
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  //@formatter:off
37  /**
38   * <p>
39   * Names for things.
40   * </p>
41   * 
42   * Definitions by example, given {@code java.lang.Thread}:
43   * <ul>
44   *    <li>
45   *       <i>internal</i>:
46   *       <pre>java/lang/Thread</pre>
47   *    </li>
48   *    <li>
49   *       <i>descriptor</i>:
50   *       <pre>Ljava/lang/Thread;</pre>
51   *    </li>
52   *    <li>
53   *       <i>resource</i>:
54   *       <pre>java/lang/Thread.class</pre>
55   *    </li>
56   * </ul>
57   * 
58   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2">JVMS §4.2</a>
59   */
60  //@formatter:on
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;  // excludes 'L'
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  	 * @param internalName the <i>internal</i> name
92  	 * @return the type name
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 	 * @param type the fully qualified type name
126 	 * @return the <i>internal</i> name
127 	 */
128 	public static String typeToInternalName(CharSequence type)
129 	{
130 		return CharSequences.replace(type, '.', '/').toString();
131 	}
132 
133 
134 	/**
135 	 * @param type the type.
136 	 * @return the <i>internal</i> name.
137 	 */
138 	public static String typeToInternalName(Type type)
139 	{
140 		return typeToInternalName(type.getTypeName());
141 	}
142 
143 
144 	/**
145 	 * @param type the type.
146 	 * @return the <i>internal</i> name with ".class" appended.
147 	 */
148 	public static String typeToResourceName(Type type)
149 	{
150 		return typeToResourceName(type.getTypeName());
151 	}
152 
153 
154 	/**
155 	 * @param type FQN of the type.
156 	 * @return the <i>internal</i> name with ".class" appended.
157 	 */
158 	public static String typeToResourceName(String type)
159 	{
160 		return typeToInternalName(type) + CLASS_SUFFIX;
161 	}
162 
163 
164 	/**
165 	 * @see <a href="http://download.forge.objectweb.org/asm/asm4-guide.pdf">2.1.3 of asm4-guide</a>
166 	 * @param desc the type descriptor.
167 	 * @return the canonical name of the type.
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 	 * @param desc a {@link java.lang.String} object.
187 	 * @return a {@link java.util.List} object.
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 	 * @param c a {@link java.lang.Class} object.
221 	 * @return a {@link java.lang.CharSequence} object.
222 	 */
223 	public static CharSequence typeToDescriptor(Type c)
224 	{
225 		return typeToDescriptor(c.getTypeName());
226 	}
227 
228 
229 	/**
230 	 * @param type a {@link java.lang.CharSequence} object.
231 	 * @return a {@link java.lang.CharSequence} object.
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 	 * @param method a {@link java.lang.reflect.Method} object.
254 	 * @return a {@link java.lang.CharSequence} object.
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 	 * @param type a {@link java.lang.Class} object.
270 	 * @return a stream of all class names declared <i>inside</i> the {@code type} argument
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 }