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

org.eclipse.aether.internal.test.util.DependencyGraphParser Maven / Gradle / Ivy

/*
 * 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.eclipse.aether.internal.test.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.DefaultDependencyNode;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.VersionScheme;

/**
 * Creates a dependency graph from a text description. 

Definition

Each (non-empty) line in the input defines * one node of the resulting graph: * *
 * line      ::= (indent? ("(null)" | node | reference))? comment?
 * comment   ::= "#" rest-of-line
 * indent    ::= "|  "*  ("+" | "\\") "- "
 * reference ::= "^" id
 * node      ::= coords (range)? space (scope("<" premanagedScope)?)? space "optional"? space
 *                  ("relocations=" coords ("," coords)*)? ("(" id ")")?
 * coords    ::= groupId ":" artifactId (":" extension (":" classifier)?)? ":" version
 * 
* * The special token {@code (null)} may be used to indicate an "empty" root node with no dependency. *

* If {@code indent} is empty, the line defines the root node. Only one root node may be defined. The level is * calculated by the distance from the beginning of the line. One level is three characters of indentation. *

* The {@code ^id} syntax allows to reuse a previously built node to share common sub graphs among different parent * nodes. *

Example

* *
 * gid:aid:ver
 * +- gid:aid2:ver scope
 * |  \- gid:aid3:ver        (id1)    # assign id for reference below
 * +- gid:aid4:ext:ver scope
 * \- ^id1                            # reuse previous node
 * 
* *

Multiple definitions in one resource

*

* By using {@link #parseMultiResource(String)}, definitions divided by a line beginning with "---" can be read from the * same resource. The rest of the line is ignored. *

Substitutions

*

* You may define substitutions (see {@link #setSubstitutions(String...)}, * {@link #DependencyGraphParser(String, Collection)}). Every '%s' in the definition will be substituted by the next * String in the defined substitutions. *

Example

* *
 * parser.setSubstitutions( "foo", "bar" );
 * String def = "gid:%s:ext:ver\n" + "+- gid:%s:ext:ver";
 * 
* * The first node will have "foo" as its artifact id, the second node (child to the first) will have "bar" as its * artifact id. */ public class DependencyGraphParser { private final VersionScheme versionScheme; private final String prefix; private Collection substitutions; /** * Create a parser with the given prefix and the given substitution strings. * * @see DependencyGraphParser#parseResource(String) */ public DependencyGraphParser(String prefix, Collection substitutions) { this.prefix = prefix; this.substitutions = substitutions; versionScheme = new TestVersionScheme(); } /** * Create a parser with the given prefix. * * @see DependencyGraphParser#parseResource(String) */ public DependencyGraphParser(String prefix) { this(prefix, Collections.emptyList()); } /** * Create a parser with an empty prefix. */ public DependencyGraphParser() { this(""); } /** * Parse the given graph definition. */ public DependencyNode parseLiteral(String dependencyGraph) throws IOException { BufferedReader reader = new BufferedReader(new StringReader(dependencyGraph)); DependencyNode node = parse(reader); reader.close(); return node; } /** * Parse the graph definition read from the given classpath resource. If a prefix is set, this method will load the * resource from 'prefix + resource'. */ public DependencyNode parseResource(String resource) throws IOException { URL res = this.getClass().getClassLoader().getResource(prefix + resource); if (res == null) { throw new IOException("Could not find classpath resource " + prefix + resource); } return parse(res); } /** * Parse multiple graphs in one resource, divided by "---". */ public List parseMultiResource(String resource) throws IOException { URL res = this.getClass().getClassLoader().getResource(prefix + resource); if (res == null) { throw new IOException("Could not find classpath resource " + prefix + resource); } BufferedReader reader = new BufferedReader(new InputStreamReader(res.openStream(), StandardCharsets.UTF_8)); List ret = new ArrayList<>(); DependencyNode root = null; while ((root = parse(reader)) != null) { ret.add(root); } return ret; } /** * Parse the graph definition read from the given URL. */ public DependencyNode parse(URL resource) throws IOException { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8)); return parse(reader); } finally { try { if (reader != null) { reader.close(); reader = null; } } catch (final IOException e) { // Suppressed due to an exception already thrown in the try block. } } } private DependencyNode parse(BufferedReader in) throws IOException { Iterator substitutionIterator = (substitutions != null) ? substitutions.iterator() : null; String line = null; DependencyNode root = null; DependencyNode node = null; int prevLevel = 0; Map nodes = new HashMap<>(); LinkedList stack = new LinkedList<>(); boolean isRootNode = true; while ((line = in.readLine()) != null) { line = cutComment(line); if (isEmpty(line)) { // skip empty line continue; } if (isEOFMarker(line)) { // stop parsing break; } while (line.contains("%s")) { if (!substitutionIterator.hasNext()) { throw new IllegalStateException("not enough substitutions to fill placeholders"); } line = line.replaceFirst("%s", substitutionIterator.next()); } LineContext ctx = createContext(line); if (prevLevel < ctx.getLevel()) { // previous node is new parent stack.add(node); } // get to real parent while (prevLevel > ctx.getLevel()) { stack.removeLast(); prevLevel -= 1; } prevLevel = ctx.getLevel(); if (ctx.getDefinition() != null && ctx.getDefinition().reference != null) { String reference = ctx.getDefinition().reference; DependencyNode child = nodes.get(reference); if (child == null) { throw new IllegalStateException("undefined reference " + reference); } node.getChildren().add(child); } else { node = build(isRootNode ? null : stack.getLast(), ctx, isRootNode); if (isRootNode) { root = node; isRootNode = false; } if (ctx.getDefinition() != null && ctx.getDefinition().id != null) { nodes.put(ctx.getDefinition().id, node); } } } return root; } private boolean isEOFMarker(String line) { return line.startsWith("---"); } private static boolean isEmpty(String line) { return line == null || line.isEmpty(); } private static String cutComment(String line) { int idx = line.indexOf('#'); if (idx != -1) { line = line.substring(0, idx); } return line; } private DependencyNode build(DependencyNode parent, LineContext ctx, boolean isRoot) { NodeDefinition def = ctx.getDefinition(); if (!isRoot && parent == null) { throw new IllegalStateException("dangling node: " + def); } else if (ctx.getLevel() == 0 && parent != null) { throw new IllegalStateException("inconsistent leveling (parent for level 0?): " + def); } DefaultDependencyNode node; if (def != null) { DefaultArtifact artifact = new DefaultArtifact(def.coords, def.properties); Dependency dependency = new Dependency(artifact, def.scope, def.optional); node = new DefaultDependencyNode(dependency); int managedBits = 0; if (def.premanagedScope != null) { managedBits |= DependencyNode.MANAGED_SCOPE; node.setData("premanaged.scope", def.premanagedScope); } if (def.premanagedVersion != null) { managedBits |= DependencyNode.MANAGED_VERSION; node.setData("premanaged.version", def.premanagedVersion); } node.setManagedBits(managedBits); if (def.relocations != null) { List relocations = new ArrayList<>(); for (String relocation : def.relocations) { relocations.add(new DefaultArtifact(relocation)); } node.setRelocations(relocations); } try { node.setVersion(versionScheme.parseVersion(artifact.getVersion())); node.setVersionConstraint( versionScheme.parseVersionConstraint(def.range != null ? def.range : artifact.getVersion())); } catch (InvalidVersionSpecificationException e) { throw new IllegalArgumentException("bad version: " + e.getMessage(), e); } } else { node = new DefaultDependencyNode((Dependency) null); } if (parent != null) { parent.getChildren().add(node); } return node; } public String dump(DependencyNode root) { StringBuilder ret = new StringBuilder(); List entries = new ArrayList<>(); addNode(root, 0, entries); for (NodeEntry nodeEntry : entries) { char[] level = new char[(nodeEntry.getLevel() * 3)]; Arrays.fill(level, ' '); if (level.length != 0) { level[level.length - 3] = '+'; level[level.length - 2] = '-'; } String definition = nodeEntry.getDefinition(); ret.append(level).append(definition).append("\n"); } return ret.toString(); } private void addNode(DependencyNode root, int level, List entries) { NodeEntry entry = new NodeEntry(); Dependency dependency = root.getDependency(); StringBuilder defBuilder = new StringBuilder(); if (dependency == null) { defBuilder.append("(null)"); } else { Artifact artifact = dependency.getArtifact(); defBuilder .append(artifact.getGroupId()) .append(":") .append(artifact.getArtifactId()) .append(":") .append(artifact.getExtension()) .append(":") .append(artifact.getVersion()); if (dependency.getScope() != null && (!"".equals(dependency.getScope()))) { defBuilder.append(":").append(dependency.getScope()); } Map properties = artifact.getProperties(); if (!(properties == null || properties.isEmpty())) { for (Map.Entry prop : properties.entrySet()) { defBuilder.append(";").append(prop.getKey()).append("=").append(prop.getValue()); } } } entry.setDefinition(defBuilder.toString()); entry.setLevel(level++); entries.add(entry); for (DependencyNode node : root.getChildren()) { addNode(node, level, entries); } } class NodeEntry { int level; String definition; Map properties; public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public String getDefinition() { return definition; } public void setDefinition(String definition) { this.definition = definition; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } } private static LineContext createContext(String line) { LineContext ctx = new LineContext(); String definition; String[] split = line.split("- "); if (split.length == 1) // root { ctx.setLevel(0); definition = split[0]; } else { ctx.setLevel((int) Math.ceil((double) split[0].length() / (double) 3)); definition = split[1]; } if ("(null)".equalsIgnoreCase(definition)) { return ctx; } ctx.setDefinition(new NodeDefinition(definition)); return ctx; } static class LineContext { NodeDefinition definition; int level; public NodeDefinition getDefinition() { return definition; } public void setDefinition(NodeDefinition definition) { this.definition = definition; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } } public Collection getSubstitutions() { return substitutions; } public void setSubstitutions(Collection substitutions) { this.substitutions = substitutions; } public void setSubstitutions(String... substitutions) { setSubstitutions(Arrays.asList(substitutions)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy