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
24import com.sun.tools.jdeps.Analyzer;
25import com.sun.tools.jdeps.DepsAnalyzer;
26import com.sun.tools.jdeps.InverseDepsAnalyzer;
27import com.sun.tools.jdeps.JdepsConfiguration;
28import com.sun.tools.jdeps.JdepsFilter;
29import com.sun.tools.jdeps.JdepsWriter;
30import com.sun.tools.jdeps.ModuleAnalyzer;
31
32import java.io.Closeable;
33import java.io.File;
34import java.io.IOException;
35import java.io.PrintStream;
36import java.io.PrintWriter;
37import java.io.StringWriter;
38import java.io.UncheckedIOException;
39import java.lang.module.ModuleDescriptor;
40import java.nio.file.Files;
41import java.nio.file.Path;
42import java.util.HashSet;
43import java.util.Set;
44import java.util.jar.JarEntry;
45import java.util.jar.JarOutputStream;
46import java.util.regex.Pattern;
47import java.util.stream.Stream;
48
49/**
50 * Utilities to run jdeps command
51 */
52public final class JdepsUtil {
53    public static Command newCommand(String cmd) {
54        return new Command(cmd);
55    }
56
57    public static class Command implements Closeable {
58
59        final StringWriter sw = new StringWriter();
60        final PrintWriter pw = new PrintWriter(sw);
61        final JdepsFilter.Builder filter = new JdepsFilter.Builder().filter(true, true);
62        final JdepsConfiguration.Builder builder =  new JdepsConfiguration.Builder();
63        final Set<String> requires = new HashSet<>();
64
65        JdepsConfiguration configuration;
66        Analyzer.Type verbose = Analyzer.Type.PACKAGE;
67        boolean apiOnly = false;
68
69        public Command(String cmd) {
70            System.err.println("============ ");
71            System.err.println(cmd);
72        }
73
74        public Command verbose(String verbose) {
75            switch (verbose) {
76                case "-verbose":
77                    this.verbose = Analyzer.Type.VERBOSE;
78                    filter.filter(false, false);
79                    break;
80                case "-verbose:package":
81                    this.verbose = Analyzer.Type.PACKAGE;
82                    break;
83                case "-verbose:class":
84                    this.verbose = Analyzer.Type.CLASS;
85                    break;
86                case "-summary":
87                    this.verbose = Analyzer.Type.SUMMARY;
88                    break;
89                default:
90                    throw new IllegalArgumentException(verbose);
91            }
92            return this;
93        }
94
95        public Command filter(String value) {
96            switch (value) {
97                case "-filter:package":
98                    filter.filter(true, false);
99                    break;
100                case "-filter:archive":
101                case "-filter:module":
102                    filter.filter(false, true);
103                    break;
104                default:
105                    throw new IllegalArgumentException(value);
106            }
107            return this;
108        }
109
110        public Command addClassPath(String classpath) {
111            builder.addClassPath(classpath);
112            return this;
113        }
114
115        public Command addRoot(Path path) {
116            builder.addRoot(path);
117            return this;
118        }
119
120        public Command appModulePath(String modulePath) {
121            builder.appModulePath(modulePath);
122            return this;
123        }
124
125        public Command addmods(Set<String> mods) {
126            builder.addmods(mods);
127            return this;
128        }
129
130        public Command requires(Set<String> mods) {
131            requires.addAll(mods);
132            return this;
133        }
134
135        public Command matchPackages(Set<String> pkgs) {
136            filter.packages(pkgs);
137            return this;
138        }
139
140        public Command regex(String regex) {
141            filter.regex(Pattern.compile(regex));
142            return this;
143        }
144
145        public Command include(String regex) {
146            filter.includePattern(Pattern.compile(regex));
147            return this;
148        }
149
150        public Command apiOnly() {
151            this.apiOnly = true;
152            return this;
153        }
154
155        public JdepsConfiguration configuration() throws IOException {
156            if (configuration == null) {
157                this.configuration = builder.build();
158                requires.forEach(name -> {
159                    ModuleDescriptor md = configuration.findModuleDescriptor(name).get();
160                    filter.requires(name, md.packages());
161                });
162            }
163            return configuration;
164        }
165
166        private JdepsWriter writer() {
167            return JdepsWriter.newSimpleWriter(pw, verbose);
168        }
169
170        public DepsAnalyzer getDepsAnalyzer() throws IOException {
171            return new DepsAnalyzer(configuration(), filter.build(), writer(),
172                                    verbose, apiOnly);
173        }
174
175        public ModuleAnalyzer getModuleAnalyzer(Set<String> mods) throws IOException {
176            // if --check is set, add to the root set and all modules are observable
177            addmods(mods);
178            builder.allModules();
179            return new ModuleAnalyzer(configuration(), pw, mods);
180        }
181
182        public InverseDepsAnalyzer getInverseDepsAnalyzer() throws IOException {
183            return new InverseDepsAnalyzer(configuration(), filter.build(), writer(),
184                                           verbose, false);
185        }
186
187        public void dumpOutput(PrintStream out) {
188            out.println(sw.toString());
189        }
190
191        @Override
192        public void close() throws IOException {
193            configuration.close();
194        }
195    }
196
197    /**
198     * Create a jar file using the list of files provided.
199     */
200    public static void createJar(Path jarfile, Path root, Stream<Path> files)
201        throws IOException {
202        Path dir = jarfile.getParent();
203        if (dir != null && Files.notExists(dir)) {
204            Files.createDirectories(dir);
205        }
206        try (JarOutputStream target = new JarOutputStream(
207            Files.newOutputStream(jarfile))) {
208            files.forEach(file -> add(root.relativize(file), file, target));
209        }
210    }
211
212    private static void add(Path path, Path source, JarOutputStream target) {
213        try {
214            String name = path.toString().replace(File.separatorChar, '/');
215            JarEntry entry = new JarEntry(name);
216            entry.setTime(source.toFile().lastModified());
217            target.putNextEntry(entry);
218            Files.copy(source, target);
219            target.closeEntry();
220        } catch (IOException e) {
221            throw new UncheckedIOException(e);
222        }
223    }
224}
225