/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package issues import java.lang.Comparable import scala.xml.Node import scala.xml.Text import scala.xml.Group import scala.xml.UnprefixedAttribute import play.api.libs.json.Json import play.api.libs.json.JsObject import play.api.libs.json.JsValue import play.api.libs.json.JsNull import play.api.libs.json.JsString import play.api.libs.json.JsNumber import org.opalj.br.PC import org.opalj.br.ClassFile import org.opalj.br.Method import org.opalj.br.Code import org.opalj.br.Field import org.opalj.br.methodToXHTML import org.opalj.br.typeToXHTML import org.opalj.br.classAccessFlagsToString import org.opalj.br.classAccessFlagsToXHTML import org.opalj.br.analyses.SomeProject /** * 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 } final override 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) final override 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)