/opup/src/main/scala/com/atlassian/labs/opup/PomScanner.scala
Scala | 296 lines | 230 code | 55 blank | 11 comment | 34 complexity | 1e126e220df1a09601ecc224caca0904 MD5 | raw file
Possible License(s): BSD-3-Clause
- package com.atlassian.labs.opup;
- import java.io.File;
- import java.io.FileInputStream
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.Set;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import javax.xml.parsers.ParserConfigurationException;
- import javax.xml.xpath.XPath;
- import javax.xml.xpath.XPathConstants;
- import javax.xml.xpath.XPathExpression;
- import javax.xml.xpath.XPathExpressionException;
- import javax.xml.xpath.XPathFactory;
- import org.apache.maven.artifact.versioning.ArtifactVersion;
- import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
- import org.w3c.dom.Document;
- import org.w3c.dom.Element;
- import org.w3c.dom.NodeList;
- import org.w3c.dom.Node;
- import org.xml.sax.SAXException;
- import scala.collection.GenIterable
- import scala.collection.JavaConverters._
- object PomScanner
- {
- def pomFiles(base: File): Collection[File] = {
- val poms = new ArrayList[File]();
- recurse(poms, base);
- return poms;
- }
- def recurse(poms: Collection[File], dir: File): Unit = {
- val p = new File(dir, "pom.xml")
- if (p.isFile())
- {
- poms.add(p);
- // Could be null?
- dir.listFiles().filter(_.isDirectory).foreach(recurse(poms, _))
- }
- }
- def groupAndArtifact(coords: String): String =
- {
- val sc = coords.indexOf(':', coords.indexOf(':') + 1)
- if (sc >= 0)
- {
- return coords.substring(0, sc)
- }
- else
- {
- return coords
- }
- }
- }
- class PomScanner
- {
- val defined: Set[String] = new HashSet()
- var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
- var xpath = XPathFactory.newInstance().newXPath()
- val versions: Map[String, Collection[String]] = new HashMap()
- val properties: Map[String, Collection[String]] = new HashMap()
- val requirements: Set[String] = new HashSet()
- def add[K, V](m: Map[K, Collection[V]], k: K, v: V) = {
- var c = m.get(k);
- if (c == null)
- {
- c = new HashSet();
- m.put(k, c);
- }
- c.add(v);
- }
- def addVersion(groupAndArtifact: String, version: String) =
- {
- add(versions, groupAndArtifact, version)
- }
- def addProperty(name: String, value: String) =
- {
- add(properties, name, value);
- }
- def evaluate(xp: String, d: Node) =
- xpath.evaluate(xp, d, XPathConstants.STRING).asInstanceOf[String]
- def evaluateNodeset(xp: String, d: Node) =
- xpath.evaluate(xp, d, XPathConstants.NODESET).asInstanceOf[NodeList]
- def elements(nl: NodeList) =
- Range(0, nl.getLength).map(nl.item(_)).collect{case e: Element => e}
- def process(poms: Iterable[File]): Unit = {
- for (f <- poms)
- {
- val in = new FileInputStream(f)
- try
- {
- this.process(in)
- }
- finally
- {
- in.close()
- }
- }
- }
- def process(in: InputStream): Unit = {
- val d = docBuilder.parse(in)
- /* This pom's artifact */
- var pomGroupId = evaluate("project/groupId", d)
- val pomArtifactId = evaluate("project/artifactId", d)
- if (pomGroupId.isEmpty())
- {
- pomGroupId = evaluate("project/parent/groupId", d)
- }
- val pomCoords = pomGroupId + ":" + pomArtifactId;
- defined.add(pomCoords);
- /* Properties */
- for (e <- elements(evaluateNodeset("project/properties/*", d)))
- {
- addProperty(e.getTagName(), evaluate(".", e))
- }
- for (e <- elements(evaluateNodeset("project/profiles/profile/properties/*", d)))
- {
- addProperty(e.getTagName(), evaluate(".", e))
- }
- /* Any dependency versions defined */
- for (x <- elements(evaluateNodeset("//dependency[groupId and artifactId]", d)))
- {
- val groupId = expanded(evaluate("groupId", x))
- val artifactId = expanded(evaluate("artifactId", x))
- val version = evaluate("version", x)
- val artifactType = evaluate("type", x)
- val scope = evaluate("scope", x)
- var coords = groupId + ":" + artifactId
- if (!artifactType.isEmpty() && !artifactType.equals("jar"))
- {
- coords += ":" + artifactType;
- }
- // Don't want to upgrade provided dependencies
- if (scope == null || !scope.equals("provided"))
- {
- requirements.add(coords);
- }
- if (!version.isEmpty())
- {
- addVersion(groupId + ":" + artifactId, version);
- }
- }
- }
- // Maybe sub-optimal
- def getDefinedArtifacts: Collection[String] = defined
- // private def toVersions(vs: GenIterable[String]) = vs.map[ArtifactVersion, collection.mutable.LinkedList[ArtifactVersion]](new DefaultArtifactVersion(_))
- private def toVersions(vs: GenIterable[String]) = vs.map(new DefaultArtifactVersion(_).asInstanceOf[ArtifactVersion])
- val currentVersions: Map[String, ArtifactVersion] = new HashMap()
- val currentProperties: Map[String, ArtifactVersion] = new HashMap()
- def getCurrentVersions() = currentVersions
- def getCurrentProperties() = currentProperties
- def findCurrent(report: ReportTarget) = {
- val external: Set[String] = new HashSet(requirements)
- /* We don't want anything we already have */
- external.removeAll(external.asScala.map(PomScanner.groupAndArtifact(_)).filter(defined.contains(_)).asJava)
- for (s <- external.asScala)
- {
- val v = versions.get(PomScanner.groupAndArtifact(s));
- if (v == null)
- {
- report.implementationLimitation("Current version defined outside project for " + s)
- }
- else if (v.size() != 1)
- {
- report.multipleVersions(s, toVersions(v.asScala).toList.asJava);
- }
- else if (v.iterator().next().contains("$"))
- {
- val expr = v.iterator().next();
- val propnameOption = propNameFromExpression(expr);
- val propname = propnameOption //.getOrElse(throw new RuntimeException("Bad expression: " + expr))
- if (propname != null)
- {
- /* Don't try to upgrade internal dependencies */
- if (!propname.equals("project.version"))
- {
- val values = properties.get(propname);
- if (values == null)
- {
- report.undefinedProperty(s, propname)
- }
- else if (values.size() != 1)
- {
- report.multipleValues(propname, toVersions(values.asScala).toList.asJava);
- }
- else
- {
- val current = new DefaultArtifactVersion(values.iterator().next());
- currentVersions.put(s, current);
- currentProperties.put(propname, current);
- }
- }
- }
- else
- {
- report.implementationLimitation("Ignoring composite version property: " + expr)
- }
- }
- else
- {
- currentVersions.put(s, new DefaultArtifactVersion(v.iterator().next()));
- }
- }
- }
- def getPropertyForArtifactVersion(artifact: String): String = {
- val v = versions.get(PomScanner.groupAndArtifact(artifact))
- if (v.size() == 1 && v.iterator().next().startsWith("$"))
- {
- val expr = v.iterator().next()
- val prop = propNameFromExpression(expr);
- return Option(prop).getOrElse(throw new RuntimeException("Bad expression: " + expr))
- }
- else
- {
- return null
- }
- }
- val SINGLE_PROPERTY_EXPRESSION = """\$\{([-\._a-zA-Z0-9]+)\}""".r
- def propNameFromExpression(expr: String): String =
- {
- expr match {
- case SINGLE_PROPERTY_EXPRESSION(propertyName) => propertyName;
- case _ => null
- }
- // SINGLE_PROPERTY_EXPRESSION.unapplySeq(expr).map(_(0)).getOrElse(null)
- }
- def expanded(id: String) =
- SINGLE_PROPERTY_EXPRESSION replaceSomeIn (id, m => {
- // Ugly; yields a single unique value, or None
- properties.asScala.get(m.group(1)).map(_.asScala.toSet).getOrElse(collection.immutable.Set()).toList match {
- case List(singleValue) => Some(singleValue)
- case _ => None
- }
- })
- }