/* * Copyright (c) 2015, 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. */ import com.sun.tools.classfile.ClassFile; import com.sun.tools.classfile.ConstantPool; import com.sun.tools.classfile.ConstantPoolException; import com.sun.tools.classfile.Module_attribute; import com.sun.tools.javac.util.Pair; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; import toolbox.JavacTask; import toolbox.Task; import toolbox.ToolBox; public class ModuleTestBase { protected final ToolBox tb = new ToolBox(); private final TestResult tr = new TestResult(); protected void run() throws Exception { boolean noTests = true; for (Method method : this.getClass().getMethods()) { if (method.isAnnotationPresent(Test.class)) { noTests = false; try { tr.addTestCase(method.getName()); method.invoke(this, Paths.get(method.getName())); } catch (Throwable th) { tr.addFailure(th); } } } if (noTests) throw new AssertionError("Tests are not found."); tr.checkStatus(); } protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception { ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class")); Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module"); ConstantPool constantPool = classFile.constant_pool; testRequires(moduleDescriptor, moduleAttribute, constantPool); testExports(moduleDescriptor, moduleAttribute, constantPool); testProvides(moduleDescriptor, moduleAttribute, constantPool); testUses(moduleDescriptor, moduleAttribute, constantPool); } private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires."); List> actualRequires = new ArrayList<>(); for (Module_attribute.RequiresEntry require : module.requires) { actualRequires.add(Pair.of( require.getRequires(constantPool), require.requires_flags)); } tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match"); } private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry { tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports."); for (Module_attribute.ExportsEntry export : module.exports) { String pkg = constantPool.getUTF8Value(export.exports_index); if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) { List expectedTo = moduleDescriptor.exports.get(pkg); tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to"); List actualTo = new ArrayList<>(); for (int toIdx : export.exports_to_index) { actualTo.add(constantPool.getUTF8Value(toIdx)); } tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match."); } } } private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses."); List actualUses = new ArrayList<>(); for (int usesIdx : module.uses_index) { String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.'); actualUses.add(uses); } tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match"); } private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { tr.checkEquals(module.provides_count, moduleDescriptor.provides.size(), "Wrong amount of provides."); List> actualProvides = new ArrayList<>(); for (Module_attribute.ProvidesEntry provide : module.provides) { String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.'); String with = constantPool.getClassInfo(provide.with_index).getBaseName().replace('/', '.'); actualProvides.add(Pair.of(provides, with)); } tr.checkContains(actualProvides, moduleDescriptor.provides, "Lists of provides don't match"); } protected void compile(Path base, String... options) throws IOException { new JavacTask(tb) .options(options) .files(findJavaFiles(base)) .run(Task.Expect.SUCCESS) .writeAll(); } private static Path[] findJavaFiles(Path src) throws IOException { return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java")) .toArray(Path[]::new); } @Retention(RetentionPolicy.RUNTIME) @interface Test { } class ModuleDescriptor { private final String name; //pair is name of module and flag(public,mandated,synthetic) private final List> requires = new ArrayList<>(); { requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED)); } private final Map> exports = new HashMap<>(); //List of service and implementation private final List> provides = new ArrayList<>(); private final List uses = new ArrayList<>(); private static final String LINE_END = ";\n"; StringBuilder content = new StringBuilder("module "); public ModuleDescriptor(String moduleName) { this.name = moduleName; content.append(name).append('{').append('\n'); } public ModuleDescriptor requires(String... requires) { for (String require : requires) { this.requires.add(Pair.of(require, 0)); content.append(" requires ").append(require).append(LINE_END); } return this; } public ModuleDescriptor requiresPublic(String... requiresPublic) { for (String require : requiresPublic) { this.requires.add(new Pair<>(require, Module_attribute.ACC_PUBLIC)); content.append(" requires public ").append(require).append(LINE_END); } return this; } public ModuleDescriptor exports(String... exports) { for (String export : exports) { this.exports.putIfAbsent(export, new ArrayList<>()); content.append(" exports ").append(export).append(LINE_END); } return this; } public ModuleDescriptor exportsTo(String exports, String to) { List tos = Pattern.compile(",") .splitAsStream(to) .map(String::trim) .collect(Collectors.toList()); this.exports.computeIfAbsent(exports, k -> new ArrayList<>()).addAll(tos); content.append(" exports ").append(exports).append(" to ").append(to).append(LINE_END); return this; } public ModuleDescriptor provides(String provides, String with) { this.provides.add(Pair.of(provides, with)); content.append(" provides ").append(provides).append(" with ").append(with).append(LINE_END); return this; } public ModuleDescriptor uses(String... uses) { Collections.addAll(this.uses, uses); for (String use : uses) { content.append(" uses ").append(use).append(LINE_END); } return this; } public ModuleDescriptor write(Path path) throws IOException { String src = content.append('}').toString(); tb.createDirectories(path); tb.writeJavaFiles(path, src); return this; } } }