1 /* 2 * Copyright (c) 2015, 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 import com.sun.tools.classfile.ClassFile; 25 import com.sun.tools.classfile.ConstantPool; 26 import com.sun.tools.classfile.ConstantPoolException; 27 import com.sun.tools.classfile.Module_attribute; 28 import com.sun.tools.javac.util.Pair; 29 30 import java.io.IOException; 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.lang.reflect.Method; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.nio.file.Paths; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.LinkedHashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.regex.Pattern; 45 import java.util.stream.Collectors; 46 47 import toolbox.JavacTask; 48 import toolbox.Task; 49 import toolbox.ToolBox; 50 51 public class ModuleTestBase { 52 protected final ToolBox tb = new ToolBox(); 53 private final TestResult tr = new TestResult(); 54 55 56 protected void run() throws Exception { 57 boolean noTests = true; 58 for (Method method : this.getClass().getMethods()) { 59 if (method.isAnnotationPresent(Test.class)) { 60 noTests = false; 61 try { 62 tr.addTestCase(method.getName()); 63 method.invoke(this, Paths.get(method.getName())); 64 } catch (Throwable th) { 65 tr.addFailure(th); 66 } 67 } 68 } 69 if (noTests) throw new AssertionError("Tests are not found."); 70 tr.checkStatus(); 71 } 72 73 protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception { 74 ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class")); 75 Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module"); 76 ConstantPool constantPool = classFile.constant_pool; 77 78 testRequires(moduleDescriptor, moduleAttribute, constantPool); 79 testExports(moduleDescriptor, moduleAttribute, constantPool); 80 testProvides(moduleDescriptor, moduleAttribute, constantPool); 81 testUses(moduleDescriptor, moduleAttribute, constantPool); 82 } 83 84 private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 85 tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires."); 86 87 List<Pair<String, Integer>> actualRequires = new ArrayList<>(); 88 for (Module_attribute.RequiresEntry require : module.requires) { 89 actualRequires.add(Pair.of( 90 require.getRequires(constantPool).replace('/', '.'), 91 require.requires_flags)); 92 } 93 tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match"); 94 } 95 96 private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry { 97 tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports."); 98 for (Module_attribute.ExportsEntry export : module.exports) { 99 String pkg = constantPool.getUTF8Value(export.exports_index); 100 if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) { 101 Export expectedExport = moduleDescriptor.exports.get(pkg); 102 tr.checkEquals(expectedExport.mask, export.exports_flags, "Wrong export flags"); 103 List<String> expectedTo = expectedExport.to; 104 tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to"); 105 List<String> actualTo = new ArrayList<>(); 106 for (int toIdx : export.exports_to_index) { 107 actualTo.add(constantPool.getUTF8Value(toIdx).replace('/', '.')); 108 } 109 tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match."); 110 } 111 } 112 } 113 114 private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 115 tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses."); 116 List<String> actualUses = new ArrayList<>(); 117 for (int usesIdx : module.uses_index) { 118 String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.'); 119 actualUses.add(uses); 120 } 121 tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match"); 122 } 123 124 private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException { 125 int moduleProvidesCount = Arrays.asList(module.provides).stream() 126 .mapToInt(e -> e.with_index.length) 127 .sum(); 128 int moduleDescriptorProvidesCount = moduleDescriptor.provides.values().stream() 129 .mapToInt(impls -> impls.size()) 130 .sum(); 131 tr.checkEquals(moduleProvidesCount, moduleDescriptorProvidesCount, "Wrong amount of provides."); 132 Map<String, List<String>> actualProvides = new HashMap<>(); 133 for (Module_attribute.ProvidesEntry provide : module.provides) { 134 String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.'); 135 List<String> impls = new ArrayList<>(); 136 for (int i = 0; i < provide.with_count; i++) { 137 String with = constantPool.getClassInfo(provide.with_index[i]).getBaseName().replace('/', '.'); 138 impls.add(with); 139 } 140 actualProvides.put(provides, impls); 141 } 142 tr.checkContains(actualProvides.entrySet(), moduleDescriptor.provides.entrySet(), "Lists of provides don't match"); 143 } 144 145 protected void compile(Path base, String... options) throws IOException { 146 new JavacTask(tb) 147 .options(options) 148 .files(findJavaFiles(base)) 149 .run(Task.Expect.SUCCESS) 150 .writeAll(); 151 } 152 153 private static Path[] findJavaFiles(Path src) throws IOException { 154 return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java")) 155 .toArray(Path[]::new); 156 } 157 158 @Retention(RetentionPolicy.RUNTIME) 159 @interface Test { 160 } 161 162 interface Mask { 163 int getMask(); 164 } 165 166 public enum RequiresFlag implements Mask { 167 TRANSITIVE("transitive", Module_attribute.ACC_TRANSITIVE), 168 STATIC("static", Module_attribute.ACC_STATIC_PHASE); 169 170 private final String token; 171 private final int mask; 172 173 RequiresFlag(String token, int mask) { 174 this.token = token; 175 this.mask = mask; 176 } 177 178 @Override 179 public int getMask() { 180 return mask; 181 } 182 } 183 184 public enum ExportFlag implements Mask { 185 SYNTHETIC("", Module_attribute.ACC_SYNTHETIC); 186 187 private final String token; 188 private final int mask; 189 190 ExportFlag(String token, int mask) { 191 this.token = token; 192 this.mask = mask; 193 } 194 195 @Override 196 public int getMask() { 197 return mask; 198 } 199 } 200 201 private class Export { 202 String pkg; 203 int mask; 204 List<String> to = new ArrayList<>(); 205 206 public Export(String pkg, int mask) { 207 this.pkg = pkg; 208 this.mask = mask; 209 } 210 } 211 212 protected class ModuleDescriptor { 213 214 private final String name; 215 //pair is name of module and flag(public,mandated,synthetic) 216 private final List<Pair<String, Integer>> requires = new ArrayList<>(); 217 218 { 219 requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED)); 220 } 221 222 private final Map<String, Export> exports = new HashMap<>(); 223 224 //List of service and implementation 225 private final Map<String, List<String>> provides = new LinkedHashMap<>(); 226 private final List<String> uses = new ArrayList<>(); 227 228 private static final String LINE_END = ";\n"; 229 230 StringBuilder content = new StringBuilder("module "); 231 232 public ModuleDescriptor(String moduleName) { 233 this.name = moduleName; 234 content.append(name).append('{').append('\n'); 235 } 236 237 public ModuleDescriptor requires(String module) { 238 this.requires.add(Pair.of(module, 0)); 239 content.append(" requires ").append(module).append(LINE_END); 240 241 return this; 242 } 243 244 public ModuleDescriptor requires(String module, RequiresFlag... flags) { 245 this.requires.add(new Pair<>(module, computeMask(flags))); 246 247 content.append(" requires "); 248 for (RequiresFlag flag : flags) { 249 content.append(flag.token).append(" "); 250 } 251 content.append(module).append(LINE_END); 252 253 return this; 254 } 255 256 public ModuleDescriptor exports(String pkg, ExportFlag... flags) { 257 this.exports.putIfAbsent(pkg, new Export(pkg, computeMask(flags))); 258 content.append(" exports "); 259 for (ExportFlag flag : flags) { 260 content.append(flag.token).append(" "); 261 } 262 content.append(pkg).append(LINE_END); 263 return this; 264 } 265 266 public ModuleDescriptor exportsTo(String pkg, String to, ExportFlag... flags) { 267 List<String> tos = Pattern.compile(",") 268 .splitAsStream(to) 269 .map(String::trim) 270 .collect(Collectors.toList()); 271 this.exports.computeIfAbsent(pkg, k -> new Export(pkg, computeMask(flags))) 272 .to.addAll(tos); 273 274 content.append(" exports "); 275 for (ExportFlag flag : flags) { 276 content.append(flag.token).append(" "); 277 } 278 content.append(pkg).append(" to ").append(to).append(LINE_END); 279 return this; 280 } 281 282 public ModuleDescriptor provides(String provides, String... with) { 283 this.provides.put(provides, Arrays.asList(with)); 284 content.append(" provides ") 285 .append(provides) 286 .append(" with ") 287 .append(String.join(",", with)) 288 .append(LINE_END); 289 return this; 290 } 291 292 public ModuleDescriptor uses(String... uses) { 293 Collections.addAll(this.uses, uses); 294 for (String use : uses) { 295 content.append(" uses ").append(use).append(LINE_END); 296 } 297 return this; 298 } 299 300 public ModuleDescriptor write(Path path) throws IOException { 301 String src = content.append('}').toString(); 302 303 tb.createDirectories(path); 304 tb.writeJavaFiles(path, src); 305 return this; 306 } 307 308 private int computeMask(Mask[] masks) { 309 return Arrays.stream(masks) 310 .map(Mask::getMask) 311 .reduce((a, b) -> a | b) 312 .orElseGet(() -> 0); 313 } 314 } 315 }