| /* | |
| * Copyright (c) 2015, 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 | |
| * @bug 8139829 | |
| * @summary Test access to members of user defined class. | |
| * @build KullaTesting TestingInputStream ExpectedDiagnostic | |
| * @run testng/timeout=600 ClassMembersTest | |
| */ | |
| import java.lang.annotation.RetentionPolicy; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import javax.tools.Diagnostic; | |
| import jdk.jshell.SourceCodeAnalysis; | |
| import org.testng.annotations.DataProvider; | |
| import org.testng.annotations.Test; | |
| import org.testng.annotations.BeforeMethod; | |
| import jdk.jshell.TypeDeclSnippet; | |
| import static jdk.jshell.Snippet.Status.OVERWRITTEN; | |
| import static jdk.jshell.Snippet.Status.VALID; | |
| public class ClassMembersTest extends KullaTesting { | |
| @BeforeMethod | |
| @Override | |
| public void setUp() { | |
| setUp(builder -> builder.executionEngine("local")); | |
| } | |
| @Test(dataProvider = "memberTestCase") | |
| public void memberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) { | |
| MemberTestCase testCase = new MemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference); | |
| assertEval(testCase.generateSource()); | |
| String expectedMessage = testCase.expectedMessage; | |
| if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) { | |
| assertEval("A a = new A();"); | |
| } | |
| if (expectedMessage == null) { | |
| assertEval(testCase.useCodeChunk()); | |
| } else { | |
| assertDeclareFail(testCase.useCodeChunk(), expectedMessage); | |
| } | |
| } | |
| private List<String> parseCode(String input) { | |
| List<String> list = new ArrayList<>(); | |
| SourceCodeAnalysis codeAnalysis = getAnalysis(); | |
| String source = input; | |
| while (!source.trim().isEmpty()) { | |
| SourceCodeAnalysis.CompletionInfo info = codeAnalysis.analyzeCompletion(source); | |
| list.add(info.source()); | |
| source = info.remaining(); | |
| } | |
| return list; | |
| } | |
| @Test(dataProvider = "memberTestCase") | |
| public void extendsMemberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) { | |
| MemberTestCase testCase = new ExtendsMemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference); | |
| String input = testCase.generateSource(); | |
| List<String> ss = parseCode(input); | |
| assertEval(ss.get(0)); | |
| if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) { | |
| assertEval(ss.get(1)); | |
| assertEval("B b = new B();"); | |
| } | |
| String expectedMessage = testCase.expectedMessage; | |
| if (expectedMessage == null) { | |
| assertEval(testCase.useCodeChunk()); | |
| } else { | |
| assertDeclareFail(testCase.useCodeChunk(), expectedMessage); | |
| } | |
| } | |
| @Test | |
| public void interfaceTest() { | |
| String interfaceSource = | |
| "interface A {\n" + | |
| " default int defaultMethod() { return 1; }\n" + | |
| " static int staticMethod() { return 2; }\n" + | |
| " int method();\n" + | |
| " class Inner1 {}\n" + | |
| " static class Inner2 {}\n" + | |
| "}"; | |
| assertEval(interfaceSource); | |
| assertEval("A.staticMethod();", "2"); | |
| String classSource = | |
| "class B implements A {\n" + | |
| " public int method() { return 3; }\n" + | |
| "}"; | |
| assertEval(classSource); | |
| assertEval("B b = new B();"); | |
| assertEval("b.defaultMethod();", "1"); | |
| assertDeclareFail("B.staticMethod();", | |
| new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR)); | |
| assertEval("b.method();", "3"); | |
| assertEval("new A.Inner1();"); | |
| assertEval("new A.Inner2();"); | |
| assertEval("new B.Inner1();"); | |
| assertEval("new B.Inner2();"); | |
| } | |
| @Test | |
| public void enumTest() { | |
| String enumSource = | |
| "enum E {A(\"s\");\n" + | |
| " private final String s;\n" + | |
| " private E(String s) { this.s = s; }\n" + | |
| " public String method() { return s; }\n" + | |
| " private String privateMethod() { return s; }\n" + | |
| " public static String staticMethod() { return staticPrivateMethod(); }\n" + | |
| " private static String staticPrivateMethod() { return \"a\"; }\n" + | |
| "}"; | |
| assertEval(enumSource); | |
| assertEval("E a = E.A;", "A"); | |
| assertDeclareFail("a.s;", | |
| new ExpectedDiagnostic("compiler.err.report.access", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR)); | |
| assertDeclareFail("new E(\"q\");", | |
| new ExpectedDiagnostic("compiler.err.enum.cant.be.instantiated", 0, 10, 0, -1, -1, Diagnostic.Kind.ERROR)); | |
| assertEval("a.method();", "\"s\""); | |
| assertDeclareFail("a.privateMethod();", | |
| new ExpectedDiagnostic("compiler.err.report.access", 0, 15, 1, -1, -1, Diagnostic.Kind.ERROR)); | |
| assertEval("E.staticMethod();", "\"a\""); | |
| assertDeclareFail("a.staticPrivateMethod();", | |
| new ExpectedDiagnostic("compiler.err.report.access", 0, 21, 1, -1, -1, Diagnostic.Kind.ERROR)); | |
| assertDeclareFail("E.method();", | |
| new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 8, 1, -1, -1, Diagnostic.Kind.ERROR)); | |
| } | |
| @Test(dataProvider = "retentionPolicyTestCase") | |
| public void annotationTest(RetentionPolicy policy) { | |
| assertEval("import java.lang.annotation.*;"); | |
| String annotationSource = | |
| "@Retention(RetentionPolicy." + policy.toString() + ")\n" + | |
| "@interface A {}"; | |
| assertEval(annotationSource); | |
| String classSource = | |
| "@A class C {\n" + | |
| " @A C() {}\n" + | |
| " @A void f() {}\n" + | |
| " @A int f;\n" + | |
| " @A class Inner {}\n" + | |
| "}"; | |
| assertEval(classSource); | |
| String isRuntimeVisible = policy == RetentionPolicy.RUNTIME ? "true" : "false"; | |
| assertEval("C.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible); | |
| assertEval("C.class.getDeclaredConstructor().getAnnotationsByType(A.class).length > 0;", isRuntimeVisible); | |
| assertEval("C.class.getDeclaredMethod(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible); | |
| assertEval("C.class.getDeclaredField(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible); | |
| assertEval("C.Inner.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible); | |
| } | |
| @DataProvider(name = "retentionPolicyTestCase") | |
| public Object[][] retentionPolicyTestCaseGenerator() { | |
| List<Object[]> list = new ArrayList<>(); | |
| for (RetentionPolicy policy : RetentionPolicy.values()) { | |
| list.add(new Object[]{policy}); | |
| } | |
| return list.toArray(new Object[list.size()][]); | |
| } | |
| @DataProvider(name = "memberTestCase") | |
| public Object[][] memberTestCaseGenerator() { | |
| List<Object[]> list = new ArrayList<>(); | |
| for (AccessModifier accessModifier : AccessModifier.values()) { | |
| for (Static isStaticMember : Static.values()) { | |
| for (Static isStaticReference : Static.values()) { | |
| for (CodeChunk codeChunk : CodeChunk.values()) { | |
| if (codeChunk == CodeChunk.CONSTRUCTOR && isStaticMember == Static.STATIC) { | |
| continue; | |
| } | |
| list.add(new Object[]{ accessModifier, codeChunk, isStaticMember, isStaticReference }); | |
| } | |
| } | |
| } | |
| } | |
| return list.toArray(new Object[list.size()][]); | |
| } | |
| public static class ExtendsMemberTestCase extends MemberTestCase { | |
| public ExtendsMemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) { | |
| super(accessModifier, codeChunk, isStaticMember, isStaticReference); | |
| } | |
| @Override | |
| public String getSourceTemplate() { | |
| return super.getSourceTemplate() + "\n" | |
| + "class B extends A {}"; | |
| } | |
| @Override | |
| public String errorMessage() { | |
| if (!isAccessible()) { | |
| if (codeChunk == CodeChunk.METHOD) { | |
| return "compiler.err.cant.resolve.location.args"; | |
| } | |
| if (codeChunk == CodeChunk.CONSTRUCTOR) { | |
| return "compiler.err.cant.resolve.location"; | |
| } | |
| } | |
| return super.errorMessage(); | |
| } | |
| @Override | |
| public String useCodeChunk() { | |
| return useCodeChunk("B"); | |
| } | |
| } | |
| public static class MemberTestCase { | |
| public final AccessModifier accessModifier; | |
| public final CodeChunk codeChunk; | |
| public final Static isStaticMember; | |
| public final Static isStaticReference; | |
| public final String expectedMessage; | |
| public MemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, | |
| Static isStaticReference) { | |
| this.accessModifier = accessModifier; | |
| this.codeChunk = codeChunk; | |
| this.isStaticMember = isStaticMember; | |
| this.isStaticReference = isStaticReference; | |
| this.expectedMessage = errorMessage(); | |
| } | |
| public String getSourceTemplate() { | |
| return "class A {\n" + | |
| " #MEMBER#\n" + | |
| "}"; | |
| } | |
| public boolean isAccessible() { | |
| return accessModifier != AccessModifier.PRIVATE; | |
| } | |
| public String errorMessage() { | |
| if (!isAccessible()) { | |
| return "compiler.err.report.access"; | |
| } | |
| if (codeChunk == CodeChunk.INNER_INTERFACE) { | |
| return "compiler.err.abstract.cant.be.instantiated"; | |
| } | |
| if (isStaticMember == Static.STATIC) { | |
| if (isStaticReference == Static.NO && codeChunk == CodeChunk.INNER_CLASS) { | |
| return "compiler.err.qualified.new.of.static.class"; | |
| } | |
| return null; | |
| } | |
| if (isStaticReference == Static.STATIC) { | |
| if (codeChunk == CodeChunk.CONSTRUCTOR) { | |
| return null; | |
| } | |
| if (codeChunk == CodeChunk.INNER_CLASS) { | |
| return "compiler.err.encl.class.required"; | |
| } | |
| return "compiler.err.non-static.cant.be.ref"; | |
| } | |
| return null; | |
| } | |
| public String generateSource() { | |
| return getSourceTemplate().replace("#MEMBER#", codeChunk.generateSource(accessModifier, isStaticMember)); | |
| } | |
| protected String useCodeChunk(String className) { | |
| String name = className.toLowerCase(); | |
| switch (codeChunk) { | |
| case CONSTRUCTOR: | |
| return String.format("new %s();", className); | |
| case METHOD: | |
| if (isStaticReference == Static.STATIC) { | |
| return String.format("%s.method();", className); | |
| } else { | |
| return String.format("%s.method();", name); | |
| } | |
| case FIELD: | |
| if (isStaticReference == Static.STATIC) { | |
| return String.format("%s.field;", className); | |
| } else { | |
| return String.format("%s.field;", name); | |
| } | |
| case INNER_CLASS: | |
| if (isStaticReference == Static.STATIC) { | |
| return String.format("new %s.Inner();", className); | |
| } else { | |
| return String.format("%s.new Inner();", name); | |
| } | |
| case INNER_INTERFACE: | |
| return String.format("new %s.Inner();", className); | |
| default: | |
| throw new AssertionError("Unknown code chunk: " + this); | |
| } | |
| } | |
| public String useCodeChunk() { | |
| return useCodeChunk("A"); | |
| } | |
| } | |
| public enum AccessModifier { | |
| PUBLIC("public"), | |
| PROTECTED("protected"), | |
| PACKAGE_PRIVATE(""), | |
| PRIVATE("private"); | |
| private final String modifier; | |
| AccessModifier(String modifier) { | |
| this.modifier = modifier; | |
| } | |
| public String getModifier() { | |
| return modifier; | |
| } | |
| } | |
| public enum Static { | |
| STATIC("static"), NO(""); | |
| private final String modifier; | |
| Static(String modifier) { | |
| this.modifier = modifier; | |
| } | |
| public String getModifier() { | |
| return modifier; | |
| } | |
| } | |
| public enum CodeChunk { | |
| CONSTRUCTOR("#MODIFIER# A() {}"), | |
| METHOD("#MODIFIER# int method() { return 10; }"), | |
| FIELD("#MODIFIER# int field = 10;"), | |
| INNER_CLASS("#MODIFIER# class Inner {}"), | |
| INNER_INTERFACE("#MODIFIER# interface Inner {}"); | |
| private final String code; | |
| CodeChunk(String code) { | |
| this.code = code; | |
| } | |
| public String generateSource(AccessModifier accessModifier, Static isStatic) { | |
| return code.replace("#MODIFIER#", accessModifier.getModifier() + " " + isStatic.getModifier()); | |
| } | |
| } | |
| } |