1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @summary tests for multi-module mode compilation
  27  * @library /tools/lib
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.main
  30  *          jdk.jdeps/com.sun.tools.classfile
  31  *          jdk.jdeps/com.sun.tools.javap
  32  * @build toolbox.ToolBox toolbox.JavacTask toolbox.ModuleBuilder ModuleTestBase
  33  * @run main OpenModulesTest
  34  */
  35 
  36 import java.io.OutputStream;
  37 import java.nio.file.Files;
  38 import java.nio.file.Path;
  39 import java.util.Arrays;
  40 import java.util.HashMap;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.Objects;
  44 
  45 import com.sun.tools.classfile.Attribute;
  46 import com.sun.tools.classfile.Attributes;
  47 import com.sun.tools.classfile.ClassFile;
  48 import com.sun.tools.classfile.ClassWriter;
  49 import com.sun.tools.classfile.Module_attribute;
  50 import toolbox.JavacTask;
  51 import toolbox.JavapTask;
  52 import toolbox.Task;
  53 import toolbox.Task.Expect;
  54 import toolbox.Task.OutputKind;
  55 
  56 public class OpenModulesTest extends ModuleTestBase {
  57 
  58     public static void main(String... args) throws Exception {
  59         new OpenModulesTest().runTests();
  60     }
  61 
  62     @Test
  63     public void testStrongModule(Path base) throws Exception {
  64         Path m1 = base.resolve("m1x");
  65         tb.writeJavaFiles(m1,
  66                           "module m1x { exports api1; opens api2; }",
  67                           "package api1; public class Api1 {}",
  68                           "package api2; public class Api2 {}",
  69                           "package impl; public class Impl {}");
  70         Path classes = base.resolve("classes");
  71         Path m1Classes = classes.resolve("m1x");
  72         tb.createDirectories(m1Classes);
  73 
  74         String log = new JavacTask(tb)
  75                 .outdir(m1Classes)
  76                 .files(findJavaFiles(m1))
  77                 .run()
  78                 .writeAll()
  79                 .getOutput(Task.OutputKind.DIRECT);
  80 
  81         if (!log.isEmpty())
  82             throw new Exception("expected output not found: " + log);
  83 
  84         String decompiled = new JavapTask(tb)
  85                 .options("--system", "none", "-bootclasspath", "")
  86                 .classes(m1Classes.resolve("module-info.class").toString())
  87                 .run()
  88                 .writeAll()
  89                 .getOutput(OutputKind.DIRECT)
  90                 .replace(System.getProperty("line.separator"), "\n")
  91                 .replaceAll("@[^;]*;", ";");
  92 
  93         String expected = "module m1x {\n" +
  94                           "  requires java.base;\n" +
  95                           "  exports api1;\n" +
  96                           "  opens api2;\n" +
  97                           "}";
  98 
  99         if (!decompiled.contains(expected)) {
 100             throw new Exception("expected output not found: " + decompiled);
 101         }
 102 
 103         //compiling against a strong module read from binary:
 104         Path m2 = base.resolve("m2x");
 105         tb.writeJavaFiles(m2,
 106                           "module m2x { requires m1x; }",
 107                           "package test; public class Test { api1.Api1 a1; api2.Api2 a2; }");
 108         Path m2Classes = classes.resolve("m2x");
 109         tb.createDirectories(m2Classes);
 110 
 111         List<String> log2 = new JavacTask(tb)
 112                 .options("--module-path", m1Classes.toString(),
 113                          "-XDrawDiagnostics")
 114                 .outdir(m2Classes)
 115                 .files(findJavaFiles(m2))
 116                 .run(Expect.FAIL)
 117                 .writeAll()
 118                 .getOutputLines(Task.OutputKind.DIRECT);
 119 
 120         List<String> expected2 = Arrays.asList("Test.java:1:53: compiler.err.doesnt.exist: api2",
 121                                                "1 error");
 122         if (!Objects.equals(log2, expected2))
 123             throw new Exception("expected output not found: " + log2);
 124     }
 125 
 126     @Test
 127     public void testOpenModule(Path base) throws Exception {
 128         Path m1 = base.resolve("m1x");
 129         tb.writeJavaFiles(m1,
 130                           "open module m1x { exports api1; }",
 131                           "package api1; public class Api1 {}",
 132                           "package api2; public class Api2 {}",
 133                           "package impl; public class Impl {}");
 134         Path classes = base.resolve("classes");
 135         Path m1Classes = classes.resolve("m1x");
 136         tb.createDirectories(m1Classes);
 137 
 138         String log = new JavacTask(tb)
 139                 .outdir(m1Classes)
 140                 .files(findJavaFiles(m1))
 141                 .run()
 142                 .writeAll()
 143                 .getOutput(Task.OutputKind.DIRECT);
 144 
 145         if (!log.isEmpty())
 146             throw new Exception("expected output not found: " + log);
 147 
 148         String decompiled = new JavapTask(tb)
 149                 .options("--system", "none", "-bootclasspath", "")
 150                 .classes(m1Classes.resolve("module-info.class").toString())
 151                 .run()
 152                 .writeAll()
 153                 .getOutput(OutputKind.DIRECT)
 154                 .replace(System.getProperty("line.separator"), "\n")
 155                 .replaceAll("@[^;]*;", ";");
 156 
 157         String expected = "open module m1x {\n" +
 158                           "  requires java.base;\n" +
 159                           "  exports api1;\n" +
 160                           "}";
 161 
 162         if (!decompiled.contains(expected)) {
 163             throw new Exception("expected output not found: " + decompiled);
 164         }
 165 
 166         //compiling against a ordinary module read from binary:
 167         Path m2 = base.resolve("m2x");
 168         tb.writeJavaFiles(m2,
 169                           "module m2x { requires m1x; }",
 170                           "package test; public class Test { api1.Api1 a1; api2.Api2 a2; }");
 171         Path m2Classes = classes.resolve("m2x");
 172         tb.createDirectories(m2Classes);
 173 
 174         List<String> log2 = new JavacTask(tb)
 175                 .options("--module-path", m1Classes.toString(),
 176                          "-XDrawDiagnostics")
 177                 .outdir(m2Classes)
 178                 .files(findJavaFiles(m2))
 179                 .run(Expect.FAIL)
 180                 .writeAll()
 181                 .getOutputLines(Task.OutputKind.DIRECT);
 182 
 183         List<String> expected2 = Arrays.asList("Test.java:1:53: compiler.err.doesnt.exist: api2",
 184                                                "1 error");
 185         if (!Objects.equals(log2, expected2))
 186             throw new Exception("expected output not found: " + log2);
 187     }
 188 
 189     @Test
 190     public void testOpenModuleNoOpens(Path base) throws Exception {
 191         Path m1 = base.resolve("m1x");
 192         tb.writeJavaFiles(m1,
 193                           "open module m1x { exports api1; opens api2; }",
 194                           "package api1; public class Api1 {}",
 195                           "package api2; public class Api2 {}",
 196                           "package impl; public class Impl {}");
 197         Path classes = base.resolve("classes");
 198         Path m1Classes = classes.resolve("m1x");
 199         tb.createDirectories(m1Classes);
 200 
 201         List<String> log = new JavacTask(tb)
 202                 .options("-XDrawDiagnostics")
 203                 .outdir(m1Classes)
 204                 .files(findJavaFiles(m1))
 205                 .run(Expect.FAIL)
 206                 .writeAll()
 207                 .getOutputLines(Task.OutputKind.DIRECT);
 208 
 209         List<String> expected = Arrays.asList("module-info.java:1:33: compiler.err.no.opens.unless.strong",
 210                                               "1 error");
 211 
 212         if (!expected.equals(log))
 213             throw new Exception("expected output not found: " + log);
 214 
 215     }
 216 
 217     @Test
 218     public void testNonZeroOpensInOpen(Path base) throws Exception {
 219         Path m1 = base.resolve("m1x");
 220         tb.writeJavaFiles(m1,
 221                           "module m1x { opens api; }",
 222                           "package api; public class Api {}");
 223         Path classes = base.resolve("classes");
 224         Path m1Classes = classes.resolve("m1x");
 225         tb.createDirectories(m1Classes);
 226 
 227         new JavacTask(tb)
 228             .options("-XDrawDiagnostics")
 229             .outdir(m1Classes)
 230             .files(findJavaFiles(m1))
 231             .run(Expect.SUCCESS)
 232             .writeAll();
 233 
 234         Path miClass = m1Classes.resolve("module-info.class");
 235         ClassFile cf = ClassFile.read(miClass);
 236         Module_attribute module = (Module_attribute) cf.attributes.map.get(Attribute.Module);
 237         Module_attribute newModule = new Module_attribute(module.attribute_name_index,
 238                                                           module.module_name,
 239                                                           module.module_flags | Module_attribute.ACC_OPEN,
 240                                                           module.module_version_index,
 241                                                           module.requires,
 242                                                           module.exports,
 243                                                           module.opens,
 244                                                           module.uses_index,
 245                                                           module.provides);
 246         Map<String, Attribute> attrs = new HashMap<>(cf.attributes.map);
 247 
 248         attrs.put(Attribute.Module, newModule);
 249 
 250         Attributes newAttributes = new Attributes(attrs);
 251         ClassFile newClassFile = new ClassFile(cf.magic,
 252                                                cf.minor_version,
 253                                                cf.major_version,
 254                                                cf.constant_pool,
 255                                                cf.access_flags,
 256                                                cf.this_class,
 257                                                cf.super_class,
 258                                                cf.interfaces,
 259                                                cf.fields,
 260                                                cf.methods,
 261                                                newAttributes);
 262 
 263         try (OutputStream out = Files.newOutputStream(miClass)) {
 264             new ClassWriter().write(newClassFile, out);
 265         }
 266 
 267         Path test = base.resolve("test");
 268         tb.writeJavaFiles(test,
 269                           "package impl; public class Impl extends api.Api {}");
 270         Path testClasses = base.resolve("test-classes");
 271         tb.createDirectories(testClasses);
 272 
 273         List<String> log = new JavacTask(tb)
 274                 .options("-XDrawDiagnostics",
 275                          "--module-path", classes.toString(),
 276                          "--add-modules", "m1x")
 277                 .outdir(testClasses)
 278                 .files(findJavaFiles(test))
 279                 .run(Expect.FAIL)
 280                 .writeAll()
 281                 .getOutputLines(Task.OutputKind.DIRECT);
 282 
 283         List<String> expected = Arrays.asList(
 284                 "- compiler.err.cant.access: m1x.module-info, "
 285                         + "(compiler.misc.bad.class.file.header: module-info.class, "
 286                         + "(compiler.misc.module.non.zero.opens: m1x))",
 287                                               "1 error");
 288 
 289         if (!expected.equals(log))
 290             throw new Exception("expected output not found: " + log);
 291     }
 292 
 293 }