1/*
2 * Copyright (c) 2014, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdeps;
27
28import java.lang.module.ModuleDescriptor;
29import java.net.URI;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.HashSet;
33import java.util.Map;
34import java.util.Set;
35
36/**
37 * Jdeps internal representation of module for dependency analysis.
38 */
39class Module extends Archive {
40    static final Module UNNAMED_MODULE = new UnnamedModule();
41    static final String JDK_UNSUPPORTED = "jdk.unsupported";
42
43    static final boolean DEBUG = Boolean.getBoolean("jdeps.debug");
44    static void trace(String fmt, Object... args) {
45        trace(DEBUG, fmt, args);
46    }
47
48    static void trace(boolean traceOn, String fmt, Object... args) {
49        if (traceOn) {
50            System.err.format(fmt, args);
51        }
52    }
53
54    private final ModuleDescriptor descriptor;
55    private final Map<String, Set<String>> exports;
56    private final Map<String, Set<String>> opens;
57    private final boolean isSystem;
58    private final URI location;
59
60    protected Module(String name) {
61        super(name);
62        this.descriptor = null;
63        this.location = null;
64        this.exports = Collections.emptyMap();
65        this.opens = Collections.emptyMap();
66        this.isSystem = true;
67    }
68
69    private Module(String name,
70                   URI location,
71                   ModuleDescriptor descriptor,
72                   Map<String, Set<String>> exports,
73                   Map<String, Set<String>> opens,
74                   boolean isSystem,
75                   ClassFileReader reader) {
76        super(name, location, reader);
77        this.descriptor = descriptor;
78        this.location = location;
79        this.exports = Collections.unmodifiableMap(exports);
80        this.opens = Collections.unmodifiableMap(opens);
81        this.isSystem = isSystem;
82    }
83
84    /**
85     * Returns module name
86     */
87    public String name() {
88        return descriptor != null ? descriptor.name() : getName();
89    }
90
91    public boolean isNamed() {
92        return true;
93    }
94
95    public boolean isAutomatic() {
96        return descriptor.isAutomatic();
97    }
98
99    public Module getModule() {
100        return this;
101    }
102
103    public ModuleDescriptor descriptor() {
104        return descriptor;
105    }
106
107    public URI location() {
108        return location;
109    }
110
111    public boolean isJDK() {
112        String mn = name();
113        return isSystem &&
114            (mn.startsWith("java.") || mn.startsWith("jdk.") || mn.startsWith("javafx."));
115    }
116
117    public boolean isSystem() {
118        return isSystem;
119    }
120
121    public Map<String, Set<String>> exports() {
122        return exports;
123    }
124
125    public Set<String> packages() {
126        return descriptor.packages();
127    }
128
129    public boolean isJDKUnsupported() {
130        return JDK_UNSUPPORTED.equals(this.name());
131    }
132
133    /**
134     * Converts this module to a normal module with the given dependences
135     *
136     * @throws IllegalArgumentException if this module is not an automatic module
137     */
138    public Module toNormalModule(Map<String, Boolean> requires) {
139        if (!isAutomatic()) {
140            throw new IllegalArgumentException(name() + " not an automatic module");
141        }
142        return new NormalModule(this, requires);
143    }
144
145    /**
146     * Tests if the package of the given name is exported.
147     */
148    public boolean isExported(String pn) {
149        return exports.containsKey(pn) && exports.get(pn).isEmpty();
150    }
151
152    /**
153     * Tests if the package of the given name is exported to the target
154     * in a qualified fashion.
155     */
156    public boolean isExported(String pn, String target) {
157        return isExported(pn)
158                || exports.containsKey(pn) && exports.get(pn).contains(target);
159    }
160
161    /**
162     * Tests if the package of the given name is open.
163     */
164    public boolean isOpen(String pn) {
165        return opens.containsKey(pn) && opens.get(pn).isEmpty();
166    }
167
168    /**
169     * Tests if the package of the given name is open to the target
170     * in a qualified fashion.
171     */
172    public boolean isOpen(String pn, String target) {
173        return isOpen(pn)
174            || opens.containsKey(pn) && opens.get(pn).contains(target);
175    }
176
177    @Override
178    public String toString() {
179        return name();
180    }
181
182    public final static class Builder {
183        final String name;
184        final ModuleDescriptor descriptor;
185        final boolean isSystem;
186        ClassFileReader reader;
187        URI location;
188
189        public Builder(ModuleDescriptor md) {
190            this(md, false);
191        }
192
193        public Builder(ModuleDescriptor md, boolean isSystem) {
194            this.name = md.name();
195            this.descriptor = md;
196            this.isSystem = isSystem;
197        }
198
199        public Builder location(URI location) {
200            this.location = location;
201            return this;
202        }
203
204        public Builder classes(ClassFileReader reader) {
205            this.reader = reader;
206            return this;
207        }
208
209        public Module build() {
210            if (descriptor.isAutomatic() && isSystem) {
211                throw new InternalError("JDK module: " + name + " can't be automatic module");
212            }
213
214            Map<String, Set<String>> exports = new HashMap<>();
215            Map<String, Set<String>> opens = new HashMap<>();
216
217            if (descriptor.isAutomatic()) {
218                // ModuleDescriptor::exports and opens returns an empty set
219                descriptor.packages().forEach(pn -> exports.put(pn, Collections.emptySet()));
220                descriptor.packages().forEach(pn -> opens.put(pn, Collections.emptySet()));
221            } else {
222                descriptor.exports().stream()
223                          .forEach(exp -> exports.computeIfAbsent(exp.source(), _k -> new HashSet<>())
224                                                 .addAll(exp.targets()));
225                descriptor.opens().stream()
226                    .forEach(exp -> opens.computeIfAbsent(exp.source(), _k -> new HashSet<>())
227                        .addAll(exp.targets()));
228            }
229            return new Module(name, location, descriptor, exports, opens, isSystem, reader);
230        }
231    }
232
233    private static class UnnamedModule extends Module {
234        private UnnamedModule() {
235            super("unnamed", null, null,
236                  Collections.emptyMap(), Collections.emptyMap(),
237                  false, null);
238        }
239
240        @Override
241        public String name() {
242            return "unnamed";
243        }
244
245        @Override
246        public boolean isNamed() {
247            return false;
248        }
249
250        @Override
251        public boolean isAutomatic() {
252            return false;
253        }
254
255        @Override
256        public boolean isExported(String pn) {
257            return true;
258        }
259    }
260
261    /**
262     * A normal module has a module-info.class
263     */
264    private static class NormalModule extends Module {
265        private final ModuleDescriptor md;
266
267        /**
268         * Converts the given automatic module to a normal module.
269         *
270         * Replace this module's dependences with the given requires and also
271         * declare service providers, if specified in META-INF/services configuration file
272         */
273        private NormalModule(Module m, Map<String, Boolean> requires) {
274            super(m.name(), m.location, m.descriptor, m.exports, m.opens, m.isSystem, m.reader());
275
276            ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(m.name());
277            requires.keySet().forEach(mn -> {
278                if (requires.get(mn).equals(Boolean.TRUE)) {
279                    builder.requires(Set.of(ModuleDescriptor.Requires.Modifier.TRANSITIVE), mn);
280                } else {
281                    builder.requires(mn);
282                }
283            });
284            // exports all packages
285            m.descriptor.packages().forEach(builder::exports);
286            m.descriptor.uses().forEach(builder::uses);
287            m.descriptor.provides().forEach(builder::provides);
288            this.md = builder.build();
289        }
290
291        @Override
292        public ModuleDescriptor descriptor() {
293            return md;
294        }
295    }
296}
297