test/tools/javac/lambda/bridge/template_tests/BridgeMethodTestCase.java

changeset 2017
67c5110c60fe
parent 0
959103a6100f
equal deleted inserted replaced
2016:f4efd6ef6e80 2017:67c5110c60fe
1 /*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24 import java.io.File;
25 import java.io.IOException;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.StringJoiner;
33
34 import org.testng.annotations.BeforeMethod;
35 import org.testng.annotations.Test;
36
37 import tools.javac.combo.*;
38
39 import static org.testng.Assert.fail;
40
41 /**
42 * BridgeMethodTestCase -- used for asserting linkage to bridges under separate compilation.
43 *
44 * Example test case:
45 * public void test1() throws IOException, ReflectiveOperationException {
46 * compileSpec("C(Bc1(A))");
47 * assertLinkage("C", LINKAGE_ERROR, "B1");
48 * recompileSpec("C(Bc1(Ac0))", "A");
49 * assertLinkage("C", "A0", "B1");
50 * }
51 *
52 * This compiles A, B, and C, asserts that C.m()Object does not exist, asserts
53 * that C.m()Number eventually invokes B.m()Number, recompiles B, and then asserts
54 * that the result of calling C.m()Object now arrives at A.
55 *
56 * @author Brian Goetz
57 */
58
59 @Test
60 public abstract class BridgeMethodTestCase extends JavacTemplateTestBase {
61
62 private static final String TYPE_LETTERS = "ABCDIJK";
63
64 private static final String BASE_INDEX_CLASS = "class C0 {\n" +
65 " int val;\n" +
66 " C0(int val) { this.val = val; }\n" +
67 " public int getVal() { return val; }\n" +
68 "}\n";
69 private static final String INDEX_CLASS_TEMPLATE = "class C#ID extends C#PREV {\n" +
70 " C#ID(int val) { super(val); }\n" +
71 "}\n";
72
73
74
75 protected static String LINKAGE_ERROR = "-1";
76
77 private List<File> compileDirs = new ArrayList<>();
78
79 /**
80 * Compile all the classes in a class spec, and put them on the classpath.
81 *
82 * The spec is the specification for a nest of classes, using the following notation
83 * A, B represent abstract classes
84 * C represents a concrete class
85 * I, J, K represent interfaces
86 * Lowercase 'c' following a class means that the method m() is concrete
87 * Lowercase 'a' following a class or interface means that the method m() is abstract
88 * Lowercase 'd' following an interface means that the method m() is default
89 * A number 0, 1, or 2 following the lowercase letter indicates the return type of that method
90 * 0 = Object, 1 = Number, 2 = Integer (these form an inheritance chain so bridges are generated)
91 * A classes supertypes follow its method spec, in parentheses
92 * Examples:
93 * C(Ia0, Jd0) -- C extends I and J, I has abstract m()Object, J has default m()Object
94 * Cc1(Ia0) -- C has concrete m()Number, extends I with abstract m()Object
95 * If a type must appear multiple times, its full spec must be in the first occurrence
96 * Example:
97 * C(I(Kd0), J(K))
98 */
99 protected void compileSpec(String spec) throws IOException {
100 compileSpec(spec, false);
101 }
102
103 /**
104 * Compile all the classes in a class spec, and assert that there were compilation errors.
105 */
106 protected void compileSpec(String spec, String... errorKeys) throws IOException {
107 compileSpec(spec, false, errorKeys);
108 }
109
110 protected void compileSpec(String spec, boolean debug, String... errorKeys) throws IOException {
111 ClassModel cm = new Parser(spec).parseClassModel();
112 for (int i = 0; i <= cm.maxIndex() ; i++) {
113 if (debug) System.out.println(indexClass(i));
114 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
115 }
116 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet()) {
117 if (debug) System.out.println(e.getValue().toSource());
118 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
119 }
120 compileDirs.add(compile(true));
121 resetSourceFiles();
122 if (errorKeys.length == 0)
123 assertCompileSucceeded();
124 else
125 assertCompileErrors(errorKeys);
126 }
127
128 /**
129 * Recompile only a subset of classes in the class spec, as named by names,
130 * and put them on the classpath such that they shadow earlier versions of that class.
131 */
132 protected void recompileSpec(String spec, String... names) throws IOException {
133 List<String> nameList = Arrays.asList(names);
134 ClassModel cm = new Parser(spec).parseClassModel();
135 for (int i = 0; i <= cm.maxIndex() ; i++) {
136 addSourceFile(String.format("C%d.java", i), new StringTemplate(indexClass(i)));
137 }
138 for (Map.Entry<String, ClassModel> e : classes(cm).entrySet())
139 if (nameList.contains(e.getKey()))
140 addSourceFile(e.getKey() + ".java", new StringTemplate(e.getValue().toSource()));
141 compileDirs.add(compile(Arrays.asList(classPaths()), true));
142 resetSourceFiles();
143 assertCompileSucceeded();
144 }
145
146 protected void assertLinkage(String name, String... expected) throws ReflectiveOperationException {
147 for (int i=0; i<expected.length; i++) {
148 String e = expected[i];
149 if (e.equals(LINKAGE_ERROR)) {
150 try {
151 int actual = invoke(name, i);
152 fail("Expected linkage error, got" + fromNum(actual));
153 }
154 catch (LinkageError x) {
155 // success
156 }
157 }
158 else {
159 if (e.length() == 1)
160 e += "0";
161 int expectedInt = toNum(e);
162 int actual = invoke(name, i);
163 if (expectedInt != actual)
164 fail(String.format("Expected %s but found %s for %s.m()%d", fromNum(expectedInt), fromNum(actual), name, i));
165 }
166 }
167 }
168
169 private Map<String, ClassModel> classes(ClassModel cm) {
170 HashMap<String, ClassModel> m = new HashMap<>();
171 classesHelper(cm, m);
172 return m;
173 }
174
175 private String indexClass(int index) {
176 if (index == 0) {
177 return BASE_INDEX_CLASS;
178 } else {
179 return INDEX_CLASS_TEMPLATE
180 .replace("#ID", String.valueOf(index))
181 .replace("#PREV", String.valueOf(index - 1));
182 }
183 }
184
185 private static String overrideName(int index) {
186 return "C" + index;
187 }
188
189 private void classesHelper(ClassModel cm, Map<String, ClassModel> m) {
190 if (!m.containsKey(cm.name))
191 m.put(cm.name, cm);
192 for (ClassModel s : cm.supertypes)
193 classesHelper(s, m);
194 }
195
196 private static String fromNum(int num) {
197 return String.format("%c%d", TYPE_LETTERS.charAt(num / 10), num % 10);
198 }
199
200 private static int toNum(String name, int index) {
201 return 10*(TYPE_LETTERS.indexOf(name.charAt(0))) + index;
202 }
203
204 private static int toNum(String string) {
205 return 10*(TYPE_LETTERS.indexOf(string.charAt(0))) + Integer.parseInt(string.substring(1, 2));
206 }
207
208 private int invoke(String name, int index) throws ReflectiveOperationException {
209 File[] files = classPaths();
210 Class clazz = loadClass(name, files);
211 Method[] ms = clazz.getMethods();
212 for (Method m : ms) {
213 if (m.getName().equals("m") && m.getReturnType().getName().equals(overrideName(index))) {
214 m.setAccessible(true);
215 Object instance = clazz.newInstance();
216 Object c0 = m.invoke(instance);
217 Method getVal = c0.getClass().getMethod("getVal");
218 getVal.setAccessible(true);
219 return (int)getVal.invoke(c0);
220 }
221 }
222 throw new NoSuchMethodError("cannot find method m()" + index + " in class " + name);
223 }
224
225 private File[] classPaths() {
226 File[] files = new File[compileDirs.size()];
227 for (int i=0; i<files.length; i++)
228 files[files.length - i - 1] = compileDirs.get(i);
229 return files;
230 }
231
232 @BeforeMethod
233 @Override
234 public void reset() {
235 compileDirs.clear();
236 super.reset();
237 }
238
239 private static class ClassModel {
240
241 enum MethodType {
242 ABSTRACT('a'), CONCRETE('c'), DEFAULT('d');
243
244 public final char designator;
245
246 MethodType(char designator) {
247 this.designator = designator;
248 }
249
250 public static MethodType find(char c) {
251 for (MethodType m : values())
252 if (m.designator == c)
253 return m;
254 throw new IllegalArgumentException();
255 }
256 }
257
258 private final String name;
259 private final boolean isInterface;
260 private final List<ClassModel> supertypes;
261 private final MethodType methodType;
262 private final int methodIndex;
263
264 private ClassModel(String name,
265 boolean anInterface,
266 List<ClassModel> supertypes,
267 MethodType methodType,
268 int methodIndex) {
269 this.name = name;
270 isInterface = anInterface;
271 this.supertypes = supertypes;
272 this.methodType = methodType;
273 this.methodIndex = methodIndex;
274 }
275
276 @Override
277 public String toString() {
278 StringBuilder sb = new StringBuilder();
279 sb.append(name);
280 if (methodType != null) {
281 sb.append(methodType.designator);
282 sb.append(methodIndex);
283 }
284 if (!supertypes.isEmpty()) {
285 sb.append("(");
286 for (int i=0; i<supertypes.size(); i++) {
287 if (i > 0)
288 sb.append(",");
289 sb.append(supertypes.get(i).toString());
290 }
291 sb.append(")");
292 }
293 return sb.toString();
294 }
295
296 int maxIndex() {
297 int maxSoFar = methodIndex;
298 for (ClassModel cm : supertypes) {
299 maxSoFar = Math.max(cm.maxIndex(), maxSoFar);
300 }
301 return maxSoFar;
302 }
303
304 public String toSource() {
305 String extendsClause = "";
306 String implementsClause = "";
307 String methodBody = "";
308 boolean isAbstract = "AB".contains(name);
309
310 for (ClassModel s : supertypes) {
311 if (!s.isInterface) {
312 extendsClause = String.format("extends %s", s.name);
313 break;
314 }
315 }
316
317 StringJoiner sj = new StringJoiner(", ");
318 for (ClassModel s : supertypes)
319 if (s.isInterface)
320 sj.add(s.name);
321 if (sj.length() > 0) {
322 if (isInterface)
323 implementsClause = "extends " + sj.toString();
324 else
325 implementsClause = "implements " + sj.toString();
326 }
327 if (methodType != null) {
328 switch (methodType) {
329 case ABSTRACT:
330 methodBody = String.format("public abstract %s m();", overrideName(methodIndex));
331 break;
332 case CONCRETE:
333 methodBody = String.format("public %s m() { return new %s(%d); };",
334 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
335 break;
336 case DEFAULT:
337 methodBody = String.format("public default %s m() { return new %s(%d); };",
338 overrideName(methodIndex), overrideName(methodIndex), toNum(name, methodIndex));
339 break;
340
341 }
342 }
343
344 return String.format("public %s %s %s %s %s { %s }", isAbstract ? "abstract" : "",
345 isInterface ? "interface" : "class",
346 name, extendsClause, implementsClause, methodBody);
347 }
348 }
349
350 private static class Parser {
351 private final String input;
352 private final char[] chars;
353 private int index;
354
355 private Parser(String s) {
356 input = s;
357 chars = s.toCharArray();
358 }
359
360 private char peek() {
361 return index < chars.length ? chars[index] : 0;
362 }
363
364 private boolean peek(String validChars) {
365 return validChars.indexOf(peek()) >= 0;
366 }
367
368 private char advanceIf(String validChars) {
369 if (peek(validChars))
370 return chars[index++];
371 else
372 return 0;
373 }
374
375 private char advanceIfDigit() {
376 return advanceIf("0123456789");
377 }
378
379 private int index() {
380 StringBuilder buf = new StringBuilder();
381 char c = advanceIfDigit();
382 while (c != 0) {
383 buf.append(c);
384 c = advanceIfDigit();
385 }
386 return Integer.valueOf(buf.toString());
387 }
388
389 private char advance() {
390 return chars[index++];
391 }
392
393 private char expect(String validChars) {
394 char c = advanceIf(validChars);
395 if (c == 0)
396 throw new IllegalArgumentException(String.format("Expecting %s at position %d of %s", validChars, index, input));
397 return c;
398 }
399
400 public ClassModel parseClassModel() {
401 List<ClassModel> supers = new ArrayList<>();
402 char name = expect(TYPE_LETTERS);
403 boolean isInterface = "IJK".indexOf(name) >= 0;
404 ClassModel.MethodType methodType = peek(isInterface ? "ad" : "ac") ? ClassModel.MethodType.find(advance()) : null;
405 int methodIndex = 0;
406 if (methodType != null) {
407 methodIndex = index();
408 }
409 if (peek() == '(') {
410 advance();
411 supers.add(parseClassModel());
412 while (peek() == ',') {
413 advance();
414 supers.add(parseClassModel());
415 }
416 expect(")");
417 }
418 return new ClassModel(new String(new char[]{ name }), isInterface, supers, methodType, methodIndex);
419 }
420 }
421 }

mercurial