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

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

There is a newer version: 2.0.5
Show newest version
package org.eclipse.aether.internal.test.util;

/*
 * 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.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 ) ); final DependencyNode node = parse( reader ); return node; } 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.length() == 0; } 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