| /* | |
| * Copyright (c) 2014, 2016, 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 8064794 | |
| * @summary The negative test against cyclic dependencies. | |
| * @library /tools/lib | |
| * @modules jdk.compiler/com.sun.tools.javac.api | |
| * jdk.compiler/com.sun.tools.javac.main | |
| * @build toolbox.ToolBox NegativeCyclicDependencyTest | |
| * @run main NegativeCyclicDependencyTest | |
| */ | |
| import javax.tools.JavaCompiler; | |
| import javax.tools.ToolProvider; | |
| import java.io.StringWriter; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import toolbox.ToolBox; | |
| /** | |
| * The test generates the following code: | |
| * | |
| * package pkg; | |
| * import pkg.B.InnerB; | |
| * class A extends InnerB { | |
| * static class InnerA {} | |
| * } | |
| * | |
| * package pkg; | |
| * import pkg.A.InnerA; | |
| * class B extends InnerA { | |
| * static class InnerB {} | |
| * } | |
| * | |
| * compiles and checks whether compilation fails with the correct message. | |
| * The test generates all possible combination of inheritance: | |
| * 1. A extends InnerB, B extends InnerA; | |
| * 2. InnerA extends InnerB, InnerB extends InnerA; | |
| * 3. A extends InnerB, InnerB extends InnerA; | |
| * 4. B extends InnerA, InnerA extends InnerB; | |
| * 5. A extends InnerA. | |
| * The test checks class, enum and interface as parent class, and checks all | |
| * possible import statements. | |
| */ | |
| public class NegativeCyclicDependencyTest { | |
| private final static String expectedErrorMessage = | |
| "\\w+:\\d+:\\d+: compiler.err.cyclic.inheritance: [\\w.]+\n1 error\n"; | |
| private final static String[] sourceTemplatesA = { | |
| "package pkg;\n" + | |
| "#IMPORT_TYPE\n" + | |
| "#OUTER_CLASS A #INHERIT InnerB {#ENUM_SEMI\n" + | |
| " static #INNER_CLASS InnerA {}\n" + | |
| "}", | |
| "package pkg;\n" + | |
| "#IMPORT_TYPE\n" + | |
| "#OUTER_CLASS A {#ENUM_SEMI\n" + | |
| " static #INNER_CLASS InnerA #INHERIT InnerB {}\n" + | |
| "}" | |
| }; | |
| private final static String[] sourceTemplatesB = { | |
| "package pkg;\n" + | |
| "#IMPORT_TYPE\n" + | |
| "#OUTER_CLASS B #INHERIT InnerA {#ENUM_SEMI\n" + | |
| " static #INNER_CLASS InnerB {}\n" + | |
| "}", | |
| "package pkg;\n" + | |
| "#IMPORT_TYPE\n" + | |
| "#OUTER_CLASS B {#ENUM_SEMI\n" + | |
| " static #INNER_CLASS InnerB #INHERIT InnerA {}\n" + | |
| "}" | |
| }; | |
| private final static String sourceTemplate = | |
| "package pkg;\n" + | |
| "#IMPORT_TYPE\n" + | |
| "#OUTER_CLASS A #INHERIT InnerA {#ENUM_SEMI\n" + | |
| " static #INNER_CLASS InnerA {}\n" + | |
| "}"; | |
| public static void main(String[] args) { | |
| new NegativeCyclicDependencyTest().test(); | |
| } | |
| public void test() { | |
| int passed = 0; | |
| List<TestCase> testCases = generateTestCases(); | |
| for (TestCase testCase : testCases) { | |
| try { | |
| String output = compile(testCase.sources); | |
| if (!output.matches(testCase.expectedMessage)) { | |
| reportFailure(testCase); | |
| printf(String.format("Message: %s, does not match regexp: %s\n", | |
| output, testCase.expectedMessage)); | |
| } else { | |
| ++passed; | |
| } | |
| } catch (RuntimeException e) { | |
| reportFailure(testCase); | |
| e.printStackTrace(); | |
| } | |
| } | |
| String message = String.format( | |
| "Total test cases run: %d, passed: %d, failed: %d.", | |
| testCases.size(), passed, testCases.size() - passed); | |
| if (passed != testCases.size()) { | |
| throw new RuntimeException(message); | |
| } | |
| echo(message); | |
| } | |
| private void reportFailure(TestCase testCase) { | |
| echo("Test case failed."); | |
| for (ToolBox.JavaSource source : testCase.sources) { | |
| echo(source.getCharContent(true)); | |
| echo(); | |
| } | |
| } | |
| public List<TestCase> generateTestCases() { | |
| List<TestCase> testCases = generateTestCasesWithTwoClasses(); | |
| testCases.addAll(generateTestCasesWithOneClass()); | |
| return testCases; | |
| } | |
| private List<TestCase> generateTestCasesWithOneClass() { | |
| String importedClassName = "pkg.A.InnerA"; | |
| List<TestCase> testCases = new ArrayList<>(); | |
| for (ClassType outerClass : ClassType.values()) { | |
| for (ClassType innerClass : ClassType.values()) { | |
| if (!outerClass.canInherit(innerClass)) { | |
| continue; | |
| } | |
| for (ImportType importType : ImportType.values()) { | |
| String source = generateSource( | |
| sourceTemplate, | |
| outerClass, | |
| innerClass, | |
| outerClass.inheritedString(innerClass), | |
| importType, | |
| importedClassName); | |
| testCases.add(new TestCase(expectedErrorMessage, | |
| new ToolBox.JavaSource("A", source))); | |
| } | |
| } | |
| } | |
| return testCases; | |
| } | |
| private List<TestCase> generateTestCasesWithTwoClasses() { | |
| String importedClassName1 = "pkg.A.InnerA"; | |
| String importedClassName2 = "pkg.B.InnerB"; | |
| List<TestCase> testCases = new ArrayList<>(); | |
| for (int i = 0; i < sourceTemplatesA.length; ++i) { | |
| for (int j = 0; j < sourceTemplatesB.length; ++j) { | |
| for (ClassType outerClass1 : ClassType.values()) { | |
| for (ClassType outerClass2 : ClassType.values()) { | |
| for (ClassType innerClass1 : ClassType.values()) { | |
| for (ClassType innerClass2 : ClassType.values()) { | |
| ClassType childClass1 = i == 0 ? outerClass1 : innerClass1; | |
| ClassType childClass2 = j == 0 ? outerClass2 : innerClass2; | |
| if (!childClass1.canInherit(innerClass2) || | |
| !childClass2.canInherit(innerClass1)) { | |
| continue; | |
| } | |
| for (ImportType importType1 : ImportType.values()) { | |
| for (ImportType importType2 : ImportType.values()) { | |
| String sourceA = generateSource( | |
| sourceTemplatesA[i], | |
| outerClass1, | |
| innerClass1, | |
| childClass1.inheritedString(innerClass2), | |
| importType1, | |
| importedClassName2); | |
| String sourceB = generateSource( | |
| sourceTemplatesB[j], | |
| outerClass2, | |
| innerClass2, | |
| childClass2.inheritedString(innerClass1), | |
| importType2, | |
| importedClassName1); | |
| testCases.add(new TestCase(expectedErrorMessage, | |
| new ToolBox.JavaSource("A", sourceA), | |
| new ToolBox.JavaSource("B", sourceB))); | |
| testCases.add(new TestCase(expectedErrorMessage, | |
| new ToolBox.JavaSource("B", sourceB), | |
| new ToolBox.JavaSource("A", sourceA))); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return testCases; | |
| } | |
| public String generateSource(String template, | |
| ClassType outerClass, | |
| ClassType innerClass, | |
| String inheritString, | |
| ImportType importType, | |
| String innerClassName) { | |
| return template | |
| .replace("#OUTER_CLASS", outerClass.getType()) | |
| .replace("#INNER_CLASS", innerClass.getType()) | |
| .replace("#INHERIT", inheritString) | |
| .replace("#IMPORT_TYPE", importType.getImport(innerClassName)) | |
| .replace("#ENUM_SEMI", outerClass == ClassType.ENUM ? ";" : ""); | |
| } | |
| /** | |
| * Compiles sources with -XDrawDiagnostics flag and | |
| * returns the output of compilation. | |
| * | |
| * @param sources sources | |
| * @return the result of compilation | |
| */ | |
| private String compile(ToolBox.JavaSource...sources) { | |
| JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); | |
| StringWriter writer = new StringWriter(); | |
| JavaCompiler.CompilationTask ct = jc.getTask(writer, null, null, | |
| Arrays.asList("-XDrawDiagnostics"), | |
| null, Arrays.asList(sources)); | |
| if (ct.call()) { | |
| throw new RuntimeException("Expected compilation failure."); | |
| } | |
| return writer.toString().replace(ToolBox.lineSeparator, "\n"); | |
| } | |
| public void echo() { | |
| echo(""); | |
| } | |
| public void echo(CharSequence message) { | |
| echo(message.toString()); | |
| } | |
| public void echo(String message) { | |
| printf(message + "\n"); | |
| } | |
| public void printf(String template, Object...args) { | |
| System.err.print(String.format(template, args).replace("\n", ToolBox.lineSeparator)); | |
| } | |
| /** | |
| * The class represents a test case. | |
| */ | |
| public static class TestCase { | |
| public final ToolBox.JavaSource[] sources; | |
| public final String expectedMessage; | |
| public TestCase(String expectedMessage, ToolBox.JavaSource...sources) { | |
| this.sources = sources; | |
| this.expectedMessage = expectedMessage; | |
| } | |
| } | |
| /** | |
| * The enum represents all possible imports. | |
| */ | |
| public enum ImportType { | |
| SINGLE_IMPORT("import %s;"), | |
| IMPORT_ON_DEMAND("import %s.*;"), | |
| SINGLE_STATIC_IMPORT("import static %s;"), | |
| STATIC_IMPORT_ON_DEMAND("import static %s.*;"); | |
| private final String type; | |
| private ImportType(String type) { | |
| this.type = type; | |
| } | |
| public String getImport(String className) { | |
| if (this == ImportType.IMPORT_ON_DEMAND || this == ImportType.STATIC_IMPORT_ON_DEMAND) { | |
| int lastDot = className.lastIndexOf('.'); | |
| className = className.substring(0, lastDot); | |
| } | |
| return String.format(type, className); | |
| } | |
| } | |
| /** | |
| * The enum represents all possible class types that can be used in | |
| * inheritance. | |
| */ | |
| public enum ClassType { | |
| CLASS("class"), INTERFACE("interface"), ENUM("enum"); | |
| public boolean canInherit(ClassType innerClass) { | |
| return innerClass != ENUM && !(this == ENUM && innerClass == ClassType.CLASS | |
| || this == INTERFACE && innerClass == ClassType.CLASS); | |
| } | |
| public String inheritedString(ClassType innerClass) { | |
| if (!canInherit(innerClass)) { | |
| throw new IllegalArgumentException(String.format("%s cannot inherit %s", this, innerClass)); | |
| } | |
| return this == innerClass ? "extends" : "implements"; | |
| } | |
| private final String type; | |
| private ClassType(String type) { | |
| this.type = type; | |
| } | |
| public String getType() { | |
| return type; | |
| } | |
| } | |
| } |