/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package issues import java.lang.Comparable import play.api.libs.json.JsNull import play.api.libs.json.JsNumber import play.api.libs.json.JsObject import play.api.libs.json.Json import play.api.libs.json.JsString import play.api.libs.json.JsValue import scala.xml.Group import scala.xml.Node import scala.xml.Text import scala.xml.UnprefixedAttribute import org.opalj.br.ClassFile import org.opalj.br.Code import org.opalj.br.Field import org.opalj.br.Method import org.opalj.br.PC import org.opalj.br.analyses.SomeProject import org.opalj.br.classAccessFlagsToString import org.opalj.br.classAccessFlagsToXHTML import org.opalj.br.methodToXHTML import org.opalj.br.typeToXHTML /** * The location of an issue. * * @author Michael Eichberg */ sealed abstract class IssueLocation extends IssueRepresentations with Comparable[IssueLocation] { /** * The description of the issue with respect to the given location. */ def description: Option[String] def compareTo(other: IssueLocation): Int } abstract class ProjectLocation( val description: Option[String], val theProject: SomeProject, val details: Seq[IssueDetails] = List.empty ) extends IssueLocation { def compareTo(other: IssueLocation): Int = { other match { case _: InstructionLocation => 1 case _: MethodLocation => 1 case _: FieldLocation => 1 case _: ClassLocation => 1 case _: PackageLocation => 1 case that: ProjectLocation => that.theProject.hashCode() compare this.theProject.hashCode() match { case 0 => (this.description, that.description) match { case (None, None) => 0 case (Some(_), None) => -1 case (None, Some(_)) => 1 case (Some(x), Some(y)) => x compare y } case x => x } } } override def toAnsiColoredString: String = { description.map(_.replace('\n', ';')).getOrElse("") } override def toEclipseConsoleString: String = { description.map(_.replace('\n', ';')).getOrElse("") } } class PackageLocation( description: Option[String], theProject: SomeProject, val thePackage: String, details: Seq[IssueDetails] = List.empty ) extends ProjectLocation(description, theProject, details) { def locationAsInlineXHTML(basicInfoOnly: Boolean): List[Node] = { List({thePackage.replace('/', '.')}) } def descriptionAsXHTML: List[Node] = { if (description.isDefined) List(
, Text(description.get)) else List.empty[Node] } def detailsAsXHTML(basicInfoOnly: Boolean): List[Node] = { details.map(d =>
{d.toXHTML(basicInfoOnly)}
).toList } override final def toXHTML(basicInfoOnly: Boolean): Node = { Group(List(
location
,
{locationAsInlineXHTML(basicInfoOnly)} {descriptionAsXHTML} {detailsAsXHTML(basicInfoOnly)}
)) } def locationAsIDL: JsObject = Json.obj("package" -> thePackage.replace('/', '.')) def detailsAsIDL: JsValue = Json.toJson(details) override final def toIDL: JsValue = { Json.obj( "description" -> description, "location" -> locationAsIDL, "details" -> detailsAsIDL ) } } class ClassLocation( description: Option[String], theProject: SomeProject, val classFile: ClassFile, details: Seq[IssueDetails] = List.empty ) extends PackageLocation(description, theProject, classFile.thisType.packageName, details) with ClassComprehension { override def locationAsInlineXHTML(basicInfoOnly: Boolean): List[Node] = { val locationAsInlineXHTML = super.locationAsInlineXHTML(basicInfoOnly) ++ List( Text("."), {typeToXHTML(classFile.thisType, true)} ) if (basicInfoOnly || classFile.accessFlags == 0) locationAsInlineXHTML else classAccessFlagsToXHTML(classFile.accessFlags) :: Text(" ") :: locationAsInlineXHTML } override def toAnsiColoredString: String = { theProject.source(classFile.thisType).map(_.toString).getOrElse("") + ":" } override def locationAsIDL: JsObject = { super.locationAsIDL + ( ( "class", Json.obj( "fqn" -> classFile.fqn, "type" -> typeToIDL(classFile.thisType), "accessFlags" -> { classFile.accessFlags match { case 0 => JsNull case _ => classAccessFlagsToString(classFile.accessFlags) } } ) ) ) } } class MethodLocation( description: Option[String], theProject: SomeProject, val method: Method, details: Seq[IssueDetails] = List.empty ) extends ClassLocation(description, theProject, method.classFile, details) with MethodComprehension { val firstLineOfMethod: Option[String] = { method.body.flatMap(_.firstLineNumber.map(ln => (if (ln > 2) (ln - 2) else 0).toString)) } override def locationAsInlineXHTML(basicInfoOnly: Boolean): List[Node] = { var methodNode = { if (basicInfoOnly) methodToXHTML(method.name, method.descriptor, true) else methodToXHTML(method.accessFlags, method.name, method.descriptor, true) } if (firstLineOfMethod.isDefined) { val firstLine = firstLineOfMethod.get.toString methodNode %= new UnprefixedAttribute("data-line", firstLine, scala.xml.Null) } super.locationAsInlineXHTML(basicInfoOnly) ++ List(Text("{ "), methodNode, Text(" }")) } override def locationAsIDL: JsObject = { super.locationAsIDL + ( "method" -> ( methodToIDL(method.accessFlags, method.name, method.descriptor) + ("signature" -> JsString(methodJVMSignature)) + ("firstLine" -> Json.toJson(firstLineOfMethod)) ) ) } } class InstructionLocation( description: Option[String], theProject: SomeProject, method: Method, val pc: PC, details: Seq[IssueDetails] = List.empty ) extends MethodLocation(description, theProject, method, details) with PCLineComprehension { assert(method.body.isDefined) def code: Code = method.body.get override def locationAsInlineXHTML(basicInfoOnly: Boolean): List[Node] = { val superLocationAsInlineXHTML = super.locationAsInlineXHTML(basicInfoOnly) superLocationAsInlineXHTML.init ++ List(Text("["), pcNode, lineNode, Text("]"), superLocationAsInlineXHTML.last) } override def locationAsIDL: JsObject = { var instructionLocation = Json.obj("pc" -> pc) if (line.isDefined) instructionLocation += (("line", JsNumber(line.get))) super.locationAsIDL + (("instruction", instructionLocation)) } override def toEclipseConsoleString: String = { val source = classFile.thisType.toJava.split('$').head val line = this.line.map(line => s":$line").getOrElse("") "(" + source + ".java" + line + ") " } override def toAnsiColoredString: String = { theProject.source(classFile.thisType).map(_.toString).getOrElse("") + ":" + line.map(line => s"$line: ").getOrElse(" ") } } class FieldLocation( description: Option[String], theProject: SomeProject, classFile: ClassFile, val field: Field, details: Seq[IssueDetails] = List.empty ) extends ClassLocation(description, theProject, classFile, details)