PageRenderTime 358ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/opup/src/main/scala/com/atlassian/labs/opup/PomScanner.scala

https://bitbucket.org/jwalton/opup
Scala | 296 lines | 230 code | 55 blank | 11 comment | 34 complexity | 1e126e220df1a09601ecc224caca0904 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. package com.atlassian.labs.opup;
  2. import java.io.File;
  3. import java.io.FileInputStream
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.HashMap;
  9. import java.util.HashSet;
  10. import java.util.Iterator;
  11. import java.util.Map;
  12. import java.util.Set;
  13. import java.util.regex.Matcher;
  14. import java.util.regex.Pattern;
  15. import javax.xml.parsers.DocumentBuilder;
  16. import javax.xml.parsers.DocumentBuilderFactory;
  17. import javax.xml.parsers.ParserConfigurationException;
  18. import javax.xml.xpath.XPath;
  19. import javax.xml.xpath.XPathConstants;
  20. import javax.xml.xpath.XPathExpression;
  21. import javax.xml.xpath.XPathExpressionException;
  22. import javax.xml.xpath.XPathFactory;
  23. import org.apache.maven.artifact.versioning.ArtifactVersion;
  24. import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
  25. import org.w3c.dom.Document;
  26. import org.w3c.dom.Element;
  27. import org.w3c.dom.NodeList;
  28. import org.w3c.dom.Node;
  29. import org.xml.sax.SAXException;
  30. import scala.collection.GenIterable
  31. import scala.collection.JavaConverters._
  32. object PomScanner
  33. {
  34. def pomFiles(base: File): Collection[File] = {
  35. val poms = new ArrayList[File]();
  36. recurse(poms, base);
  37. return poms;
  38. }
  39. def recurse(poms: Collection[File], dir: File): Unit = {
  40. val p = new File(dir, "pom.xml")
  41. if (p.isFile())
  42. {
  43. poms.add(p);
  44. // Could be null?
  45. dir.listFiles().filter(_.isDirectory).foreach(recurse(poms, _))
  46. }
  47. }
  48. def groupAndArtifact(coords: String): String =
  49. {
  50. val sc = coords.indexOf(':', coords.indexOf(':') + 1)
  51. if (sc >= 0)
  52. {
  53. return coords.substring(0, sc)
  54. }
  55. else
  56. {
  57. return coords
  58. }
  59. }
  60. }
  61. class PomScanner
  62. {
  63. val defined: Set[String] = new HashSet()
  64. var docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
  65. var xpath = XPathFactory.newInstance().newXPath()
  66. val versions: Map[String, Collection[String]] = new HashMap()
  67. val properties: Map[String, Collection[String]] = new HashMap()
  68. val requirements: Set[String] = new HashSet()
  69. def add[K, V](m: Map[K, Collection[V]], k: K, v: V) = {
  70. var c = m.get(k);
  71. if (c == null)
  72. {
  73. c = new HashSet();
  74. m.put(k, c);
  75. }
  76. c.add(v);
  77. }
  78. def addVersion(groupAndArtifact: String, version: String) =
  79. {
  80. add(versions, groupAndArtifact, version)
  81. }
  82. def addProperty(name: String, value: String) =
  83. {
  84. add(properties, name, value);
  85. }
  86. def evaluate(xp: String, d: Node) =
  87. xpath.evaluate(xp, d, XPathConstants.STRING).asInstanceOf[String]
  88. def evaluateNodeset(xp: String, d: Node) =
  89. xpath.evaluate(xp, d, XPathConstants.NODESET).asInstanceOf[NodeList]
  90. def elements(nl: NodeList) =
  91. Range(0, nl.getLength).map(nl.item(_)).collect{case e: Element => e}
  92. def process(poms: Iterable[File]): Unit = {
  93. for (f <- poms)
  94. {
  95. val in = new FileInputStream(f)
  96. try
  97. {
  98. this.process(in)
  99. }
  100. finally
  101. {
  102. in.close()
  103. }
  104. }
  105. }
  106. def process(in: InputStream): Unit = {
  107. val d = docBuilder.parse(in)
  108. /* This pom's artifact */
  109. var pomGroupId = evaluate("project/groupId", d)
  110. val pomArtifactId = evaluate("project/artifactId", d)
  111. if (pomGroupId.isEmpty())
  112. {
  113. pomGroupId = evaluate("project/parent/groupId", d)
  114. }
  115. val pomCoords = pomGroupId + ":" + pomArtifactId;
  116. defined.add(pomCoords);
  117. /* Properties */
  118. for (e <- elements(evaluateNodeset("project/properties/*", d)))
  119. {
  120. addProperty(e.getTagName(), evaluate(".", e))
  121. }
  122. for (e <- elements(evaluateNodeset("project/profiles/profile/properties/*", d)))
  123. {
  124. addProperty(e.getTagName(), evaluate(".", e))
  125. }
  126. /* Any dependency versions defined */
  127. for (x <- elements(evaluateNodeset("//dependency[groupId and artifactId]", d)))
  128. {
  129. val groupId = expanded(evaluate("groupId", x))
  130. val artifactId = expanded(evaluate("artifactId", x))
  131. val version = evaluate("version", x)
  132. val artifactType = evaluate("type", x)
  133. val scope = evaluate("scope", x)
  134. var coords = groupId + ":" + artifactId
  135. if (!artifactType.isEmpty() && !artifactType.equals("jar"))
  136. {
  137. coords += ":" + artifactType;
  138. }
  139. // Don't want to upgrade provided dependencies
  140. if (scope == null || !scope.equals("provided"))
  141. {
  142. requirements.add(coords);
  143. }
  144. if (!version.isEmpty())
  145. {
  146. addVersion(groupId + ":" + artifactId, version);
  147. }
  148. }
  149. }
  150. // Maybe sub-optimal
  151. def getDefinedArtifacts: Collection[String] = defined
  152. // private def toVersions(vs: GenIterable[String]) = vs.map[ArtifactVersion, collection.mutable.LinkedList[ArtifactVersion]](new DefaultArtifactVersion(_))
  153. private def toVersions(vs: GenIterable[String]) = vs.map(new DefaultArtifactVersion(_).asInstanceOf[ArtifactVersion])
  154. val currentVersions: Map[String, ArtifactVersion] = new HashMap()
  155. val currentProperties: Map[String, ArtifactVersion] = new HashMap()
  156. def getCurrentVersions() = currentVersions
  157. def getCurrentProperties() = currentProperties
  158. def findCurrent(report: ReportTarget) = {
  159. val external: Set[String] = new HashSet(requirements)
  160. /* We don't want anything we already have */
  161. external.removeAll(external.asScala.map(PomScanner.groupAndArtifact(_)).filter(defined.contains(_)).asJava)
  162. for (s <- external.asScala)
  163. {
  164. val v = versions.get(PomScanner.groupAndArtifact(s));
  165. if (v == null)
  166. {
  167. report.implementationLimitation("Current version defined outside project for " + s)
  168. }
  169. else if (v.size() != 1)
  170. {
  171. report.multipleVersions(s, toVersions(v.asScala).toList.asJava);
  172. }
  173. else if (v.iterator().next().contains("$"))
  174. {
  175. val expr = v.iterator().next();
  176. val propnameOption = propNameFromExpression(expr);
  177. val propname = propnameOption //.getOrElse(throw new RuntimeException("Bad expression: " + expr))
  178. if (propname != null)
  179. {
  180. /* Don't try to upgrade internal dependencies */
  181. if (!propname.equals("project.version"))
  182. {
  183. val values = properties.get(propname);
  184. if (values == null)
  185. {
  186. report.undefinedProperty(s, propname)
  187. }
  188. else if (values.size() != 1)
  189. {
  190. report.multipleValues(propname, toVersions(values.asScala).toList.asJava);
  191. }
  192. else
  193. {
  194. val current = new DefaultArtifactVersion(values.iterator().next());
  195. currentVersions.put(s, current);
  196. currentProperties.put(propname, current);
  197. }
  198. }
  199. }
  200. else
  201. {
  202. report.implementationLimitation("Ignoring composite version property: " + expr)
  203. }
  204. }
  205. else
  206. {
  207. currentVersions.put(s, new DefaultArtifactVersion(v.iterator().next()));
  208. }
  209. }
  210. }
  211. def getPropertyForArtifactVersion(artifact: String): String = {
  212. val v = versions.get(PomScanner.groupAndArtifact(artifact))
  213. if (v.size() == 1 && v.iterator().next().startsWith("$"))
  214. {
  215. val expr = v.iterator().next()
  216. val prop = propNameFromExpression(expr);
  217. return Option(prop).getOrElse(throw new RuntimeException("Bad expression: " + expr))
  218. }
  219. else
  220. {
  221. return null
  222. }
  223. }
  224. val SINGLE_PROPERTY_EXPRESSION = """\$\{([-\._a-zA-Z0-9]+)\}""".r
  225. def propNameFromExpression(expr: String): String =
  226. {
  227. expr match {
  228. case SINGLE_PROPERTY_EXPRESSION(propertyName) => propertyName;
  229. case _ => null
  230. }
  231. // SINGLE_PROPERTY_EXPRESSION.unapplySeq(expr).map(_(0)).getOrElse(null)
  232. }
  233. def expanded(id: String) =
  234. SINGLE_PROPERTY_EXPRESSION replaceSomeIn (id, m => {
  235. // Ugly; yields a single unique value, or None
  236. properties.asScala.get(m.group(1)).map(_.asScala.toSet).getOrElse(collection.immutable.Set()).toList match {
  237. case List(singleValue) => Some(singleValue)
  238. case _ => None
  239. }
  240. })
  241. }