All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.tools.jdeps.ModuleInfoBuilder Maven / Gradle / Ivy

There is a newer version: 9-dev-r4023-3
Show newest version
/*
 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.tools.jdeps;

import static com.sun.tools.jdeps.JdepsTask.*;
import static com.sun.tools.jdeps.Analyzer.*;
import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.lang.module.ModuleFinder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;


public class ModuleInfoBuilder {
    final JdepsConfiguration configuration;
    final Path outputdir;
    final boolean open;

    final DependencyFinder dependencyFinder;
    final Analyzer analyzer;

    // an input JAR file (loaded as an automatic module for analysis)
    // maps to a normal module to generate module-info.java
    final Map automaticToNormalModule;
    public ModuleInfoBuilder(JdepsConfiguration configuration,
                             List args,
                             Path outputdir,
                             boolean open) {
        this.configuration = configuration;
        this.outputdir = outputdir;
        this.open = open;

        this.dependencyFinder = new DependencyFinder(configuration, DEFAULT_FILTER);
        this.analyzer = new Analyzer(configuration, Type.CLASS, DEFAULT_FILTER);

        // add targets to modulepath if it has module-info.class
        List paths = args.stream()
            .map(fn -> Paths.get(fn))
            .collect(toList());

        // automatic module to convert to normal module
        this.automaticToNormalModule = ModuleFinder.of(paths.toArray(new Path[0]))
                .findAll().stream()
                .map(configuration::toModule)
                .collect(toMap(Function.identity(), Function.identity()));

        Optional om = automaticToNormalModule.keySet().stream()
                                    .filter(m -> !m.descriptor().isAutomatic())
                                    .findAny();
        if (om.isPresent()) {
            throw new UncheckedBadArgs(new BadArgs("err.genmoduleinfo.not.jarfile",
                                                   om.get().getPathName()));
        }
        if (automaticToNormalModule.isEmpty()) {
            throw new UncheckedBadArgs(new BadArgs("err.invalid.path", args));
        }
    }

    public boolean run() throws IOException {
        try {
            // pass 1: find API dependencies
            Map> requiresTransitive = computeRequiresTransitive();

            // pass 2: analyze all class dependences
            dependencyFinder.parse(automaticModules().stream());

            analyzer.run(automaticModules(), dependencyFinder.locationToArchive());

            boolean missingDeps = false;
            for (Module m : automaticModules()) {
                Set apiDeps = requiresTransitive.containsKey(m)
                                            ? requiresTransitive.get(m)
                                            : Collections.emptySet();

                Path file = outputdir.resolve(m.name()).resolve("module-info.java");

                // computes requires and requires transitive
                Module normalModule = toNormalModule(m, apiDeps);
                if (normalModule != null) {
                    automaticToNormalModule.put(m, normalModule);

                    // generate module-info.java
                    System.out.format("writing to %s%n", file);
                    writeModuleInfo(file,  normalModule.descriptor());
                } else {
                    // find missing dependences
                    System.out.format("Missing dependence: %s not generated%n", file);
                    missingDeps = true;
                }
            }

            return !missingDeps;
        } finally {
            dependencyFinder.shutdown();
        }
    }

    boolean notFound(Archive m) {
        return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
    }

    private Module toNormalModule(Module module, Set requiresTransitive)
        throws IOException
    {
        // done analysis
        module.close();

        if (analyzer.requires(module).anyMatch(this::notFound)) {
            // missing dependencies
            return null;
        }

        Map requires = new HashMap<>();
        requiresTransitive.stream()
            .map(Archive::getModule)
            .forEach(m -> requires.put(m.name(), Boolean.TRUE));

        analyzer.requires(module)
            .map(Archive::getModule)
            .forEach(d -> requires.putIfAbsent(d.name(), Boolean.FALSE));

        return module.toNormalModule(requires);
    }

    /**
     * Returns the stream of resulting modules
     */
    Stream modules() {
        return automaticToNormalModule.values().stream();
    }

    /**
     * Returns the stream of resulting ModuleDescriptors
     */
    public Stream descriptors() {
        return automaticToNormalModule.entrySet().stream()
                    .map(Map.Entry::getValue)
                    .map(Module::descriptor);
    }

    void visitMissingDeps(Analyzer.Visitor visitor) {
        automaticModules().stream()
            .filter(m -> analyzer.requires(m).anyMatch(this::notFound))
            .forEach(m -> {
                analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
            });
    }

    void writeModuleInfo(Path file, ModuleDescriptor md) {
        try {
            Files.createDirectories(file.getParent());
            try (PrintWriter pw = new PrintWriter(Files.newOutputStream(file))) {
                printModuleInfo(pw, md);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private void printModuleInfo(PrintWriter writer, ModuleDescriptor md) {
        writer.format("%smodule %s {%n", open ? "open " : "", md.name());

        Map modules = configuration.getModules();

        // first print requires
        Set reqs = md.requires().stream()
            .filter(req -> !req.name().equals("java.base") && req.modifiers().isEmpty())
            .collect(Collectors.toSet());
        reqs.stream()
            .sorted(Comparator.comparing(Requires::name))
            .forEach(req -> writer.format("    requires %s;%n",
                                          toString(req.modifiers(), req.name())));
        if (!reqs.isEmpty()) {
            writer.println();
        }

        // requires transitive
        reqs = md.requires().stream()
                 .filter(req -> !req.name().equals("java.base") && !req.modifiers().isEmpty())
                 .collect(Collectors.toSet());
        reqs.stream()
            .sorted(Comparator.comparing(Requires::name))
            .forEach(req -> writer.format("    requires %s;%n",
                                          toString(req.modifiers(), req.name())));
        if (!reqs.isEmpty()) {
            writer.println();
        }

        if (!open) {
            md.exports().stream()
              .peek(exp -> {
                  if (exp.isQualified())
                      throw new InternalError(md.name() + " qualified exports: " + exp);
                  })
              .sorted(Comparator.comparing(Exports::source))
              .forEach(exp -> writer.format("    exports %s;%n", exp.source()));

            if (!md.exports().isEmpty()) {
                writer.println();
            }
        }

        md.provides().stream()
          .sorted(Comparator.comparing(Provides::service))
          .map(p -> p.providers().stream()
                     .map(impl -> "        " + impl.replace('$', '.'))
                     .collect(joining(",\n",
                                      String.format("    provides %s with%n",
                                                    p.service().replace('$', '.')),
                                      ";")))
                     .forEach(writer::println);

        if (!md.provides().isEmpty()) {
            writer.println();
        }
        writer.println("}");
    }

    private Set automaticModules() {
        return automaticToNormalModule.keySet();
    }

    /**
     * Returns a string containing the given set of modifiers and label.
     */
    private static  String toString(Set mods, String what) {
        return (Stream.concat(mods.stream().map(e -> e.toString().toLowerCase(Locale.US)),
                              Stream.of(what)))
                      .collect(Collectors.joining(" "));
    }

    /**
     * Compute 'requires transitive' dependences by analyzing API dependencies
     */
    private Map> computeRequiresTransitive()
        throws IOException
    {
        // parse the input modules
        dependencyFinder.parseExportedAPIs(automaticModules().stream());

        return dependencyFinder.dependences();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy