| /* |
| * 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; |
| } |
| } |
| } |