1/*
2 * Copyright (c) 2015, 2017, 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
24import com.sun.tools.classfile.ClassFile;
25import com.sun.tools.classfile.ConstantPool;
26import com.sun.tools.classfile.ConstantPoolException;
27import com.sun.tools.classfile.Module_attribute;
28
29import java.io.IOException;
30import java.lang.annotation.Retention;
31import java.lang.annotation.RetentionPolicy;
32import java.lang.reflect.Method;
33import java.nio.file.Files;
34import java.nio.file.Path;
35import java.nio.file.Paths;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.HashMap;
39import java.util.LinkedHashMap;
40import java.util.List;
41import java.util.Map;
42import java.util.Objects;
43import java.util.regex.Pattern;
44import java.util.stream.Collectors;
45
46import toolbox.JavacTask;
47import toolbox.Task;
48import toolbox.ToolBox;
49
50public class ModuleTestBase {
51    protected final ToolBox tb = new ToolBox();
52    private final TestResult tr = new TestResult();
53
54
55    protected void run() throws Exception {
56        boolean noTests = true;
57        for (Method method : this.getClass().getMethods()) {
58            if (method.isAnnotationPresent(Test.class)) {
59                noTests = false;
60                try {
61                    tr.addTestCase(method.getName());
62                    method.invoke(this, Paths.get(method.getName()));
63                } catch (Throwable th) {
64                    tr.addFailure(th);
65                }
66            }
67        }
68        if (noTests) throw new AssertionError("Tests are not found.");
69        tr.checkStatus();
70    }
71
72    protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
73        ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
74        Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
75        ConstantPool constantPool = classFile.constant_pool;
76        testModuleName(moduleDescriptor, moduleAttribute, constantPool);
77        testModuleFlags(moduleDescriptor, moduleAttribute);
78        testRequires(moduleDescriptor, moduleAttribute, constantPool);
79        testExports(moduleDescriptor, moduleAttribute, constantPool);
80        testOpens(moduleDescriptor, moduleAttribute, constantPool);
81        testProvides(moduleDescriptor, moduleAttribute, constantPool);
82        testUses(moduleDescriptor, moduleAttribute, constantPool);
83    }
84
85    private void testModuleName(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
86        tr.checkEquals(constantPool.getModuleInfo(module.module_name).getName(), moduleDescriptor.name, "Unexpected module name");
87    }
88
89    private void testModuleFlags(ModuleDescriptor moduleDescriptor, Module_attribute module) {
90        tr.checkEquals(module.module_flags, moduleDescriptor.flags, "Unexpected module flags");
91    }
92
93    private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
94        tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");
95
96        List<Requires> actualRequires = new ArrayList<>();
97        for (Module_attribute.RequiresEntry require : module.requires) {
98            actualRequires.add(new Requires(
99                    require.getRequires(constantPool),
100                    require.requires_flags));
101        }
102        tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
103    }
104
105    private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
106        tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
107        for (Module_attribute.ExportsEntry export : module.exports) {
108            String pkg = constantPool.getPackageInfo(export.exports_index).getName();
109            if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
110                Export expectedExport = moduleDescriptor.exports.get(pkg);
111                tr.checkEquals(expectedExport.mask, export.exports_flags, "Wrong export flags");
112                List<String> expectedTo = expectedExport.to;
113                tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
114                List<String> actualTo = new ArrayList<>();
115                for (int toIdx : export.exports_to_index) {
116                    actualTo.add(constantPool.getModuleInfo(toIdx).getName());
117                }
118                tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
119            }
120        }
121    }
122
123    private void testOpens(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
124        tr.checkEquals(module.opens_count, moduleDescriptor.opens.size(), "Wrong amount of opens.");
125        for (Module_attribute.OpensEntry open : module.opens) {
126            String pkg = constantPool.getPackageInfo(open.opens_index).getName();
127            if (tr.checkTrue(moduleDescriptor.opens.containsKey(pkg), "Unexpected open " + pkg)) {
128                Open expectedOpen = moduleDescriptor.opens.get(pkg);
129                tr.checkEquals(expectedOpen.mask, open.opens_flags, "Wrong open flags");
130                List<String> expectedTo = expectedOpen.to;
131                tr.checkEquals(open.opens_to_count, expectedTo.size(), "Wrong amount of opens to");
132                List<String> actualTo = new ArrayList<>();
133                for (int toIdx : open.opens_to_index) {
134                    actualTo.add(constantPool.getModuleInfo(toIdx).getName());
135                }
136                tr.checkContains(actualTo, expectedTo, "Lists of \"opens to\" don't match.");
137            }
138        }
139    }
140
141    private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
142        tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
143        List<String> actualUses = new ArrayList<>();
144        for (int usesIdx : module.uses_index) {
145            String uses = constantPool.getClassInfo(usesIdx).getBaseName();
146            actualUses.add(uses);
147        }
148        tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
149    }
150
151    private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
152        int moduleProvidesCount = Arrays.asList(module.provides).stream()
153                .mapToInt(e -> e.with_index.length)
154                .sum();
155        int moduleDescriptorProvidesCount = moduleDescriptor.provides.values().stream()
156                .mapToInt(impls -> impls.size())
157                .sum();
158        tr.checkEquals(moduleProvidesCount, moduleDescriptorProvidesCount, "Wrong amount of provides.");
159        Map<String, List<String>> actualProvides = new HashMap<>();
160        for (Module_attribute.ProvidesEntry provide : module.provides) {
161            String provides = constantPool.getClassInfo(provide.provides_index).getBaseName();
162            List<String> impls = new ArrayList<>();
163            for (int i = 0; i < provide.with_count; i++) {
164                String with = constantPool.getClassInfo(provide.with_index[i]).getBaseName();
165                impls.add(with);
166            }
167            actualProvides.put(provides, impls);
168        }
169        tr.checkContains(actualProvides.entrySet(), moduleDescriptor.provides.entrySet(), "Lists of provides don't match");
170    }
171
172    protected void compile(Path base, String... options) throws IOException {
173        new JavacTask(tb)
174                .options(options)
175                .files(findJavaFiles(base))
176                .run(Task.Expect.SUCCESS)
177                .writeAll();
178    }
179
180    private static Path[] findJavaFiles(Path src) throws IOException {
181        return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
182                .toArray(Path[]::new);
183    }
184
185    @Retention(RetentionPolicy.RUNTIME)
186    @interface Test {
187    }
188
189    interface Mask {
190        int getMask();
191    }
192
193    public enum ModuleFlag implements Mask {
194        OPEN("open", Module_attribute.ACC_OPEN);
195
196        private final String token;
197        private final int mask;
198
199        ModuleFlag(String token, int mask) {
200            this.token = token;
201            this.mask = mask;
202        }
203
204        @Override
205        public int getMask() {
206            return mask;
207        }
208    }
209
210    public enum RequiresFlag implements Mask {
211        TRANSITIVE("transitive", Module_attribute.ACC_TRANSITIVE),
212        STATIC("static", Module_attribute.ACC_STATIC_PHASE),
213        MANDATED("", Module_attribute.ACC_MANDATED);
214
215        private final String token;
216        private final int mask;
217
218        RequiresFlag(String token, int mask) {
219            this.token = token;
220            this.mask = mask;
221        }
222
223        @Override
224        public int getMask() {
225            return mask;
226        }
227    }
228
229    public enum ExportsFlag implements Mask {
230        SYNTHETIC("", Module_attribute.ACC_SYNTHETIC);
231
232        private final String token;
233        private final int mask;
234
235        ExportsFlag(String token, int mask) {
236            this.token = token;
237            this.mask = mask;
238        }
239
240        @Override
241        public int getMask() {
242            return mask;
243        }
244    }
245
246    public enum OpensFlag implements Mask {
247        SYNTHETIC("", Module_attribute.ACC_SYNTHETIC);
248
249        private final String token;
250        private final int mask;
251
252        OpensFlag(String token, int mask) {
253            this.token = token;
254            this.mask = mask;
255        }
256
257        @Override
258        public int getMask() {
259            return mask;
260        }
261    }
262
263    private class Export {
264        private final String pkg;
265        private final int mask;
266        private final List<String> to = new ArrayList<>();
267
268        Export(String pkg, int mask) {
269            this.pkg = pkg;
270            this.mask = mask;
271        }
272    }
273
274    private class Open {
275        private final String pkg;
276        private final int mask;
277        private final List<String> to = new ArrayList<>();
278
279        Open(String pkg, int mask) {
280            this.pkg = pkg;
281            this.mask = mask;
282        }
283    }
284
285    private class Requires {
286        private final String module;
287        private final int mask;
288
289        Requires(String module, int mask) {
290            this.module = module;
291            this.mask = mask;
292        }
293
294        @Override
295        public boolean equals(Object o) {
296            if (this == o) return true;
297            if (o == null || getClass() != o.getClass()) return false;
298            Requires requires = (Requires) o;
299            return mask == requires.mask &&
300                    Objects.equals(module, requires.module);
301        }
302
303        @Override
304        public int hashCode() {
305            return Objects.hash(module, mask);
306        }
307    }
308
309    protected class ModuleDescriptor {
310
311        private final String name;
312        private final int flags;
313
314        private final List<Requires> requires = new ArrayList<>();
315
316        {
317            requires.add(new Requires("java.base", computeMask(RequiresFlag.MANDATED)));
318        }
319
320        private final Map<String, Export> exports = new HashMap<>();
321        private final Map<String, Open> opens = new HashMap<>();
322
323        //List of service and implementation
324        private final Map<String, List<String>> provides = new LinkedHashMap<>();
325        private final List<String> uses = new ArrayList<>();
326
327        private static final String LINE_END = ";\n";
328
329        StringBuilder content = new StringBuilder("");
330
331        public ModuleDescriptor(String moduleName, ModuleFlag... flags) {
332            this.name = moduleName;
333            this.flags = computeMask(flags);
334            for (ModuleFlag flag : flags) {
335                content.append(flag.token).append(" ");
336            }
337            content.append("module ").append(moduleName).append('{').append('\n');
338        }
339
340        public ModuleDescriptor requires(String module) {
341            this.requires.add(new Requires(module, 0));
342            content.append("    requires ").append(module).append(LINE_END);
343
344            return this;
345        }
346
347        public ModuleDescriptor requires(String module, RequiresFlag... flags) {
348            this.requires.add(new Requires(module, computeMask(flags)));
349
350            content.append("    requires ");
351            for (RequiresFlag flag : flags) {
352                content.append(flag.token).append(" ");
353            }
354            content.append(module).append(LINE_END);
355
356            return this;
357        }
358
359        public ModuleDescriptor exports(String pkg, ExportsFlag... flags) {
360            this.exports.put(toInternalForm(pkg), new Export(toInternalForm(pkg), computeMask(flags)));
361            content.append("    exports ");
362            for (ExportsFlag flag : flags) {
363                content.append(flag.token).append(" ");
364            }
365            content.append(pkg).append(LINE_END);
366            return this;
367        }
368
369        public ModuleDescriptor exportsTo(String pkg, String to, ExportsFlag... flags) {
370            List<String> tos = Pattern.compile(",")
371                    .splitAsStream(to)
372                    .map(String::trim)
373                    .collect(Collectors.toList());
374            this.exports.compute(toInternalForm(pkg), (k,v) -> new Export(k, computeMask(flags)))
375                    .to.addAll(tos);
376
377            content.append("    exports ");
378            for (ExportsFlag flag : flags) {
379                content.append(flag.token).append(" ");
380            }
381            content.append(pkg).append(" to ").append(to).append(LINE_END);
382            return this;
383        }
384
385        public ModuleDescriptor opens(String pkg, OpensFlag... flags) {
386            this.opens.put(toInternalForm(pkg), new Open(toInternalForm(pkg), computeMask(flags)));
387            content.append("    opens ");
388            for (OpensFlag flag : flags) {
389                content.append(flag.token).append(" ");
390            }
391            content.append(pkg).append(LINE_END);
392            return this;
393        }
394
395        public ModuleDescriptor opensTo(String pkg, String to, OpensFlag... flags) {
396            List<String> tos = Pattern.compile(",")
397                    .splitAsStream(to)
398                    .map(String::trim)
399                    .collect(Collectors.toList());
400            this.opens.compute(toInternalForm(pkg), (k,v) -> new Open(toInternalForm(k), computeMask(flags)))
401                    .to.addAll(tos);
402
403            content.append("    opens ");
404            for (OpensFlag flag : flags) {
405                content.append(flag.token).append(" ");
406            }
407            content.append(pkg).append(" to ").append(to).append(LINE_END);
408            return this;
409        }
410
411        public ModuleDescriptor provides(String provides, String... with) {
412            List<String> impls = Arrays.stream(with)
413                    .map(this::toInternalForm)
414                    .collect(Collectors.toList());
415            this.provides.put(toInternalForm(provides), impls);
416            content.append("    provides ")
417                    .append(provides)
418                    .append(" with ")
419                    .append(String.join(",", with))
420                    .append(LINE_END);
421            return this;
422        }
423
424        public ModuleDescriptor uses(String... uses) {
425            for (String use : uses) {
426                this.uses.add(toInternalForm(use));
427                content.append("    uses ").append(use).append(LINE_END);
428            }
429            return this;
430        }
431
432        public ModuleDescriptor write(Path path) throws IOException {
433            String src = content.append('}').toString();
434
435            tb.createDirectories(path);
436            tb.writeJavaFiles(path, src);
437            return this;
438        }
439
440        private String toInternalForm(String name) {
441            return name.replace('.', '/');
442        }
443
444        private int computeMask(Mask... masks) {
445            return Arrays.stream(masks)
446                    .map(Mask::getMask)
447                    .reduce((a, b) -> a | b)
448                    .orElseGet(() -> 0);
449        }
450    }
451}
452