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

pomutil.POM.scala Maven / Gradle / Ivy

There is a newer version: 1.8
Show newest version
//
// pom-util - a Scala library for reading Maven POM files
// http://github.com/samskivert/pom-util/blob/master/LICENSE

package pomutil

import java.io.File
import java.util.regex.Pattern

import scala.collection.mutable.ArrayBuffer
import scala.xml.{XML, Node}

/**
 * Project metadata.
 */
class POM (
  val parent :Option[POM],
  val parentDep :Option[Dependency],
  elem :Node
) {
  import POM._

  lazy val modelVersion :String = attr("modelVersion") getOrElse("4.0.0")

  lazy val groupId :String = attr("groupId") orElse(parent map(_.groupId)) getOrElse("missing")
  lazy val artifactId :String = attr("artifactId") getOrElse("missing")
  lazy val version :String = attr("version") orElse(parent map(_.version)) getOrElse("missing")
  lazy val packaging :String = attr("packaging") orElse(parent map(_.packaging)) getOrElse("missing")

  lazy val name :Option[String] = attr("name")
  lazy val description :Option[String] = attr("description")
  lazy val url :Option[String] = attr("url")

  lazy val properties :Map[String,String] =
    (elem \ "properties" \ "_") map(n => (n.label.trim, n.text.trim)) toMap
  lazy val depends :Seq[Dependency] =
    (elem \ "dependencies" \ "dependency") map(Dependency.fromXML(subProps))
  lazy val modules :Seq[String] =
    (elem \ "modules" \\ "module") map(_.text.trim)
  lazy val profiles :Seq[Profile] =
    (elem \ "profiles" \\ "profile") map(new Profile(this, _))

  /** Returns an identifier that encompases the group, artifact and version. */
  def id = groupId + ":" + artifactId + ":" + version

  /** Returns all modules defined in the main POM and in all profiles. */
  def allModules = modules ++ profiles.flatMap(_.modules)

  // TODO: other bits

  /** Looks up a POM attribute, which may include properties defined in the POM as well as basic
   * project attributes like `project.version`, etc. */
  def getAttr (name :String) :Option[String] =
    // TODO: support env.x and Java system properties?
    getProjectAttr(name) orElse properties.get(name) orElse parent.flatMap(_.getAttr(name))

  /** Returns a dependency on the (optionally classified) artifact described by this POM. */
  def toDependency (classifier :Option[String] = None,
                    scope :String = Dependency.DefaultScope,
                    optional :Boolean = false) =
    Dependency(groupId, artifactId, version, packaging, classifier, scope, optional)

  /** Returns true if this POM declares a snapshot artifact, false otherwise. */
  def isSnapshot = version.endsWith("-SNAPSHOT")

  /** Extracts the text of an attribute from the supplied element and substitutes properties. */
  def attr (elem :Node, name :String) :Option[String] = XMLUtil.text(elem, name) map(subProps)

  /** A function that substitutes this POM's properties into the supplied text. */
  val subProps = (text :String) => {
    val m = PropRe.matcher(text)
    val sb = new StringBuffer
    while (m.find()) {
      val name = m.group(1)
      m.appendReplacement(sb, getAttr(name).getOrElse("\\$!{" + name + "}"))
    }
    m.appendTail(sb)
    sb.toString
  }

  override def toString = groupId + ":" + artifactId + ":" + version +
    parent.map(p => "\n  (p: " + p + ")").getOrElse("")

  private def getProjectAttr (key :String) :Option[String] =
    if (!key.startsWith("project.")) None else key.substring(8) match {
      case "groupId" => Some(groupId)
      case "artifactId" => Some(artifactId)
      case "version" => Some(version)
      case "packaging" => Some(packaging)
      case "name" => name
      case "description" => description
      case "url" => url
      case pkey if (pkey.startsWith("parent.")) => pkey.substring(7) match {
        case "groupId" => parentDep.map(_.groupId)
        case "artifactId" => parentDep.map(_.artifactId)
        case "version" => parentDep.map(_.version)
        case _ => None
      }
      case _ => None
    }

  private def attr (name :String) :Option[String] = attr(elem, name)
}

object POM {
  import XMLUtil._

  /** Parses the POM in the specified file. */
  def fromFile (file :File) :Option[POM] = fromXML(XML.loadFile(file), Some(file.getAbsoluteFile))

  /** Parses a POM from the supplied XML. */
  def fromXML (node :Node, file :Option[File]) :Option[POM] = node match {
    case elem if (elem.label == "project") => {
      val parentDep = (elem \ "parent").headOption map(Dependency.fromXML) map(
        _.copy(`type` = "pom"))
      val parent = try {
        localParent(file, parentDep) orElse installedParent(parentDep)
      } catch {
        case e => println("Failed to read parent pom (" + parentDep + "): " + e.getMessage) ; None
      }
      Some(new POM(parent, parentDep, elem))
    }
    case _ => None
  }

  private def localParent (file :Option[File], parentDep :Option[Dependency]) = for {
    pdep <- parentDep
    pomFile <- file
    pomDir <- Option(pomFile.getParentFile)
    parentDir <- Option(pomDir.getParentFile)
    val parentFile = new File(parentDir, "pom.xml")
    if (parentFile.exists)
    pom <- fromFile(parentFile)
    if (pom.toDependency() == pdep)
  } yield pom

  private def installedParent (parentDep :Option[Dependency]) = for {
    pdep <- parentDep
    pfile <- pdep.localPOM
    pom <- fromFile(pfile)
  } yield pom

  private val PropRe = Pattern.compile("\\$\\{([^}]+)\\}")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy