Java tutorial
/******************************************************************************* * Copyright (C) 2013 John Casey. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.commonjava.maven.atlas.spi.jung.effective; import static org.apache.commons.lang.StringUtils.join; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.maven.graph.common.ref.ArtifactRef; import org.apache.maven.graph.common.ref.ProjectVersionRef; import org.apache.maven.graph.common.version.SingleVersion; import org.apache.maven.graph.effective.EProjectCycle; import org.apache.maven.graph.effective.EProjectNet; import org.apache.maven.graph.effective.filter.ProjectRelationshipFilter; import org.apache.maven.graph.effective.rel.AbstractProjectRelationship; import org.apache.maven.graph.effective.rel.ParentRelationship; import org.apache.maven.graph.effective.rel.ProjectRelationship; import org.apache.maven.graph.effective.rel.RelationshipComparator; import org.apache.maven.graph.effective.rel.RelationshipPathComparator; import org.apache.maven.graph.effective.traverse.AbstractTraversal; import org.apache.maven.graph.effective.traverse.FilteringTraversal; import org.apache.maven.graph.effective.traverse.ProjectNetTraversal; import org.apache.maven.graph.effective.util.RelationshipUtils; import org.apache.maven.graph.spi.GraphDriverException; import org.apache.maven.graph.spi.effective.EGraphDriver; import org.commonjava.util.logging.Logger; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; public class JungEGraphDriver implements EGraphDriver { // private final Logger logger = new Logger( getClass() ); private DirectedGraph<ProjectVersionRef, ProjectRelationship<?>> graph = new DirectedSparseMultigraph<ProjectVersionRef, ProjectRelationship<?>>(); private transient Set<ProjectVersionRef> incompleteSubgraphs = new HashSet<ProjectVersionRef>(); private transient Set<ProjectVersionRef> variableSubgraphs = new HashSet<ProjectVersionRef>(); private transient Map<ProjectVersionRef, ProjectVersionRef> selected = new HashMap<ProjectVersionRef, ProjectVersionRef>(); private transient Map<ProjectRelationship<?>, ProjectRelationship<?>> replaced = new HashMap<ProjectRelationship<?>, ProjectRelationship<?>>(); private final Map<String, Set<ProjectVersionRef>> metadataOwners = new HashMap<String, Set<ProjectVersionRef>>(); private final Map<ProjectVersionRef, Map<String, String>> metadata = new HashMap<ProjectVersionRef, Map<String, String>>(); private transient Set<EProjectCycle> cycles = new HashSet<EProjectCycle>(); private ProjectVersionRef[] roots; public JungEGraphDriver() { } public JungEGraphDriver(final JungEGraphDriver from, final ProjectRelationshipFilter filter, final EProjectNet net, final ProjectVersionRef... roots) throws GraphDriverException { this.roots = roots; Collection<ProjectRelationship<?>> rels; if (filter != null && roots.length > 0) { rels = filterRelationships(filter, net, roots); } else { rels = from.getAllRelationships(); } addRelationships(rels.toArray(new ProjectRelationship<?>[] {})); for (final ProjectVersionRef ref : from.incompleteSubgraphs) { if (graph.containsVertex(ref)) { incompleteSubgraphs.add(ref); } } for (final ProjectVersionRef ref : from.variableSubgraphs) { if (graph.containsVertex(ref)) { variableSubgraphs.add(ref); } } for (final Map.Entry<ProjectVersionRef, Map<String, String>> entry : from.metadata.entrySet()) { final ProjectVersionRef ref = entry.getKey(); if (graph.containsVertex(ref)) { metadata.put(ref, new HashMap<String, String>(entry.getValue())); } } } private Set<ProjectRelationship<?>> filterRelationships(final ProjectRelationshipFilter filter, final EProjectNet net, final ProjectVersionRef... roots) throws GraphDriverException { final FilteringTraversal traversal = new FilteringTraversal(filter, true); for (final ProjectVersionRef root : roots) { traverse(traversal, net, root); } return new HashSet<ProjectRelationship<?>>(traversal.getCapturedRelationships()); } public Collection<? extends ProjectRelationship<?>> getRelationshipsDeclaredBy(final ProjectVersionRef ref) { return graph.getOutEdges(ref); } public Collection<? extends ProjectRelationship<?>> getRelationshipsTargeting(final ProjectVersionRef ref) { return graph.getInEdges(ref); } public Collection<ProjectRelationship<?>> getAllRelationships() { return graph.getEdges(); } public Set<ProjectRelationship<?>> addRelationships(final ProjectRelationship<?>... rels) { final Set<ProjectRelationship<?>> skipped = new HashSet<ProjectRelationship<?>>(); for (final ProjectRelationship<?> rel : rels) { if (!graph.containsVertex(rel.getDeclaring())) { graph.addVertex(rel.getDeclaring()); } final ProjectVersionRef target = rel.getTarget().asProjectVersionRef(); if (!target.getVersionSpec().isSingle()) { variableSubgraphs.add(target); } else if (!graph.containsVertex(target)) { incompleteSubgraphs.add(target); } if (!graph.containsVertex(target)) { graph.addVertex(target); } if (!graph.containsEdge(rel)) { graph.addEdge(rel, rel.getDeclaring(), target); } incompleteSubgraphs.remove(rel.getDeclaring()); } for (final ProjectRelationship<?> rel : rels) { if (skipped.contains(rel)) { continue; } final CycleDetectionTraversal traversal = new CycleDetectionTraversal(rel); dfsTraverse(traversal, 0, rel.getTarget().asProjectVersionRef()); final List<EProjectCycle> cycles = traversal.getCycles(); if (!cycles.isEmpty()) { skipped.add(rel); graph.removeEdge(rel); this.cycles.addAll(cycles); } } return skipped; } public Set<List<ProjectRelationship<?>>> getAllPathsTo(final ProjectVersionRef... refs) { final PathDetectionTraversal traversal = new PathDetectionTraversal(refs); if (roots == null) { new Logger(getClass()).warn( "Cannot retrieve paths targeting %s. No roots specified for this project network!", join(refs, ", ")); return null; } for (final ProjectVersionRef root : roots) { dfsTraverse(traversal, 0, root); } return traversal.getPaths(); } public boolean introducesCycle(final ProjectRelationship<?> rel) { final CycleDetectionTraversal traversal = new CycleDetectionTraversal(rel); dfsTraverse(traversal, 0, rel.getTarget().asProjectVersionRef()); return !traversal.getCycles().isEmpty(); } public Set<ProjectVersionRef> getAllProjects() { return new HashSet<ProjectVersionRef>(graph.getVertices()); } public void traverse(final ProjectNetTraversal traversal, final EProjectNet net, final ProjectVersionRef root) throws GraphDriverException { final int passes = traversal.getRequiredPasses(); for (int i = 0; i < passes; i++) { traversal.startTraverse(i, net); switch (traversal.getType(i)) { case breadth_first: { bfsTraverse(traversal, i, root); break; } case depth_first: { dfsTraverse(traversal, i, root); break; } } traversal.endTraverse(i, net); } } // TODO: Implement without recursion. private void dfsTraverse(final ProjectNetTraversal traversal, final int pass, final ProjectVersionRef root) { dfsIterate(root, traversal, new LinkedList<ProjectRelationship<?>>(), pass); } private void dfsIterate(final ProjectVersionRef node, final ProjectNetTraversal traversal, final LinkedList<ProjectRelationship<?>> path, final int pass) { final List<ProjectRelationship<?>> edges = getSortedOutEdges(node); if (edges != null) { for (final ProjectRelationship<?> edge : edges) { if (traversal.traverseEdge(edge, path, pass)) { if (!(edge instanceof ParentRelationship) || !((ParentRelationship) edge).isTerminus()) { ProjectVersionRef target = edge.getTarget(); if (target instanceof ArtifactRef) { target = ((ArtifactRef) target).asProjectVersionRef(); } // FIXME: Are there cases where a traversal needs to see cycles?? boolean cycle = false; for (final ProjectRelationship<?> item : path) { if (item.getDeclaring().equals(target)) { cycle = true; break; } } if (!cycle) { path.addLast(edge); dfsIterate(target, traversal, path, pass); path.removeLast(); } } traversal.edgeTraversed(edge, path, pass); } } } } // TODO: Implement without recursion. private void bfsTraverse(final ProjectNetTraversal traversal, final int pass, final ProjectVersionRef root) { final List<ProjectRelationship<?>> path = new ArrayList<ProjectRelationship<?>>(); path.add(new SelfEdge(root)); bfsIterate(Collections.singletonList(path), traversal, pass); } private void bfsIterate(final List<List<ProjectRelationship<?>>> thisLayer, final ProjectNetTraversal traversal, final int pass) { final List<List<ProjectRelationship<?>>> nextLayer = new ArrayList<List<ProjectRelationship<?>>>(); for (final List<ProjectRelationship<?>> path : thisLayer) { if (path.isEmpty()) { continue; } ProjectVersionRef node = path.get(path.size() - 1).getTarget(); if (node instanceof ArtifactRef) { node = ((ArtifactRef) node).asProjectVersionRef(); } if (!path.isEmpty() && (path.get(0) instanceof SelfEdge)) { path.remove(0); } final List<ProjectRelationship<?>> edges = getSortedOutEdges(node); if (edges != null) { for (final ProjectRelationship<?> edge : edges) { // call traverseEdge no matter what, to allow traversal to "see" all relationships. if ( /*( edge instanceof SelfEdge ) ||*/traversal.traverseEdge(edge, path, pass)) { // Don't account for terminal parent relationship. if (!(edge instanceof ParentRelationship) || !((ParentRelationship) edge).isTerminus()) { final List<ProjectRelationship<?>> nextPath = new ArrayList<ProjectRelationship<?>>( path); // FIXME: How do we avoid cycle traversal here?? nextPath.add(edge); nextLayer.add(nextPath); } traversal.edgeTraversed(edge, path, pass); } } } } if (!nextLayer.isEmpty()) { Collections.sort(nextLayer, new RelationshipPathComparator()); bfsIterate(nextLayer, traversal, pass); } } private List<ProjectRelationship<?>> getSortedOutEdges(final ProjectVersionRef node) { Collection<ProjectRelationship<?>> unsorted = graph.getOutEdges(node); if (unsorted == null) { return null; } unsorted = new ArrayList<ProjectRelationship<?>>(unsorted); RelationshipUtils.filterTerminalParents(unsorted); final List<ProjectRelationship<?>> sorted = new ArrayList<ProjectRelationship<?>>(unsorted); Collections.sort(sorted, new RelationshipComparator()); return sorted; } private static final class SelfEdge extends AbstractProjectRelationship<ProjectVersionRef> { private static final long serialVersionUID = 1L; SelfEdge(final ProjectVersionRef ref) { super(null, null, ref, ref, 0); } @Override public ArtifactRef getTargetArtifact() { return new ArtifactRef(getTarget(), "pom", null, false); } public ProjectRelationship<ProjectVersionRef> selectDeclaring(final SingleVersion version) { return new SelfEdge(getDeclaring().selectVersion(version)); } public ProjectRelationship<ProjectVersionRef> selectTarget(final SingleVersion version) { return new SelfEdge(getDeclaring().selectVersion(version)); } } public EGraphDriver newInstanceFrom(final EProjectNet net, final ProjectRelationshipFilter filter, final ProjectVersionRef... from) throws GraphDriverException { final JungEGraphDriver neo = new JungEGraphDriver(this, filter, net, from); neo.restrictProjectMembership(Arrays.asList(from)); return neo; } public EGraphDriver newInstance() { return new JungEGraphDriver(); } public boolean containsProject(final ProjectVersionRef ref) { return graph.containsVertex(ref); } public boolean containsRelationship(final ProjectRelationship<?> rel) { return graph.containsEdge(rel); } public void restrictProjectMembership(final Collection<ProjectVersionRef> refs) { final Set<ProjectRelationship<?>> rels = new HashSet<ProjectRelationship<?>>(); for (final ProjectVersionRef ref : refs) { final Collection<ProjectRelationship<?>> edges = graph.getOutEdges(ref); if (edges != null) { rels.addAll(edges); } } restrictRelationshipMembership(rels); } public void restrictRelationshipMembership(final Collection<ProjectRelationship<?>> rels) { graph = new DirectedSparseMultigraph<ProjectVersionRef, ProjectRelationship<?>>(); incompleteSubgraphs.clear(); variableSubgraphs.clear(); addRelationships(rels.toArray(new ProjectRelationship<?>[] {})); recomputeIncompleteSubgraphs(); } public void close() throws IOException { // NOP; stored in memory. } public boolean isDerivedFrom(final EGraphDriver driver) { return false; } public boolean isMissing(final ProjectVersionRef project) { return !graph.containsVertex(project); } public boolean hasMissingProjects() { return !incompleteSubgraphs.isEmpty(); } public Set<ProjectVersionRef> getMissingProjects() { return new HashSet<ProjectVersionRef>(incompleteSubgraphs); } public boolean hasVariableProjects() { return !variableSubgraphs.isEmpty(); } public Set<ProjectVersionRef> getVariableProjects() { return new HashSet<ProjectVersionRef>(variableSubgraphs); } public boolean addCycle(final EProjectCycle cycle) { boolean changed = false; synchronized (this.cycles) { changed = this.cycles.add(cycle); } for (final ProjectRelationship<?> rel : cycle) { incompleteSubgraphs.remove(rel.getDeclaring()); } return changed; } public Set<EProjectCycle> getCycles() { return new HashSet<EProjectCycle>(cycles); } public boolean isCycleParticipant(final ProjectRelationship<?> rel) { for (final EProjectCycle cycle : cycles) { if (cycle.contains(rel)) { return true; } } return false; } public boolean isCycleParticipant(final ProjectVersionRef ref) { for (final EProjectCycle cycle : cycles) { if (cycle.contains(ref)) { return true; } } return false; } public void recomputeIncompleteSubgraphs() { for (final ProjectVersionRef vertex : getAllProjects()) { final Collection<? extends ProjectRelationship<?>> outEdges = getRelationshipsDeclaredBy(vertex); if (outEdges != null && !outEdges.isEmpty()) { incompleteSubgraphs.remove(vertex); } } } public Map<String, String> getProjectMetadata(final ProjectVersionRef ref) { return metadata.get(ref); } public void addProjectMetadata(final ProjectVersionRef ref, final String key, final String value) { if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { return; } final Map<String, String> md = getMetadata(ref); md.put(key, value); addMetadataOwner(key, ref); } private synchronized void addMetadataOwner(final String key, final ProjectVersionRef ref) { Set<ProjectVersionRef> owners = this.metadataOwners.get(key); if (owners == null) { owners = new HashSet<ProjectVersionRef>(); metadataOwners.put(key, owners); } owners.add(ref); } public void addProjectMetadata(final ProjectVersionRef ref, final Map<String, String> metadata) { if (metadata == null || metadata.isEmpty()) { return; } final Map<String, String> md = getMetadata(ref); md.putAll(metadata); } private synchronized Map<String, String> getMetadata(final ProjectVersionRef ref) { Map<String, String> metadata = this.metadata.get(ref); if (metadata == null) { metadata = new HashMap<String, String>(); this.metadata.put(ref, metadata); } return metadata; } public boolean includeGraph(final ProjectVersionRef project) { throw new UnsupportedOperationException( "need to implement notion of a global graph in jung before this can work."); } public synchronized void reindex() throws GraphDriverException { for (final Map.Entry<ProjectVersionRef, Map<String, String>> refEntry : metadata.entrySet()) { for (final Map.Entry<String, String> mdEntry : refEntry.getValue().entrySet()) { addMetadataOwner(mdEntry.getKey(), refEntry.getKey()); } } } public Set<ProjectVersionRef> getProjectsWithMetadata(final String key) { return metadataOwners.get(key); } public void selectVersionFor(final ProjectVersionRef variable, final ProjectVersionRef select) throws GraphDriverException { if (!select.isSpecificVersion()) { throw new GraphDriverException("Cannot select non-concrete version! Attempted to select: %s", select); } if (variable.isSpecificVersion()) { throw new GraphDriverException( "Cannot select version if target is already a concrete version! Attempted to select for: %s", variable); } selected.put(variable, select); // Don't worry about selecting for outbound edges, as those subgraphs are supposed to be the same... final Collection<ProjectRelationship<?>> rels = graph.getInEdges(variable); for (final ProjectRelationship<?> rel : rels) { ProjectRelationship<?> repl; if (rel.getTarget().asProjectVersionRef().equals(variable)) { repl = rel.selectTarget((SingleVersion) select.getVersionSpec()); } else { continue; } graph.removeEdge(rel); graph.addEdge(repl, repl.getDeclaring(), repl.getTarget().asProjectVersionRef()); replaced.put(rel, repl); } } public Map<ProjectVersionRef, ProjectVersionRef> clearSelectedVersions() { final Map<ProjectVersionRef, ProjectVersionRef> selected = new HashMap<ProjectVersionRef, ProjectVersionRef>( this.selected); selected.clear(); for (final Map.Entry<ProjectRelationship<?>, ProjectRelationship<?>> entry : replaced.entrySet()) { final ProjectRelationship<?> rel = entry.getKey(); final ProjectRelationship<?> repl = entry.getValue(); graph.removeEdge(repl); graph.addEdge(rel, rel.getDeclaring(), rel.getTarget().asProjectVersionRef()); } for (final ProjectVersionRef select : new HashSet<ProjectVersionRef>(selected.values())) { final Collection<ProjectRelationship<?>> edges = graph.getInEdges(select); if (edges.isEmpty()) { graph.removeVertex(select); } } return selected; } public Map<ProjectVersionRef, ProjectVersionRef> getSelectedVersions() { return selected; } private static final class CycleDetectionTraversal extends AbstractTraversal { private final List<EProjectCycle> cycles = new ArrayList<EProjectCycle>(); private final ProjectRelationship<?> rel; private CycleDetectionTraversal(final ProjectRelationship<?> rel) { this.rel = rel; } public List<EProjectCycle> getCycles() { return cycles; } public boolean preCheck(final ProjectRelationship<?> relationship, final List<ProjectRelationship<?>> path, final int pass) { if (rel.getDeclaring().equals(rel.getTarget().asProjectVersionRef())) { return false; } new Logger(getClass()).info("Checking for cycle:\n\n%s\n\n", join(path, "\n")); final ProjectVersionRef from = rel.getDeclaring(); if (from.equals(relationship.getTarget().asProjectVersionRef())) { final List<ProjectRelationship<?>> cycle = new ArrayList<ProjectRelationship<?>>(path); cycle.add(rel); cycles.add(new EProjectCycle(cycle)); return false; } return true; } } private static final class PathDetectionTraversal extends AbstractTraversal { private final ProjectVersionRef[] to; private final Set<List<ProjectRelationship<?>>> paths = new HashSet<List<ProjectRelationship<?>>>(); private PathDetectionTraversal(final ProjectVersionRef[] refs) { this.to = refs; } public Set<List<ProjectRelationship<?>>> getPaths() { return paths; } public boolean preCheck(final ProjectRelationship<?> relationship, final List<ProjectRelationship<?>> path, final int pass) { final ProjectVersionRef target = relationship.getTarget().asProjectVersionRef(); for (final ProjectVersionRef t : to) { if (t.equals(target)) { paths.add(new ArrayList<ProjectRelationship<?>>(path)); return false; } } return true; } } public Set<ProjectVersionRef> getRoots() { return new HashSet<ProjectVersionRef>(Arrays.asList(roots)); } public void addDisconnectedProject(final ProjectVersionRef ref) { if (!graph.containsVertex(ref)) { graph.addVertex(ref); } } }