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

com.google.cloud.tools.opensource.dependencies.DependencyGraph Maven / Gradle / Ivy

There is a newer version: 1.5.13
Show newest version
/*
 * Copyright 2018 Google LLC.
 *
 * Licensed 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 com.google.cloud.tools.opensource.dependencies;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.aether.artifact.Artifact;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.TreeMultimap;

/**
 * A representation of the complete non-cyclic transitive dependency tree of a Maven artifact.
 * 
 * 

Imagine performing a breadth first search through the tree. As we go we build up a list of * dependencies. The path to each dependency node is placed in a list. Although each path should * appear only once, each dependency may appear many times in different paths. This representation * is unusual because it represents a tree as a list of every path from the root to each node, * instead of a network of nodes. * *

Artifacts are considered to be the same if they have the same group ID, artifact ID, and version. */ public class DependencyGraph { // DependencyGraphBuilder builds this in breadth first order, unless explicitly stated otherwise. // That is, this list contains the paths to each node in breadth first order private final List graph = new ArrayList<>(); // map of groupId:artifactId to versions // TODO if versions' values were the whole coordinate string // (or even the Artifact itself), would this be simpler? private final TreeMultimap versions = TreeMultimap.create(Comparator.naturalOrder(), new VersionComparator()); // map of groupId:artifactId:version to paths private SetMultimap paths = HashMultimap.create(); @VisibleForTesting public DependencyGraph() { } void addPath(DependencyPath path) { graph.add(path); Artifact leaf = path.getLeaf(); String coordinates = Artifacts.toCoordinates(leaf); versions.put(Artifacts.makeKey(leaf), leaf.getVersion()); paths.put(coordinates, path); } /** * Returns a list of paths to artifacts in this graph that appear with more than one version. * There can be multiple paths to a single version. */ List findConflicts() { List result = new ArrayList<>(); for (String coordinates : versions.keySet()) { Set artifactVersions = versions.get(coordinates); if (artifactVersions.size() > 1) { // multiple versions for (String conflictingVersion : artifactVersions) { result.addAll(paths.get(coordinates + ":" + conflictingVersion)); } } } return result; } /** * @return a mutable copy of the paths in this graph, usually in breadth first order */ public List list() { return new ArrayList<>(graph); } /** * @return all paths to the specified artifact */ public Set getPaths(String coordinates) { return paths.get(coordinates); } /** * Returns a list of updates indicating desired updates formatted for a person to read. */ public List findUpdates() { List paths = findConflicts(); // now generate necessary upgrades LinkedHashSet upgrades = new LinkedHashSet<>(); for (DependencyPath path : paths) { Artifact leaf = path.getLeaf(); String key = Artifacts.makeKey(leaf); String highestVersion = versions.get(key).last(); if (!leaf.getVersion().equals(highestVersion)) { Artifact parent = path.get(path.size() - 2); // when the parent is out of date, update the parent instead // TODO drop if any ancestor needs an update, instead of just the parent // or perhaps we just order the updates from root down, and then rerun after // each fix. Maybe even calculate what will be needed postfix String lastParentVersion = versions.get(Artifacts.makeKey(parent)).last(); if (parent.getVersion().equals(lastParentVersion)) { // setVersion returns a new instance on change Artifact updated = leaf.setVersion(highestVersion); Update update = Update.builder() .setParent(parent) .setFrom(leaf) .setTo(updated) .build(); upgrades.add(update); } } } // TODO sort by path by comparing with the graph return new ArrayList<>(upgrades); } /** * @return a map of groupId:artifactId to the highest version found in the tree */ public Map getHighestVersionMap() { Map> input = versions.asMap(); Map output = new HashMap<>(); VersionComparator comparator = new VersionComparator(); for (Map.Entry> entry : input.entrySet()) { String highestVersion = Collections.max(entry.getValue(), comparator); output.put(entry.getKey(), highestVersion); } return output; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy