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