| /* |
| * 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 jdk.jshell.TypeDeclSnippet; |
| import static jdk.jshell.Snippet.Status.OVERWRITTEN; |
| import static jdk.jshell.Snippet.Status.VALID; |
| |
| public class ClassMembersTest extends KullaTesting { |
| |
| @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()); |
| } |
| } |
| } |