
org.opalj.av.viz.DependencyAnalysis.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package av
package viz
import java.net.URL
import java.util.concurrent.atomic.AtomicInteger
import scala.language.reflectiveCalls
import scala.util.Random
import org.opalj.io.writeAndOpen
import org.opalj.br.analyses.Analysis
import org.opalj.br.analyses.Project
import org.opalj.de.DependencyExtractor
import org.opalj.de.DependencyProcessor
import org.opalj.de.DependencyType
import org.opalj.br.ArrayType
import org.opalj.br.BaseType
import org.opalj.br.ObjectType
import org.opalj.br.VirtualClass
import org.opalj.br.VirtualSourceElement
import org.opalj.br.analyses.AnalysisApplication
import org.opalj.br.analyses.BasicReport
import org.opalj.br.analyses.ProgressManagement
import org.opalj.br.analyses.ProgressEvents
/**
* @author Tobias Becker
*/
object DependencyAnalysis extends AnalysisApplication {
val template = this.getClass().getResource("DependencyAnalysis.html.template")
if (template eq null)
throw new RuntimeException(
"the HTML template (DependencyAnalysis.html.template) cannot be found"
)
val colors = Set("#E41A1C", "#FFFF33", "#FF7F00", "#999999", "#984EA3", "#377EB8", "#4DAF4A", "#F781BF", "#A65628")
var mainPackage: String = ""
var debug = false
var filter: String = ""
var inverse: Boolean = false
def readParameter(param: String, args: Seq[String], default: String = ""): (String, Seq[String]) = {
args.partition(_.startsWith("-"+param+"=")) match {
case (Seq(), parameters1) => (default, parameters1)
case (Seq(p), parameters1) => {
if (p.startsWith("-"+param+"=\"") && p.endsWith("\""))
(p.substring(param.length + 3, p.length - 1), parameters1)
else
(p.substring(param.length + 2), parameters1)
}
}
}
override def checkAnalysisSpecificParameters(args: Seq[String]): Iterable[String] = {
val (mainPackage, parameters1) = readParameter("mp", args)
this.mainPackage = mainPackage
val (debug, parameters2) = readParameter("debug", parameters1, "false")
this.debug = debug.toBoolean
val (inverse, parameters3) = readParameter("inverse", parameters2, "false")
this.inverse = inverse.toBoolean
val (filter, parameters4) = readParameter("filter", parameters3)
this.filter = filter
if (parameters4.isEmpty)
Iterable.empty
else
parameters4.map("unknown parameter: "+_)
}
override def analysisSpecificParametersDescription: String = ""+
"[-mp= (Main Package, won't be clustered. default: \"\")]\n"+
"[-debug= (true, if there should be additional output. default: false)]\n"+
"[-inverse= (true, if incoming and outgoing dependencies should be switched. default: false)]\n"+
"[-filter= (Only show dependencies within packages with this prefix. default: \"\")]\n"
private def checkDocument(doc: String): String = {
val pattern = "<%[A-Z_]+%>".r
val option = pattern findFirstIn doc
option match {
case Some(o) => {
println(
Console.YELLOW+
"[warn] HtmlDocument has at least one unset option "+o +
Console.RESET
)
}
case None =>
}
doc
}
val analysis = new Analysis[URL, BasicReport] {
override def description: String =
"Collects information about the number of dependencies on others packages per package."
def analyze(
project: Project[URL],
parameters: Seq[String],
initProgressManagement: (Int) => ProgressManagement
) = {
val pm = initProgressManagement(3)
pm.progress(1, ProgressEvents.Start, Some("setup"))
import scala.collection.mutable.HashMap
val rootPackages = project.rootPackages
val dependencyProcessor = new DependencyProcessor {
protected[this] val dependencyCount = HashMap.empty[String, HashMap[String, Int]]
protected[this] val dependencyCounter = new AtomicInteger(0);
def addDependency(sourcePN: String, targetPN: String): Unit = {
val sourcePackage = getPackageName(sourcePN)
val targetPackage = getPackageName(targetPN)
// filter by -filter=
if (filter != "" && !sourcePackage.startsWith(filter) || !targetPackage.startsWith(filter))
return
// ignore interpackage dependencies
if (sourcePackage == targetPackage)
return
val depsForSource = dependencyCount.getOrElse(sourcePackage, HashMap.empty[String, Int])
depsForSource.update(targetPackage, depsForSource.getOrElse(targetPackage, 0) + 1)
dependencyCount.update(sourcePackage, depsForSource)
dependencyCounter.incrementAndGet()
}
override def processDependency(
source: VirtualSourceElement,
target: VirtualSourceElement,
dType: DependencyType
): Unit = {
if (source.isClass && target.isClass)
addDependency(
source.asInstanceOf[VirtualClass].thisType.packageName,
target.asInstanceOf[VirtualClass].thisType.packageName
)
}
def getPackageName(pn: String): String = {
if (pn == "") // standard package =
return ""
if (mainPackage != "" && pn.startsWith(mainPackage))
return pn
rootPackages.getOrElse(pn, pn)
}
def processDependency(
source: VirtualSourceElement,
baseType: BaseType,
dType: DependencyType
): Unit = {
}
def processDependency(
source: VirtualSourceElement,
arrayType: ArrayType,
dType: DependencyType
): Unit = {
if (source.isClass && arrayType.componentType.isObjectType)
addDependency(
source.asInstanceOf[VirtualClass].thisType.packageName,
arrayType.componentType.asInstanceOf[ObjectType].packageName
)
}
def currentDependencyCount(source: String, target: String): Int = {
dependencyCount.getOrElse(source, HashMap.empty[String, Int]).getOrElse(target, 0)
}
def currentPackages = dependencyCount.keySet
def currentMaxDependencyCount = dependencyCounter.doubleValue()
} // dependencyCount(source,target,anzahl)
val dependencyExtractor = new DependencyExtractor(dependencyProcessor)
pm.progress(1, ProgressEvents.End, None)
pm.progress(2, ProgressEvents.Start, Some("extracting dependencies"))
for {
classFile <- project.allClassFiles
packageName = classFile.thisType.packageName
} {
dependencyExtractor.process(classFile)
}
pm.progress(2, ProgressEvents.End, None)
// create html file from template
pm.progress(3, ProgressEvents.Start, Some("creating HTML"))
// get packages and sort them
val packages = dependencyProcessor.currentPackages.toSeq.sorted
val maxCount = dependencyProcessor.currentMaxDependencyCount
var data = ("["+packages.foldRight("")(
(p1, l1) => "["+
packages.foldRight("")(
(p2, l2) => s"${dependencyProcessor.currentDependencyCount(p1, p2) / maxCount},$l2"
)+"],"+l1
)+"]").replaceAll(",]", "]")
if (inverse)
data = "d3.transpose("+data+")"
val cS = """ style="border-style:solid;border-width:1px;""""
val addOut =
if (debug)
(" "+packages.foldRight(" ")((p, l) => ""+p+" "+l) + packages.foldRight("
")(
(p1, l1) => ""+p1+" "+
packages.foldRight(" \n")(
(p2, l2) => ""+(dependencyProcessor.currentDependencyCount(p1, p2))+" "+l2
) + l1
))
else
""
// read the template
var htmlDocument = scala.io.Source.fromFile(template.getPath())(scala.io.Codec.UTF8).mkString
if (!htmlDocument.contains("<%DATA%>") || !htmlDocument.contains("<%PACKAGES%>")) {
println(Console.RED+
"[error] The template: "+template+" is not valid."+Console.RESET)
sys.exit(-2)
}
htmlDocument = htmlDocument.replace("<%TITLE%>", "DependencyAnalysis")
htmlDocument = htmlDocument.replace("<%DATA%>", data)
htmlDocument = htmlDocument.replace("<%ADDITIONAL_OUTPUT%>", addOut)
htmlDocument = htmlDocument.replace("<%PACKAGES%>", "["+packages.foldRight("")(
(name, json) =>
s"""{ "name": "$name", "color": "${Random.shuffle(colors.toList).head}"},\n"""+json
)+"]")
writeAndOpen(checkDocument(htmlDocument), "DependencyAnalysis", ".html")
pm.progress(3, ProgressEvents.End, None)
BasicReport(packages)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy