File: ProxyGeneratorCombo.java

package info (click to toggle)
openjdk-21 21.0.8%2B9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 823,976 kB
  • sloc: java: 5,613,338; xml: 1,643,607; cpp: 1,296,296; ansic: 420,291; asm: 404,850; objc: 20,994; sh: 15,271; javascript: 11,245; python: 6,895; makefile: 2,362; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (568 lines) | stat: -rw-r--r-- 21,012 bytes parent folder | download | duplicates (11)
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
/*
 * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @summary Proxy Generator Combo tests
 * @library /test/langtools/tools/javac/lib .
 * @modules jdk.compiler/com.sun.tools.javac.api
 *          jdk.compiler/com.sun.tools.javac.code
 *          jdk.compiler/com.sun.tools.javac.comp
 *          jdk.compiler/com.sun.tools.javac.file
 *          jdk.compiler/com.sun.tools.javac.main
 *          jdk.compiler/com.sun.tools.javac.tree
 *          jdk.compiler/com.sun.tools.javac.util
 * @build combo.ComboTestHelper
 * @run main/othervm ProxyGeneratorCombo
 */

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

import combo.ComboInstance;
import combo.ComboParameter;
import combo.ComboTask.Result;
import combo.ComboTestHelper;

public class ProxyGeneratorCombo extends ComboInstance<ProxyGeneratorCombo> {

    // The unique number to qualify interface names, unique across multiple runs
    private static int uniqueId = 0;

    /**
     * Class Access kinds.
     */
    enum ClassAccessKind implements ComboParameter {
        PUBLIC("public"),
        PACKAGE(""),
        ;

        String classAccessTemplate;

        ClassAccessKind(String classAccessTemplate) {
            this.classAccessTemplate = classAccessTemplate;
        }

        @Override
        public String expand(String optParameter) {
            return classAccessTemplate;
        }
    }

    /**
     * Signatures of methods to be tested.
     */
    enum MethodsKind implements ComboParameter {
        NONE(""),
        ZERO("#{METHODACCESS} void zero() #{EXCEPTION};"),
        ONE("#{METHODACCESS} void one(#{ARG[0]} a) #{EXCEPTION};"),
        TWO("#{METHODACCESS} void one(#{ARG[0]} b);\n" +
                "#{METHODACCESS} void two(#{ARG[0]} a, #{ARG[1]} b);"),
        THREE("#{METHODACCESS} void one(#{ARG[0]} a);\n" +
                "#{METHODACCESS} void two(#{ARG[0]} a, #{ARG[1]} b);\n" +
                "#{METHODACCESS} void three(#{ARG[0]} a, #{ARG[1]} b, #{ARG[0]} c);");

        String methodsKindTemplate;

        MethodsKind(String methodsKindTemplate) {
            this.methodsKindTemplate = methodsKindTemplate;
        }

        @Override
        public String expand(String optParameter) {
            return methodsKindTemplate;
        }
    }

    /**
     * Type of arguments to insert in method signatures
     */
    enum ArgumentKind implements ComboParameter {
        BOOLEAN("boolean"),
        BYTE("byte"),
        CHAR("char"),
        SHORT("short"),
        INT("int"),
        LONG("long"),
        FLOAT("float"),
        DOUBLE("double"),
        STRING("String");

        String argumentsKindTemplate;

        ArgumentKind(String argumentsKindTemplate) {
            this.argumentsKindTemplate = argumentsKindTemplate;
        }

        @Override
        public String expand(String optParameter) {
            return argumentsKindTemplate;
        }
    }

    /**
     * Exceptions to be added to zero and one methods.
     */
    enum ExceptionKind implements ComboParameter {
        NONE(null),
        EXCEPTION(java.lang.Exception.class),
        RUNTIME_EXCEPTION(java.lang.RuntimeException.class),
        ILLEGAL_ARGUMENT_EXCEPTION(java.lang.IllegalArgumentException.class),
        IOEXCEPTION(java.io.IOException.class),
        /**
         * Used only for throw testing, is empty for throws clause in the source,
         */
        UNDECLARED_EXCEPTION(Exception1.class),
        ;

        Class<? extends Throwable> exceptionKindClass;

        ExceptionKind(Class<? extends Throwable> exceptionKindClass) {
            this.exceptionKindClass = exceptionKindClass;
        }

        @Override
        public String expand(String optParameter) {
            return exceptionKindClass == null || exceptionKindClass == Exception1.class
                    ? "" : "throws " + exceptionKindClass.getName();
        }
    }

    /**
     * Extra interfaces to be added.
     */
    enum MultiInterfacesKind implements ComboParameter {
        NONE(new Class<?>[0]),
        INTERFACE_WITH_EXCEPTION(new Class<?>[] {InterfaceWithException.class}),
        ;

        Class<?>[] multiInterfaceClasses;

        MultiInterfacesKind(Class<?>[] multiInterfaceClasses) {
            this.multiInterfaceClasses = multiInterfaceClasses;
        }

        @Override
        // Not used for expansion only execution
        public String expand(String optParameter) {
            throw new RuntimeException("NYI");
        }

        Class<?>[] classes() {
            return multiInterfaceClasses;
        }
    }

    @Override
    public int id() {
        return ++uniqueId;
    }

    protected void fail(String msg, Throwable thrown) {
        super.fail(msg);
        thrown.printStackTrace();
    }

    /**
     * Test interface with a "one(int)" method.
     */
    interface InterfaceWithException {
        // The signature must match the ONE MethodsKind above
        void one(int a) throws RuntimeException, IOException;
    }


    /**
     * Main to generate combinations and run the tests.
     * @param args unused
     * @throws Exception In case of failure
     */
    public static void main(String... args) throws Exception {

        // Test variations of access declarations
        new ComboTestHelper<ProxyGeneratorCombo>()
                .withDimension("CLASSACCESS", ClassAccessKind.values())
                .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC})
                .withDimension("METHODS", ProxyGeneratorCombo::saveMethod,
                        new MethodsKind[] {MethodsKind.NONE, MethodsKind.ZERO, MethodsKind.ONE})
                .withDimension("ARG[0]", new ArgumentKind[] {ArgumentKind.INT})
                .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException,
                        new ExceptionKind[]{ExceptionKind.NONE})
                .run(ProxyGeneratorCombo::new);

        // Test variations of argument types
        new ComboTestHelper<ProxyGeneratorCombo>()
                .withDimension("CLASSACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC})
                .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC})
                .withDimension("METHODS", ProxyGeneratorCombo::saveMethod,
                        MethodsKind.values())
                .withArrayDimension("ARG", ProxyGeneratorCombo::saveArg, 2,
                        ArgumentKind.values())
                .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException,
                        new ExceptionKind[]{ExceptionKind.NONE})
                .withFilter(ProxyGeneratorCombo::filter)
                .run(ProxyGeneratorCombo::new);

        // Test for conflicts in Exceptions on methods with the same signatures
        new ComboTestHelper<ProxyGeneratorCombo>()
                .withDimension("CLASSACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC})
                .withDimension("METHODACCESS", new ClassAccessKind[]{ClassAccessKind.PUBLIC})
                .withDimension("METHODS", ProxyGeneratorCombo::saveMethod, new MethodsKind[] {
                        MethodsKind.ZERO})
                .withDimension("EXCEPTION", ProxyGeneratorCombo::saveException,
                        ExceptionKind.values())
                .withDimension("MULTI_INTERFACES", ProxyGeneratorCombo::saveInterface,
                        new MultiInterfacesKind[] {MultiInterfacesKind.NONE})
                .run(ProxyGeneratorCombo::new);
    }

    /**
     * Basic template.
     */
    String template = "#{CLASSACCESS} interface #{TESTNAME} {\n" +
            "#{METHODS}" +
            "}";

    // Saved values of Combo values
    private MultiInterfacesKind currInterface = MultiInterfacesKind.NONE;
    private MethodsKind currMethod = MethodsKind.NONE;
    private ExceptionKind currException = ExceptionKind.NONE;
    private ArgumentKind[] currArgs = new ArgumentKind[0];

    void saveInterface(ComboParameter s) {
        currInterface = (MultiInterfacesKind)s;
    }

    void saveMethod(ComboParameter s) {
        currMethod = (MethodsKind)s;
    }

    void saveException(ComboParameter s) {
        currException = (ExceptionKind)s;
    }

    void saveArg(ComboParameter s, int index) {
        if (index >= currArgs.length) {
            currArgs = Arrays.copyOf(currArgs, index + 1);
        }
        currArgs[index] = (ArgumentKind)s;
    }

    /**
     * Filter out needless tests (mostly with more variations of arguments than needed).
     * @return true to run the test, false if not
     */
    boolean filter() {
        if ((currMethod == MethodsKind.NONE || currMethod == MethodsKind.ZERO) &&
                currArgs.length >= 2) {
            return currArgs[0] == ArgumentKind.INT &&
                currArgs[1] == ArgumentKind.INT;
        }
        if (currMethod == MethodsKind.ONE &&
                currArgs.length >= 2 ) {
            return currArgs[0] == currArgs[1];
        }
        return true;
    }

    /**
     * Generate the source file and compile.
     * Generate a proxy for the interface and test the resulting Proxy
     * for the methods, exceptions and handling of a thrown exception
     * @throws IOException catch all IOException
     */
    @Override
    public void doWork() throws IOException {
        String cp = System.getProperty("test.classes");
        String ifaceName = "Interface_" + this.id();
        newCompilationTask()
                .withSourceFromTemplate(ifaceName, template.replace("#{TESTNAME}", ifaceName))
                .withOption("-d")
                .withOption(cp)
                .generate(this::checkCompile);
        try {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            Class<?> tc = Class.forName(ifaceName);
            InvocationHandler handler =
                    new ProxyHandler(currException.exceptionKindClass);

            // Construct array of interfaces for the proxy
            Class<?>[] interfaces = new Class<?>[currInterface.classes().length + 1];
            interfaces[0] = tc;
            System.arraycopy(currInterface.classes(), 0,
                    interfaces, 1,
                    currInterface.classes().length);

            Object proxy = Proxy.newProxyInstance(loader, interfaces, handler);
            if (!Proxy.isProxyClass(proxy.getClass())) {
                fail("generated proxy is not a proxy class");
                return;
            }
            for (Class<?> i : interfaces) {
                if (!i.isAssignableFrom(proxy.getClass())) {
                    fail("proxy is not assignable to " + i.getName());
                }
            }
            try {
                String s = proxy.toString();
            } catch (Exception ex) {
                ex.printStackTrace();
                fail("proxy.toString() threw an exception");
            }

            checkDeclaredProxyExceptions(proxy, interfaces);

            if (currMethod == MethodsKind.ZERO && currException != ExceptionKind.NONE) {
                checkThrowsException(proxy, interfaces);
            }

        } catch (Exception ex) {
            throw new RuntimeException("doWork unexpected", ex);
        }
    }

    /**
     * Check that the exceptions declared on the proxy match the declarations for
     * exceptions from the interfaces.
     *
     * @param proxy a proxy object
     * @param interfaces the interfaces that defined it
     */
    void checkDeclaredProxyExceptions(Object proxy, Class<?>[] interfaces) {
        var allMethods = allMethods(Arrays.asList(interfaces));
        Method[] methods = proxy.getClass().getDeclaredMethods();
        for (Method m : methods) {
            String sig = toShortSignature(m);
            var imethods = allMethods.get(sig);
            if (imethods != null) {
                var expectedEx = Set.copyOf(Arrays.asList(m.getExceptionTypes()));
                var exs = Set.copyOf(extractExceptions(imethods));
                if (!expectedEx.equals(exs)) {
                    System.out.printf("mismatch on exceptions for method %s:%nExpected: " +
                                    "%s%nActual:  %s%n",
                            sig, expectedEx, exs);
                    fail("Exceptions declared on proxy don't match interface methods");
                }
            }
        }
    }

    void checkThrowsException(Object proxy, Class<?>[] interfaces) {
        ProxyHandler ph = (ProxyHandler)(Proxy.getInvocationHandler(proxy));
        try {
            Method m = proxy.getClass().getDeclaredMethod("zero");
            m.invoke(proxy);
            fail("Missing exception: " + ph.exceptionClass);
        } catch (NoSuchMethodException nsme) {
            System.out.printf("No method 'zero()' to test exceptions with%n");
            for (var cl : interfaces) {
                System.out.printf("     i/f %s: %s%n", cl, Arrays.toString(cl.getMethods()));
            }
            Method[] methods = proxy.getClass().getMethods();
            System.out.printf("    Proxy methods: %s%n", Arrays.toString(methods));
            fail("No such method test bug", nsme);
        } catch (InvocationTargetException actual) {
            ph.checkThrownException(actual.getTargetException());
        } catch (IllegalAccessException iae) {
            fail("IllegalAccessException", iae);
        }
    }

    /**
     * Exceptions known to be supported by all methods with the same signature.
     * @return a list of universal exception types
     */
    private static List<Class<?>> extractExceptions(List<Method> methods) {
        // for all methods with the same signature
        // start with the exceptions from the first method
        // while there are any exceptions remaining
        // look at the next method
        List<Class<?>> exceptions = null;
        for (Method m : methods) {
            var e = m.getExceptionTypes();
            if (e.length == 0)
                return emptyClassList();
            List<Class<?>> elist = Arrays.asList(e);
            if (exceptions == null) {
                exceptions = elist;    // initialize to first method exceptions
            } else {
                // for each exception
                // if it is compatible (both ways) with any of the existing exceptions continue
                //    else remove the current exception
                var okExceptions = new HashSet<Class<?>>();
                for (int j = 0; j < exceptions.size(); j++) {
                    var ex = exceptions.get(j);
                    for (int i = 0; i < elist.size();i++) {
                        var ci = elist.get(i);

                        if (ci.isAssignableFrom(ex)) {
                            okExceptions.add(ex);
                        }
                        if (ex.isAssignableFrom(ci)) {
                            okExceptions.add(ci);
                        }
                    }
                }
                if (exceptions.isEmpty()) {
                    // The empty set terminates the search for a common set of exceptions
                    return emptyClassList();
                }
                // Use the new set for the next iteration
                exceptions = List.copyOf(okExceptions);
            }
        }
        return (exceptions == null) ? emptyClassList() : exceptions;
    }

    /**
     * An empty correctly typed list of classes.
     * @return An empty typed list of classes
     */
    @SuppressWarnings("unchecked")
    static List<Class<?>> emptyClassList() {
        return Collections.EMPTY_LIST;
    }

    /**
     * Accumulate all of the unique methods.
     *
     * @param interfaces a list of interfaces
     * @return a map from signature to List of methods, unique by signature
     */
    private static Map<String, List<Method>> allMethods(List<Class<?>> interfaces) {
        Map<String, List<Method>> methods = new HashMap<>();
        for (Class<?> c : interfaces) {
            for (Method m : c.getMethods()) {
                if (!Modifier.isStatic(m.getModifiers())) {
                    String sig = toShortSignature(m);
                    methods.computeIfAbsent(sig, s -> new ArrayList<Method>())
                            .add(m);
                }
            }
        }
        return methods;
    }

    /**
     * The signature of a method without the return type.
     * @param m a Method
     * @return the signature with method name and parameters
     */
    static String toShortSignature(Method m) {
        StringJoiner sj = new StringJoiner(",", m.getName() + "(", ")");
        for (Class<?> parameterType : m.getParameterTypes()) {
            sj.add(parameterType.getTypeName());
        }
        return sj.toString();
    }

    /**
     * Report any compilation errors.
     * @param res the result
     */
    void checkCompile(Result<?> res) {
        if (res.hasErrors()) {
            fail("invalid diagnostics for source:\n" +
                    res.compilationInfo() +
                    "\nFound error: " + res.hasErrors());
        }
    }

    /**
     * The Handler for the proxy includes the method to invoke the proxy
     * and the expected exception, if any.
     */
    class ProxyHandler implements InvocationHandler {

        private final Class<? extends Throwable> exceptionClass;

        ProxyHandler(Class<? extends Throwable> throwable) {
            this.exceptionClass = throwable;
        }

        /**
         * Invoke a method on the proxy or return a value.
         * @param   proxy the proxy instance that the method was invoked on
         * @param   method a method
         * @param   args some args
         * @return
         * @throws Throwable a throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("toString")) {
                return "Proxy" + System.identityHashCode(proxy);
            }
            if (method.getName().equals("zero")) {
                if (exceptionClass != null) {
                    throw exceptionClass.getDeclaredConstructor().newInstance();
                }
            }
            return "meth: " + method.toString();
        }

        /**
         * Check that the expected exception was thrown.
         * Special case is handled for Exception1 which does not appear in the
         * throws clause of the method so UndeclaredThrowableException is expected.
         */
        void checkThrownException(Throwable thrown) {
            if (exceptionClass == Exception1.class &&
                    thrown instanceof UndeclaredThrowableException &&
                    ((UndeclaredThrowableException)thrown).getUndeclaredThrowable() instanceof Exception1) {
                // Exception1 caused UndeclaredThrowableException
                return;
            } else if (exceptionClass == Exception1.class) {
                fail("UndeclaredThrowableException", thrown);
            }

            if (exceptionClass != null &&
                    !exceptionClass.equals(thrown.getClass())) {
                throw new RuntimeException("Wrong exception thrown: expected: " + exceptionClass +
                        ", actual: " + thrown.getClass());
            }
        }
    }

    /**
     * Exception to be thrown as a test of InvocationTarget.
     */
    static class Exception1 extends Exception {
        private static final long serialVersionUID = 1L;
        Exception1() {}
    }
}