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

org.specs2.analysis.LayersAnalysis.scala Maven / Gradle / Ivy

The newest version!
package org.specs2
package analysis

import data.IncludedExcluded
import util.Properties._
/**
 * This trait allows to define expected dependencies between packages layers
 */
trait LayersAnalysis extends ClassycleDependencyFinder {

  /** this implicit definition allows to use a single string instead of a Layer object */
  implicit def toLayer(s: String): Layer = layer(s.split("\\s").map(l => l.trim):_*)
  /**
   * @return a new Layer object. If a prefix is defined, it will apply to each given name
   */
  def layer(names: String*): Layer = new Layer(names.filterNot(_.isEmpty).toSet)
  /**
   * @return a new Layers object, composed of other layers. If a prefix is defined it will apply to each layer
   */
  def layers(ls: Layer*) = Layers(ls)

  /**
   * The Layer class represent a list of packages.
   *
   * If those packages share a common prefix, it will be stored in the `prefix` member
   */
  case class Layer(names: Set[String], prefix: String = "",
                   sourceDir: String = "src/main/scala/",
                   targetDir: String = "target/scala-"+releaseVersion.orElse(developmentVersion).getOrElse("2.9.1")+"/classes/",
                   included: Seq[String] = Seq(), excluded: Seq[String] = Seq()) {
    /** specify a prefix for this layer packages */
    def withPrefix(p: String) = copy(prefix = if (this.prefix.isEmpty) p else p+"."+this.prefix)
    /** specify a source directory for this layer packages */
    def inSourceDir(s: String) = copy(sourceDir = s)
    /** specify a target directory for this layer packages */
    def inTargetDir(s: String) = copy(targetDir = s)

    override def toString = names.mkString("\n") + (if (prefix == "") "" else " ("+prefix+")")
    /** @return the package names */
    lazy val packageNames = names.map(n => if (prefix.isEmpty) n else prefix+"."+n)

    /** @return the list of dependents of each package of this layer */
    lazy val getDependents: Set[Dependency] = packageNames.flatMap(p => getPackageDependents(p, sourceDir, targetDir))

    /**
     * @return the list of dependencies showing that this layer depends on the `other` layer
     * meaning thisLayer -- depends on --> otherLayer
     */
    def dependsOn(otherLayer: Layer) = otherLayer.getDependents.filter(inThisLayer)

    /** use regexes to include fully qualified class names in the layer */
    def include(names: String*) = copy(included = names.map(n => ".*"+n+".*"))
    /** use regexes to exclude fully qualified class names from the layer */
    def exclude(names: String*) = copy(excluded = names.map(n => ".*"+n+".*"))

    /** @return true if the dependent class of this dependency has its package in this layer */
    def inThisLayer(d: Dependency) = includedExcluded.keep(d.dependentClassName) && (packageNames contains d.dependentPackageName)

    /**
     * check if a class name should be included or excluded from the dependency analysis, based on include/exclude regular expressions
     */
    private def includedExcluded = new IncludedExcluded[String] {
      val include = included
      val exclude = excluded

      val keepFunction    = (n: String, tags: Seq[String]) => tags.exists(t => n.matches(prefixed(t)))
    }

    private def prefixed(n: String) = if (prefix.isEmpty) n else prefix+"."+n
  }

  /**
   * This class represents an ordered set of layers, where each package on a given layer can only depend on packages defined in lower layers
   */
  case class Layers(layers: Seq[Layer]) {
    /** specify a prefix for all layers */
    def withPrefix(p: String) = copy(layers = layers.map(_.withPrefix(p)))
    /** specify a source directory for all layers */
    def inSourceDir(s: String) = copy(layers = layers.map(_.inSourceDir(s)))
    /** specify a target directory for all layers */
    def inTargetDir(s: String) = copy(layers = layers.map(_.inTargetDir(s)))

    override def toString = layers.mkString("\n")
    /** @return the layers as Markdown bullet points */
    lazy val toMarkdown: String = layers.map(l => " * "+l.names.mkString(" ")).mkString("\n")
    /** the list of dependencies which are not respecting the layers definitions */
    lazy val unsatisfied: Dependencies = new Dependencies(unsatisfiedDependencies)

    /**
     * @return the list of unsatisfied dependencies by:
     *
     * - taking all the subsequences of layers with at least 2 elements, say `Layers(l1, l2, l3) => Seq(Seq(l1, l2, l3), Seq(l1, l2))`
     * - getting all the dependencies of the last element of a sequence with all its parents (should always be empty)
     */
    private lazy val unsatisfiedDependencies = {
      layers.inits.filter(_.size > 1).flatMap(parents => parents.dropRight(1).flatMap(p => parents.last.dependsOn(p))).toSeq
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy