| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.java.module.graph; |
| |
| import com.sun.source.tree.Tree; |
| import com.sun.source.util.TreePath; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| import javax.lang.model.element.ModuleElement; |
| import javax.tools.JavaFileObject; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.URLMapper; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Parameters; |
| |
| /** |
| * |
| * @author Tomas Zezula |
| */ |
| final class DependencyCalculator { |
| |
| private final FileObject moduleInfo; |
| private Collection<? extends ModuleNode> nodes; |
| private Collection<DependencyEdge> edges; |
| |
| public DependencyCalculator( |
| @NonNull final FileObject moduleInfo) { |
| Parameters.notNull("moduleInfo", moduleInfo); |
| this.moduleInfo = moduleInfo; |
| } |
| |
| @NonNull |
| Collection<? extends ModuleNode> getNodes() { |
| init(); |
| assert nodes != null; |
| return nodes; |
| } |
| |
| @NonNull |
| Collection<DependencyEdge> getEdges() { |
| init(); |
| assert edges != null; |
| return edges; |
| } |
| |
| private void init() { |
| if (nodes == null) { |
| assert edges == null; |
| nodes = Collections.emptyList(); |
| edges = Collections.emptyList(); |
| final JavaSource js = JavaSource.forFileObject(moduleInfo); |
| if (js != null) { |
| try { |
| js.runUserActionTask((cc)-> { |
| cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); |
| final List<? extends Tree> decls = cc.getCompilationUnit().getTypeDecls(); |
| final ModuleElement me = !decls.isEmpty() && decls.get(0).getKind() == Tree.Kind.MODULE ? |
| (ModuleElement) cc.getTrees().getElement(TreePath.getPath(cc.getCompilationUnit(), decls.get(0))) : |
| null; |
| if (me != null) { |
| final Map<String, ModuleNode> mods = new LinkedHashMap<>(); |
| final Collection<DependencyEdge> deps = new HashSet<>(); |
| String name = me.getQualifiedName().toString(); |
| ClasspathInfo classpathInfo = cc.getClasspathInfo(); |
| ModuleNode node = new ModuleNode(name, me.isUnnamed(), isJDK(me, classpathInfo), moduleInfo); |
| mods.put(name, node); |
| collect(node, me, mods, deps, classpathInfo); |
| nodes = mods.values(); |
| edges = deps; |
| } |
| }, true); |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } |
| } |
| } |
| } |
| |
| private void collect( |
| @NonNull ModuleNode meNode, |
| @NonNull ModuleElement me, |
| @NonNull Map<String, ModuleNode> mods, |
| @NonNull Collection<DependencyEdge> deps, |
| ClasspathInfo classpathInfo) { |
| for (Dependency d : collect(me, mods, deps, classpathInfo)) { |
| meNode.addChild(d.node); |
| d.node.setParent(meNode); |
| deps.add(new DependencyEdge(meNode, d.node, d.reqD.isTransitive(), false)); |
| } |
| deps.addAll(collectTransitiveDependencies(new HashSet<>(deps))); |
| } |
| |
| private Collection<Dependency> collect( |
| @NonNull final ModuleElement me, |
| @NonNull final Map<String, ModuleNode> mods, |
| @NonNull final Collection<DependencyEdge> deps, |
| ClasspathInfo classpathInfo) { |
| List<Dependency> dependencies = new ArrayList<>(); |
| if (!me.isUnnamed()) { |
| for (ModuleElement.Directive d : me.getDirectives()) { |
| if (d.getKind() == ModuleElement.DirectiveKind.REQUIRES) { |
| final ModuleElement.RequiresDirective reqD = (ModuleElement.RequiresDirective) d; |
| final ModuleElement reqMod = reqD.getDependency(); |
| final String name = reqMod.getQualifiedName().toString(); |
| boolean unseen; |
| ModuleNode n = mods.get(name); |
| if(n == null) { |
| n = new ModuleNode(name, reqMod.isUnnamed(), isJDK(reqMod, classpathInfo), moduleInfo); |
| mods.put(name, n); |
| unseen = true; |
| } else { |
| unseen = false; |
| } |
| dependencies.add(new Dependency(n, reqD, unseen)); |
| } |
| } |
| |
| for (Dependency d : dependencies) { |
| if(d.unseen) { |
| collect(d.node, d.reqD.getDependency(), mods, deps, classpathInfo); |
| } |
| } |
| } |
| return dependencies; |
| } |
| |
| private boolean isJDK(final ModuleElement me, ClasspathInfo cpinfo) { |
| for (FileObject root : cpinfo.getClassPath(ClasspathInfo.PathKind.BOOT).getRoots()) { |
| if (root.getNameExt().contentEquals(me.getQualifiedName())) |
| return true; |
| } |
| return false; |
| } |
| |
| Collection<DependencyEdge> collectTransitiveDependencies(Collection<DependencyEdge> deps) { |
| Map<ModuleNode, List<ModuleNode>> publicEdges = deps.stream() |
| .filter((e) -> e.isPublic()) |
| .collect(Collectors.groupingBy(DependencyEdge::getSource, |
| Collectors.mapping(DependencyEdge::getTarget, Collectors.toList()))); |
| |
| Collection<DependencyEdge> transitiveEdges = new HashSet<>(); |
| for (DependencyEdge dep : deps) { |
| List<ModuleNode> targets = publicEdges.get(dep.getTarget()); |
| if(targets != null) { |
| ModuleNode source = dep.getSource(); |
| transitiveEdges.addAll(toDependencyEdges(source, targets)); |
| Collection<ModuleNode> transTargets = new HashSet<>(); |
| collectTransTargets(targets, publicEdges, transTargets); |
| transitiveEdges.addAll(toDependencyEdges(source, transTargets)); |
| } |
| } |
| return transitiveEdges; |
| } |
| |
| private void collectTransTargets(List<ModuleNode> sources, Map<ModuleNode, List<ModuleNode>> publicEdges, Collection<ModuleNode> transTargets) { |
| for (ModuleNode source : sources) { |
| List<ModuleNode> targets = publicEdges.get(source); |
| if(targets != null) { |
| List<ModuleNode> ts = new LinkedList<>(); |
| for (ModuleNode target : targets) { |
| if(!transTargets.contains(target)) { |
| ts.add(target); |
| } |
| } |
| transTargets.addAll(ts); |
| collectTransTargets(ts, publicEdges, transTargets); |
| } |
| } |
| } |
| |
| private static Collection<DependencyEdge> toDependencyEdges(ModuleNode source, Collection<ModuleNode> targets) { |
| return targets.stream().map(target -> new DependencyEdge(source, target, false, true)).collect(Collectors.toList()); |
| } |
| |
| private static class Dependency { |
| final ModuleNode node; |
| final boolean unseen; |
| final ModuleElement.RequiresDirective reqD; |
| |
| public Dependency(ModuleNode node, ModuleElement.RequiresDirective reqD, boolean unseen) { |
| this.node = node; |
| this.unseen = unseen; |
| this.reqD = reqD; |
| } |
| |
| @Override |
| public String toString() { |
| return String.valueOf(reqD); |
| } |
| } |
| |
| } |