blob: f1f798e1cbdd29d92cd9a1a6d9b21bb22467cb1f [file] [log] [blame]
package org.eclipse.aether.util.graph.transformer;
/*
* 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.
*/
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.aether.RepositoryException;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.DependencyGraphTransformationContext;
import org.eclipse.aether.collection.DependencyGraphTransformer;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
/**
* A dependency graph transformer that identifies conflicting dependencies. When this transformer has executed, the
* transformation context holds a {@code Map<DependencyNode, Object>} where dependency nodes that belong to the same
* conflict group will have an equal conflict identifier. This map is stored using the key
* {@link TransformationContextKeys#CONFLICT_IDS}.
*/
public final class ConflictMarker
implements DependencyGraphTransformer
{
/**
* After the execution of this method, every DependencyNode with an attached dependency is member of one conflict
* group.
*
* @see DependencyGraphTransformer#transformGraph(DependencyNode, DependencyGraphTransformationContext)
*/
public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context )
throws RepositoryException
{
@SuppressWarnings( "unchecked" )
Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS );
long time1 = System.nanoTime();
Map<DependencyNode, Object> nodes = new IdentityHashMap<DependencyNode, Object>( 1024 );
Map<Object, ConflictGroup> groups = new HashMap<Object, ConflictGroup>( 1024 );
analyze( node, nodes, groups, new int[] { 0 } );
long time2 = System.nanoTime();
Map<DependencyNode, Object> conflictIds = mark( nodes.keySet(), groups );
context.put( TransformationContextKeys.CONFLICT_IDS, conflictIds );
if ( stats != null )
{
long time3 = System.nanoTime();
stats.put( "ConflictMarker.analyzeTime", time2 - time1 );
stats.put( "ConflictMarker.markTime", time3 - time2 );
stats.put( "ConflictMarker.nodeCount", nodes.size() );
}
return node;
}
private void analyze( DependencyNode node, Map<DependencyNode, Object> nodes, Map<Object, ConflictGroup> groups,
int[] counter )
{
if ( nodes.put( node, Boolean.TRUE ) != null )
{
return;
}
Set<Object> keys = getKeys( node );
if ( !keys.isEmpty() )
{
ConflictGroup group = null;
boolean fixMappings = false;
for ( Object key : keys )
{
ConflictGroup g = groups.get( key );
if ( group != g )
{
if ( group == null )
{
Set<Object> newKeys = merge( g.keys, keys );
if ( newKeys == g.keys )
{
group = g;
break;
}
else
{
group = new ConflictGroup( newKeys, counter[0]++ );
fixMappings = true;
}
}
else if ( g == null )
{
fixMappings = true;
}
else
{
Set<Object> newKeys = merge( g.keys, group.keys );
if ( newKeys == g.keys )
{
group = g;
fixMappings = false;
break;
}
else if ( newKeys != group.keys )
{
group = new ConflictGroup( newKeys, counter[0]++ );
fixMappings = true;
}
}
}
}
if ( group == null )
{
group = new ConflictGroup( keys, counter[0]++ );
fixMappings = true;
}
if ( fixMappings )
{
for ( Object key : group.keys )
{
groups.put( key, group );
}
}
}
for ( DependencyNode child : node.getChildren() )
{
analyze( child, nodes, groups, counter );
}
}
private Set<Object> merge( Set<Object> keys1, Set<Object> keys2 )
{
int size1 = keys1.size();
int size2 = keys2.size();
if ( size1 < size2 )
{
if ( keys2.containsAll( keys1 ) )
{
return keys2;
}
}
else
{
if ( keys1.containsAll( keys2 ) )
{
return keys1;
}
}
Set<Object> keys = new HashSet<Object>();
keys.addAll( keys1 );
keys.addAll( keys2 );
return keys;
}
private Set<Object> getKeys( DependencyNode node )
{
Set<Object> keys;
Dependency dependency = node.getDependency();
if ( dependency == null )
{
keys = Collections.emptySet();
}
else
{
Object key = toKey( dependency.getArtifact() );
if ( node.getRelocations().isEmpty() && node.getAliases().isEmpty() )
{
keys = Collections.singleton( key );
}
else
{
keys = new HashSet<Object>();
keys.add( key );
for ( Artifact relocation : node.getRelocations() )
{
key = toKey( relocation );
keys.add( key );
}
for ( Artifact alias : node.getAliases() )
{
key = toKey( alias );
keys.add( key );
}
}
}
return keys;
}
private Map<DependencyNode, Object> mark( Collection<DependencyNode> nodes, Map<Object, ConflictGroup> groups )
{
Map<DependencyNode, Object> conflictIds = new IdentityHashMap<DependencyNode, Object>( nodes.size() + 1 );
for ( DependencyNode node : nodes )
{
Dependency dependency = node.getDependency();
if ( dependency != null )
{
Object key = toKey( dependency.getArtifact() );
conflictIds.put( node, groups.get( key ).index );
}
}
return conflictIds;
}
private static Object toKey( Artifact artifact )
{
return new Key( artifact );
}
static class ConflictGroup
{
final Set<Object> keys;
final int index;
ConflictGroup( Set<Object> keys, int index )
{
this.keys = keys;
this.index = index;
}
@Override
public String toString()
{
return String.valueOf( keys );
}
}
static class Key
{
private final Artifact artifact;
Key( Artifact artifact )
{
this.artifact = artifact;
}
@Override
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
else if ( !( obj instanceof Key ) )
{
return false;
}
Key that = (Key) obj;
return artifact.getArtifactId().equals( that.artifact.getArtifactId() )
&& artifact.getGroupId().equals( that.artifact.getGroupId() )
&& artifact.getExtension().equals( that.artifact.getExtension() )
&& artifact.getClassifier().equals( that.artifact.getClassifier() );
}
@Override
public int hashCode()
{
int hash = 17;
hash = hash * 31 + artifact.getArtifactId().hashCode();
hash = hash * 31 + artifact.getGroupId().hashCode();
hash = hash * 31 + artifact.getClassifier().hashCode();
hash = hash * 31 + artifact.getExtension().hashCode();
return hash;
}
@Override
public String toString()
{
return artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + artifact.getClassifier() + ':'
+ artifact.getExtension();
}
}
}