mirror of https://github.com/apache/maven.git
[MNG-7615] Multithreaded project builder
This commit is contained in:
parent
2f6ec159fe
commit
7fcdd32e87
|
@ -150,10 +150,7 @@ public class DefaultProjectBuilder implements ProjectBuilder {
|
|||
public String getLocation() {
|
||||
StringBuilder buffer = new StringBuilder(256);
|
||||
|
||||
if (getSource().length() > 0) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
if (!getSource().isEmpty()) {
|
||||
buffer.append(getSource());
|
||||
}
|
||||
|
||||
|
@ -205,13 +202,6 @@ public class DefaultProjectBuilder implements ProjectBuilder {
|
|||
public Node getRoot() {
|
||||
return session.getNode(r.getDependencyGraph());
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public List<ArtifactResolverResult>
|
||||
// getArtifactResults()
|
||||
// {
|
||||
// return Collections.emptyList();
|
||||
// }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,15 +25,7 @@ import javax.inject.Singleton;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.function.Function;
|
||||
|
@ -48,7 +40,6 @@ import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException
|
|||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.apache.maven.building.Source;
|
||||
import org.apache.maven.model.Activation;
|
||||
import org.apache.maven.model.ActivationFile;
|
||||
import org.apache.maven.model.Build;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.model.DependencyManagement;
|
||||
|
@ -58,7 +49,6 @@ import org.apache.maven.model.Parent;
|
|||
import org.apache.maven.model.Plugin;
|
||||
import org.apache.maven.model.PluginManagement;
|
||||
import org.apache.maven.model.Profile;
|
||||
import org.apache.maven.model.Repository;
|
||||
import org.apache.maven.model.building.ModelProblem.Severity;
|
||||
import org.apache.maven.model.building.ModelProblem.Version;
|
||||
import org.apache.maven.model.composition.DependencyManagementImporter;
|
||||
|
@ -713,7 +703,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
profileActivationContext.setUserProperties(profileProps);
|
||||
}
|
||||
|
||||
profileActivationContext.setProjectProperties(inputModel.getProperties());
|
||||
profileActivationContext.setProjectProperties(inputModel.getDelegate().getProperties());
|
||||
problems.setSource(inputModel);
|
||||
List<Profile> activePomProfiles =
|
||||
profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, problems);
|
||||
|
@ -742,6 +732,9 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
DefaultModelProblemCollector problems)
|
||||
throws ModelBuildingException {
|
||||
Model inputModel = readRawModel(request, problems);
|
||||
if (problems.hasFatalErrors()) {
|
||||
throw problems.newModelBuildingException();
|
||||
}
|
||||
|
||||
problems.setRootModel(inputModel);
|
||||
|
||||
|
@ -777,50 +770,43 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
String modelId = currentData.getId();
|
||||
result.addModelId(modelId);
|
||||
|
||||
Model rawModel = currentData.getModel();
|
||||
result.setRawModel(modelId, rawModel);
|
||||
|
||||
profileActivationContext.setProjectProperties(rawModel.getProperties());
|
||||
problems.setSource(rawModel);
|
||||
List<Profile> activePomProfiles =
|
||||
profileSelector.getActiveProfiles(rawModel.getProfiles(), profileActivationContext, problems);
|
||||
result.setActivePomProfiles(modelId, activePomProfiles);
|
||||
|
||||
Model tmpModel = rawModel.clone();
|
||||
|
||||
problems.setSource(tmpModel);
|
||||
Model model = currentData.getModel();
|
||||
result.setRawModel(modelId, model);
|
||||
problems.setSource(model);
|
||||
org.apache.maven.api.model.Model modelv4 = model.getDelegate();
|
||||
|
||||
// model normalization
|
||||
tmpModel = new Model(modelNormalizer.mergeDuplicates(tmpModel.getDelegate(), request, problems));
|
||||
modelv4 = modelNormalizer.mergeDuplicates(modelv4, request, problems);
|
||||
|
||||
profileActivationContext.setProjectProperties(tmpModel.getProperties());
|
||||
// profile activation
|
||||
profileActivationContext.setProjectProperties(modelv4.getProperties());
|
||||
|
||||
Map<String, Activation> interpolatedActivations =
|
||||
getInterpolatedActivations(rawModel, profileActivationContext, problems);
|
||||
injectProfileActivations(tmpModel, interpolatedActivations);
|
||||
List<org.apache.maven.api.model.Profile> interpolatedProfiles =
|
||||
interpolateActivations(modelv4.getProfiles(), profileActivationContext, problems);
|
||||
|
||||
// profile injection
|
||||
for (Profile activeProfile : result.getActivePomProfiles(modelId)) {
|
||||
profileInjector.injectProfile(tmpModel, activeProfile, request, problems);
|
||||
}
|
||||
|
||||
List<org.apache.maven.api.model.Profile> activePomProfiles =
|
||||
profileSelector.getActiveProfilesV4(interpolatedProfiles, profileActivationContext, problems);
|
||||
result.setActivePomProfiles(
|
||||
modelId, activePomProfiles.stream().map(Profile::new).collect(Collectors.toList()));
|
||||
modelv4 = profileInjector.injectProfiles(modelv4, activePomProfiles, request, problems);
|
||||
if (currentData == resultData) {
|
||||
for (Profile activeProfile : activeExternalProfiles) {
|
||||
profileInjector.injectProfile(tmpModel, activeProfile, request, problems);
|
||||
modelv4 = profileInjector.injectProfile(modelv4, activeProfile.getDelegate(), request, problems);
|
||||
}
|
||||
result.setEffectiveModel(tmpModel);
|
||||
}
|
||||
|
||||
lineage.add(tmpModel);
|
||||
lineage.add(new Model(modelv4));
|
||||
|
||||
if (currentData == superData) {
|
||||
break;
|
||||
}
|
||||
|
||||
configureResolver(request.getModelResolver(), tmpModel, problems);
|
||||
// add repositories specified by the current model so that we can resolve the parent
|
||||
configureResolver(request.getModelResolver(), modelv4, problems, false);
|
||||
|
||||
ModelData parentData =
|
||||
readParent(currentData.getModel(), currentData.getSource(), request, result, problems);
|
||||
// we pass a cloned model, so that resolving the parent version does not affect the returned model
|
||||
ModelData parentData = readParent(new Model(modelv4), currentData.getSource(), request, problems);
|
||||
|
||||
if (parentData == null) {
|
||||
currentData = superData;
|
||||
|
@ -840,7 +826,22 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
problems.setSource(result.getRawModel());
|
||||
Model tmpModel = lineage.get(0);
|
||||
|
||||
// inject interpolated activations
|
||||
List<org.apache.maven.api.model.Profile> interpolated =
|
||||
interpolateActivations(tmpModel.getDelegate().getProfiles(), profileActivationContext, problems);
|
||||
if (interpolated != tmpModel.getDelegate().getProfiles()) {
|
||||
tmpModel.update(tmpModel.getDelegate().withProfiles(interpolated));
|
||||
}
|
||||
|
||||
// inject external profile into current model
|
||||
tmpModel.update(profileInjector.injectProfiles(
|
||||
tmpModel.getDelegate(),
|
||||
activeExternalProfiles.stream().map(Profile::getDelegate).collect(Collectors.toList()),
|
||||
request,
|
||||
problems));
|
||||
|
||||
checkPluginVersions(lineage, request, problems);
|
||||
|
||||
// inheritance assembly
|
||||
|
@ -860,44 +861,76 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
result.setEffectiveModel(resultModel);
|
||||
|
||||
// Now the fully interpolated model is available: reconfigure the resolver
|
||||
configureResolver(request.getModelResolver(), resultModel, problems, true);
|
||||
configureResolver(request.getModelResolver(), resultModel.getDelegate(), problems, true);
|
||||
|
||||
return resultModel;
|
||||
}
|
||||
|
||||
private Map<String, Activation> getInterpolatedActivations(
|
||||
Model rawModel, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) {
|
||||
Map<String, Activation> interpolatedActivations = getProfileActivations(rawModel, true);
|
||||
for (Activation activation : interpolatedActivations.values()) {
|
||||
if (activation.getFile() != null) {
|
||||
replaceWithInterpolatedValue(activation.getFile(), context, problems);
|
||||
}
|
||||
}
|
||||
return interpolatedActivations;
|
||||
}
|
||||
|
||||
private void replaceWithInterpolatedValue(
|
||||
ActivationFile activationFile, ProfileActivationContext context, DefaultModelProblemCollector problems) {
|
||||
private List<org.apache.maven.api.model.Profile> interpolateActivations(
|
||||
List<org.apache.maven.api.model.Profile> profiles,
|
||||
DefaultProfileActivationContext context,
|
||||
DefaultModelProblemCollector problems) {
|
||||
List<org.apache.maven.api.model.Profile> newProfiles = null;
|
||||
for (int index = 0; index < profiles.size(); index++) {
|
||||
org.apache.maven.api.model.Profile profile = profiles.get(index);
|
||||
org.apache.maven.api.model.Activation activation = profile.getActivation();
|
||||
if (activation != null) {
|
||||
org.apache.maven.api.model.ActivationFile file = activation.getFile();
|
||||
if (file != null) {
|
||||
String oldExists = file.getExists();
|
||||
if (isNotEmpty(oldExists)) {
|
||||
try {
|
||||
if (isNotEmpty(activationFile.getExists())) {
|
||||
String path = activationFile.getExists();
|
||||
String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context);
|
||||
activationFile.setExists(absolutePath);
|
||||
} else if (isNotEmpty(activationFile.getMissing())) {
|
||||
String path = activationFile.getMissing();
|
||||
String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context);
|
||||
activationFile.setMissing(absolutePath);
|
||||
String newExists = interpolate(oldExists, context);
|
||||
if (!Objects.equals(oldExists, newExists)) {
|
||||
if (newProfiles == null) {
|
||||
newProfiles = new ArrayList<>(profiles);
|
||||
}
|
||||
newProfiles.set(
|
||||
index, profile.withActivation(activation.withFile(file.withExists(newExists))));
|
||||
}
|
||||
} catch (InterpolationException e) {
|
||||
String path =
|
||||
isNotEmpty(activationFile.getExists()) ? activationFile.getExists() : activationFile.getMissing();
|
||||
addInterpolationProblem(problems, file, oldExists, e, "exists");
|
||||
}
|
||||
} else {
|
||||
String oldMissing = file.getMissing();
|
||||
if (isNotEmpty(oldMissing)) {
|
||||
try {
|
||||
String newMissing = interpolate(oldMissing, context);
|
||||
if (!Objects.equals(oldMissing, newMissing)) {
|
||||
if (newProfiles == null) {
|
||||
newProfiles = new ArrayList<>(profiles);
|
||||
}
|
||||
newProfiles.set(
|
||||
index,
|
||||
profile.withActivation(activation.withFile(file.withMissing(newMissing))));
|
||||
}
|
||||
} catch (InterpolationException e) {
|
||||
addInterpolationProblem(problems, file, oldMissing, e, "missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newProfiles != null ? newProfiles : profiles;
|
||||
}
|
||||
|
||||
private static void addInterpolationProblem(
|
||||
DefaultModelProblemCollector problems,
|
||||
org.apache.maven.api.model.ActivationFile file,
|
||||
String path,
|
||||
InterpolationException e,
|
||||
String locationKey) {
|
||||
problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
|
||||
.setMessage("Failed to interpolate file location " + path + ": " + e.getMessage())
|
||||
.setLocation(
|
||||
activationFile.getLocation(isNotEmpty(activationFile.getExists()) ? "exists" : "missing"))
|
||||
.setLocation(Optional.ofNullable(file.getLocation(locationKey))
|
||||
.map(InputLocation::new)
|
||||
.orElse(null))
|
||||
.setException(e));
|
||||
}
|
||||
|
||||
private String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
|
||||
return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path;
|
||||
}
|
||||
|
||||
private static boolean isNotEmpty(String string) {
|
||||
|
@ -910,6 +943,15 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
return build(request, result, new LinkedHashSet<>());
|
||||
}
|
||||
|
||||
public Model buildRawModel(final ModelBuildingRequest request) throws ModelBuildingException {
|
||||
DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuildingResult());
|
||||
Model model = readRawModel(request, problems);
|
||||
if (hasModelErrors(problems)) {
|
||||
throw problems.newModelBuildingException();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private ModelBuildingResult build(
|
||||
final ModelBuildingRequest request, final ModelBuildingResult phaseOneResult, Collection<String> imports)
|
||||
throws ModelBuildingException {
|
||||
|
@ -1127,7 +1169,18 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
throws ModelBuildingException {
|
||||
ModelSource modelSource = request.getModelSource();
|
||||
|
||||
ModelData cachedData = cache(request.getModelCache(), modelSource, ModelCacheTag.RAW, () -> {
|
||||
ModelData modelData = cache(
|
||||
request.getModelCache(),
|
||||
modelSource,
|
||||
ModelCacheTag.RAW,
|
||||
() -> doReadRawModel(modelSource, request, problems));
|
||||
|
||||
return modelData.getModel();
|
||||
}
|
||||
|
||||
private ModelData doReadRawModel(
|
||||
ModelSource modelSource, ModelBuildingRequest request, DefaultModelProblemCollector problems)
|
||||
throws ModelBuildingException {
|
||||
Model rawModel;
|
||||
if (Features.buildConsumer(request.getUserProperties()) && modelSource instanceof FileModelSource) {
|
||||
rawModel = readFileModel(request, problems);
|
||||
|
@ -1158,10 +1211,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
String artifactId = rawModel.getArtifactId();
|
||||
String version = getVersion(rawModel);
|
||||
|
||||
ModelData modelData = new ModelData(modelSource, rawModel, groupId, artifactId, version);
|
||||
return modelData;
|
||||
});
|
||||
return cachedData != null ? cachedData.getModel() : null;
|
||||
return new ModelData(modelSource, rawModel, groupId, artifactId, version);
|
||||
}
|
||||
|
||||
String getGroupId(Model model) {
|
||||
|
@ -1204,34 +1254,24 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
return context;
|
||||
}
|
||||
|
||||
private void configureResolver(ModelResolver modelResolver, Model model, DefaultModelProblemCollector problems) {
|
||||
configureResolver(modelResolver, model, problems, false);
|
||||
}
|
||||
|
||||
private void configureResolver(
|
||||
ModelResolver modelResolver,
|
||||
Model model,
|
||||
org.apache.maven.api.model.Model model,
|
||||
DefaultModelProblemCollector problems,
|
||||
boolean replaceRepositories) {
|
||||
if (modelResolver == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
problems.setSource(model);
|
||||
|
||||
List<Repository> repositories = model.getRepositories();
|
||||
|
||||
for (Repository repository : repositories) {
|
||||
if (modelResolver != null) {
|
||||
for (org.apache.maven.api.model.Repository repository : model.getRepositories()) {
|
||||
try {
|
||||
modelResolver.addRepository(repository, replaceRepositories);
|
||||
} catch (InvalidRepositoryException e) {
|
||||
problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
|
||||
.setMessage("Invalid repository " + repository.getId() + ": " + e.getMessage())
|
||||
.setLocation(repository.getLocation(""))
|
||||
.setLocation(new InputLocation(repository.getLocation("")))
|
||||
.setException(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPluginVersions(
|
||||
List<Model> lineage, ModelBuildingRequest request, ModelProblemCollector problems) {
|
||||
|
@ -1353,19 +1393,15 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
}
|
||||
|
||||
private ModelData readParent(
|
||||
Model childModel,
|
||||
Source childSource,
|
||||
ModelBuildingRequest request,
|
||||
ModelBuildingResult result,
|
||||
DefaultModelProblemCollector problems)
|
||||
Model childModel, Source childSource, ModelBuildingRequest request, DefaultModelProblemCollector problems)
|
||||
throws ModelBuildingException {
|
||||
ModelData parentData = null;
|
||||
|
||||
Parent parent = childModel.getParent();
|
||||
if (parent != null) {
|
||||
parentData = readParentLocally(childModel, childSource, request, result, problems);
|
||||
parentData = readParentLocally(childModel, childSource, request, problems);
|
||||
if (parentData == null) {
|
||||
parentData = readParentExternally(childModel, request, result, problems);
|
||||
parentData = readParentExternally(childModel, request, problems);
|
||||
}
|
||||
|
||||
Model parentModel = parentData.getModel();
|
||||
|
@ -1381,11 +1417,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
}
|
||||
|
||||
private ModelData readParentLocally(
|
||||
Model childModel,
|
||||
Source childSource,
|
||||
ModelBuildingRequest request,
|
||||
ModelBuildingResult result,
|
||||
DefaultModelProblemCollector problems)
|
||||
Model childModel, Source childSource, ModelBuildingRequest request, DefaultModelProblemCollector problems)
|
||||
throws ModelBuildingException {
|
||||
final Parent parent = childModel.getParent();
|
||||
final ModelSource2 candidateSource;
|
||||
|
@ -1524,10 +1556,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
}
|
||||
|
||||
private ModelData readParentExternally(
|
||||
Model childModel,
|
||||
ModelBuildingRequest request,
|
||||
ModelBuildingResult result,
|
||||
DefaultModelProblemCollector problems)
|
||||
Model childModel, ModelBuildingRequest request, DefaultModelProblemCollector problems)
|
||||
throws ModelBuildingException {
|
||||
problems.setSource(childModel);
|
||||
|
||||
|
@ -1701,12 +1730,14 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
return null;
|
||||
}
|
||||
|
||||
org.apache.maven.api.model.DependencyManagement importMgmt =
|
||||
cache(request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT, () -> {
|
||||
DependencyManagement importMgmtV3 = doLoadDependencyManagement(
|
||||
model, request, problems, dependency, groupId, artifactId, version, importIds);
|
||||
return importMgmtV3 != null ? importMgmtV3.getDelegate() : null;
|
||||
});
|
||||
org.apache.maven.api.model.DependencyManagement importMgmt = cache(
|
||||
request.getModelCache(),
|
||||
groupId,
|
||||
artifactId,
|
||||
version,
|
||||
ModelCacheTag.IMPORT,
|
||||
() -> doLoadDependencyManagement(
|
||||
model, request, problems, dependency, groupId, artifactId, version, importIds));
|
||||
|
||||
// [MNG-5600] Dependency management import should support exclusions.
|
||||
List<Exclusion> exclusions = dependency.getDelegate().getExclusions();
|
||||
|
@ -1732,7 +1763,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:parameternumber")
|
||||
private DependencyManagement doLoadDependencyManagement(
|
||||
private org.apache.maven.api.model.DependencyManagement doLoadDependencyManagement(
|
||||
Model model,
|
||||
ModelBuildingRequest request,
|
||||
DefaultModelProblemCollector problems,
|
||||
|
@ -1810,7 +1841,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
if (importMgmt == null) {
|
||||
importMgmt = new DependencyManagement();
|
||||
}
|
||||
return importMgmt;
|
||||
return importMgmt.getDelegate();
|
||||
}
|
||||
|
||||
private static <T> T cache(
|
||||
|
@ -1863,8 +1894,7 @@ public class DefaultModelBuilder implements ModelBuilder {
|
|||
Model model,
|
||||
ModelBuildingRequest request,
|
||||
ModelProblemCollector problems,
|
||||
ModelBuildingEventCatapult catapult)
|
||||
throws ModelBuildingException {
|
||||
ModelBuildingEventCatapult catapult) {
|
||||
ModelBuildingListener listener = request.getModelBuildingListener();
|
||||
|
||||
if (listener != null) {
|
||||
|
|
|
@ -28,8 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.building.DefaultTransformerContext.GAKey;
|
||||
import org.apache.maven.model.building.DefaultTransformerContext.Holder;
|
||||
import org.codehaus.plexus.util.dag.CycleDetectedException;
|
||||
import org.codehaus.plexus.util.dag.DAG;
|
||||
|
||||
/**
|
||||
* Builds up the transformer context.
|
||||
|
@ -39,7 +37,7 @@ import org.codehaus.plexus.util.dag.DAG;
|
|||
* @since 4.0.0
|
||||
*/
|
||||
class DefaultTransformerContextBuilder implements TransformerContextBuilder {
|
||||
private final DAG dag = new DAG();
|
||||
private final Graph dag = new Graph();
|
||||
private final DefaultModelBuilder defaultModelBuilder;
|
||||
private final DefaultTransformerContext context;
|
||||
|
||||
|
@ -134,10 +132,10 @@ class DefaultTransformerContextBuilder implements TransformerContextBuilder {
|
|||
private boolean addEdge(Path from, Path p, DefaultModelProblemCollector problems) {
|
||||
try {
|
||||
synchronized (dag) {
|
||||
dag.addEdge(from.toString(), p.toString());
|
||||
dag.addEdge(dag.addVertex(from.toString()), dag.addVertex(p.toString()));
|
||||
}
|
||||
return true;
|
||||
} catch (CycleDetectedException e) {
|
||||
} catch (Graph.CycleDetectedException e) {
|
||||
problems.add(new DefaultModelProblem(
|
||||
"Cycle detected between models at " + from + " and " + p,
|
||||
ModelProblem.Severity.FATAL,
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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.apache.maven.model.building;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class Graph {
|
||||
private enum DfsState {
|
||||
VISITING,
|
||||
VISITED
|
||||
}
|
||||
|
||||
final Map<String, Vertex> vertices = new LinkedHashMap<>();
|
||||
|
||||
public Vertex getVertex(String id) {
|
||||
return vertices.get(id);
|
||||
}
|
||||
|
||||
public Collection<Vertex> getVertices() {
|
||||
return vertices.values();
|
||||
}
|
||||
|
||||
Vertex addVertex(String label) {
|
||||
return vertices.computeIfAbsent(label, Vertex::new);
|
||||
}
|
||||
|
||||
void addEdge(Vertex from, Vertex to) throws CycleDetectedException {
|
||||
from.children.add(to);
|
||||
to.parents.add(from);
|
||||
List<String> cycle = findCycle(to);
|
||||
if (cycle != null) {
|
||||
// remove edge which introduced cycle
|
||||
removeEdge(from, to);
|
||||
throw new CycleDetectedException(
|
||||
"Edge between '" + from.label + "' and '" + to.label + "' introduces to cycle in the graph", cycle);
|
||||
}
|
||||
}
|
||||
|
||||
void removeEdge(Vertex from, Vertex to) {
|
||||
from.children.remove(to);
|
||||
to.parents.remove(from);
|
||||
}
|
||||
|
||||
List<String> visitAll() {
|
||||
return visitAll(vertices.values(), new HashMap<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
List<String> findCycle(Vertex vertex) {
|
||||
return visitCycle(Collections.singleton(vertex), new HashMap<>(), new LinkedList<>());
|
||||
}
|
||||
|
||||
private static List<String> visitAll(
|
||||
Collection<Vertex> children, Map<Vertex, DfsState> stateMap, List<String> list) {
|
||||
for (Vertex v : children) {
|
||||
DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
|
||||
if (state == null) {
|
||||
visitAll(v.children, stateMap, list);
|
||||
stateMap.put(v, DfsState.VISITED);
|
||||
list.add(v.label);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<String> visitCycle(
|
||||
Collection<Vertex> children, Map<Vertex, DfsState> stateMap, LinkedList<String> cycle) {
|
||||
for (Vertex v : children) {
|
||||
DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
|
||||
if (state == null) {
|
||||
cycle.addLast(v.label);
|
||||
List<String> ret = visitCycle(v.children, stateMap, cycle);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
cycle.removeLast();
|
||||
stateMap.put(v, DfsState.VISITED);
|
||||
} else if (state == DfsState.VISITING) {
|
||||
// we are already visiting this vertex, this mean we have a cycle
|
||||
int pos = cycle.lastIndexOf(v.label);
|
||||
List<String> ret = cycle.subList(pos, cycle.size());
|
||||
ret.add(v.label);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static class Vertex {
|
||||
final String label;
|
||||
final List<Vertex> children = new ArrayList<>();
|
||||
final List<Vertex> parents = new ArrayList<>();
|
||||
|
||||
Vertex(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
List<Vertex> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
List<Vertex> getParents() {
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CycleDetectedException extends Exception {
|
||||
private final List<String> cycle;
|
||||
|
||||
CycleDetectedException(String message, List<String> cycle) {
|
||||
super(message);
|
||||
this.cycle = cycle;
|
||||
}
|
||||
|
||||
public List<String> getCycle() {
|
||||
return cycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return super.getMessage() + " " + String.join(" --> ", cycle);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue