mirror of
https://github.com/jlengrand/korge-intellij-plugin.git
synced 2026-03-10 08:31:19 +00:00
Added show Korim Bitmap action to the debugger
This commit is contained in:
@@ -1,59 +1,79 @@
|
||||
package com.soywiz.korge.intellij.debug
|
||||
|
||||
import com.sun.jdi.*
|
||||
import kotlin.ByteArray
|
||||
import javassist.util.proxy.*
|
||||
import org.gradle.internal.id.UniqueId.*
|
||||
import java.io.*
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
|
||||
fun ArrayReference.convertToLocalBytes(): kotlin.ByteArray {
|
||||
val base64Class = this.virtualMachine().getRemoteClass(Base64::class.java) ?: error("Can't find Base64 class")
|
||||
val encoder = base64Class.invoke("getEncoder", listOf()) as ObjectReference
|
||||
val str = encoder.invoke("encodeToString", listOf(this)) as StringReference
|
||||
fun ArrayReference.convertToLocalBytes(thread: ThreadReference? = null): kotlin.ByteArray {
|
||||
val base64Class = this.virtualMachine().getRemoteClass(Base64::class.java, thread) ?: error("Can't find Base64 class")
|
||||
val encoder = base64Class.invoke("getEncoder", listOf(), thread = thread) as ObjectReference
|
||||
val str = encoder.invoke("encodeToString", listOf(this), thread = thread) as StringReference
|
||||
return Base64.getDecoder().decode(str.value())
|
||||
}
|
||||
|
||||
fun ObjectReference.debugToLocalInstanceViaSerialization(): Any? {
|
||||
return this.debugSerialize().debugDeserialize()
|
||||
fun ObjectReference.debugToLocalInstanceViaSerialization(thread: ThreadReference? = null): Any? {
|
||||
return this.debugSerialize(thread).debugDeserialize()
|
||||
}
|
||||
|
||||
fun ObjectReference.debugSerialize(): ByteArray {
|
||||
inline fun <reified T> Type?.instanceOf(): Boolean = instanceOf(T::class.java)
|
||||
fun Type?.instanceOf(clazz: Class<*>): Boolean = instanceOf(clazz.name)
|
||||
|
||||
fun Type?.instanceOf(name: String): Boolean {
|
||||
val type = this ?: return false
|
||||
if (type is ClassType) {
|
||||
//println("TYPE: ${type.signature()} ${type.name()} :: ${Bitmap::class.java.name}")
|
||||
if (type.name() == name) return true
|
||||
return type.superclass().instanceOf(name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun ObjectReference.debugSerialize(thread: ThreadReference? = null): ByteArray {
|
||||
val vm = this.virtualMachine()
|
||||
|
||||
val baosClass = vm.getRemoteClass(ByteArrayOutputStream::class.java) ?: error("Cant't find ByteArrayOutputStream")
|
||||
val baosClassConstructor = baosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 0 } ?: error("Can't find ByteArrayOutputStream constructor")
|
||||
val baos = baosClass.newInstance(vm.anyThread(), baosClassConstructor, listOf(), ClassType.INVOKE_SINGLE_THREADED)
|
||||
val baosClass = vm.getRemoteClass(ByteArrayOutputStream::class.java, thread) ?: error("Cant't find ByteArrayOutputStream")
|
||||
val baosClassConstructor =
|
||||
baosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 0 } ?: error("Can't find ByteArrayOutputStream constructor")
|
||||
val baos = baosClass.newInstance(thread ?: vm.anyThread(), baosClassConstructor, listOf(), ClassType.INVOKE_SINGLE_THREADED)
|
||||
|
||||
val oosClass = vm.getRemoteClass(ObjectOutputStream::class.java) ?: error("Can't find ObjectOutputStream")
|
||||
val oosClassConstructor = oosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 1 } ?: error("Can't find ObjectOutputStream constructor")
|
||||
val oos = oosClass.newInstance(vm.anyThread(), oosClassConstructor, listOf(baos), ClassType.INVOKE_SINGLE_THREADED)
|
||||
val oosClass = vm.getRemoteClass(ObjectOutputStream::class.java, thread) ?: error("Can't find ObjectOutputStream")
|
||||
val oosClassConstructor =
|
||||
oosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 1 } ?: error("Can't find ObjectOutputStream constructor")
|
||||
val oos = oosClass.newInstance(thread ?: vm.anyThread(), oosClassConstructor, listOf(baos), ClassType.INVOKE_SINGLE_THREADED)
|
||||
|
||||
oos.invoke("writeObject", listOf(this))
|
||||
oos.invoke("writeObject", listOf(this), thread = thread)
|
||||
|
||||
return (baos.invoke("toByteArray", listOf()) as ArrayReference).convertToLocalBytes()
|
||||
return (baos.invoke("toByteArray", listOf(), thread = thread) as ArrayReference).convertToLocalBytes(thread)
|
||||
}
|
||||
|
||||
fun ByteArray.debugDeserialize(): Any? = ObjectInputStream(this.inputStream()).readObject()
|
||||
|
||||
fun VirtualMachine.getRemoteClass(clazz: Class<*>): ClassType? {
|
||||
fun VirtualMachine.getRemoteClass(clazz: Class<*>, thread: ThreadReference? = null): ClassType? {
|
||||
val clazzType = classesByName("java.lang.Class").firstOrNull() ?: error("Can't find java.lang.Class")
|
||||
val clazzClassType = (clazzType as? ClassType?) ?: error("Invalid java.lang.Class")
|
||||
val realClazz = clazzClassType.invoke("forName", listOf(mirrorOf(clazz.name))) as ClassObjectReference
|
||||
val realClazz = clazzClassType.invoke("forName", listOf(mirrorOf(clazz.name)), thread = thread) as ClassObjectReference
|
||||
return realClazz.reflectedType() as ClassType?
|
||||
}
|
||||
|
||||
fun VirtualMachine.anyThread() = allThreads().first()
|
||||
|
||||
fun ClassType.invoke(methodName: String, args: List<Value>, signature: String? = null, thread: ThreadReference? = null): Value {
|
||||
val method = if (signature != null) this.methodsByName(methodName, signature).first() else this.methodsByName(methodName).first()
|
||||
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
|
||||
try {
|
||||
val method = if (signature != null) this.methodsByName(methodName, signature).first() else this.methodsByName(methodName).first()
|
||||
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
|
||||
} catch (e: IncompatibleThreadStateException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun ObjectReference.invoke(methodName: String, args: List<Value>, signature: String? = null, thread: ThreadReference? = null): Value {
|
||||
val method = if (signature != null) this.referenceType().methodsByName(methodName, signature).first() else this.referenceType().methodsByName(methodName).first()
|
||||
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
|
||||
try {
|
||||
val method =
|
||||
if (signature != null) this.referenceType().methodsByName(methodName, signature).first() else this.referenceType().methodsByName(methodName).first()
|
||||
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
|
||||
} catch (e: IncompatibleThreadStateException) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassType.getField(fieldName: String): Value = this.getValue(this.fieldByName(fieldName))
|
||||
@@ -65,6 +85,7 @@ fun Value.int(default: Int): Int = if (this is PrimitiveValue) this.intValue() e
|
||||
fun Value.bool(): Boolean? = if (this is PrimitiveValue) this.booleanValue() else null
|
||||
fun Value.bool(default: Boolean): Boolean = if (this is PrimitiveValue) this.booleanValue() else default
|
||||
|
||||
/*
|
||||
inline fun <reified T> Value.asLocalType() = asLocalType(T::class.java)
|
||||
|
||||
fun <T> Value.asLocalType(clazz: Class<T>): T {
|
||||
@@ -75,3 +96,4 @@ fun <T> Value.asLocalType(clazz: Class<T>): T {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
} as T
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.soywiz.korge.intellij.debug
|
||||
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.soywiz.korim.color.*
|
||||
import com.sun.jdi.*
|
||||
|
||||
fun ObjectReference.readKorimBitmap32(thread: ThreadReference? = null): Bitmap32 {
|
||||
val value = this
|
||||
if (!value.type().instanceOf<Bitmap>()) error("Not a korim bitmap")
|
||||
val width = value.invoke("getWidth", listOf(), thread = thread).int(0)
|
||||
val height = value.invoke("getHeight", listOf(), thread = thread).int(0)
|
||||
val premultiplied = value.invoke("getPremultiplied", listOf(), thread = thread).bool(false)
|
||||
val bmp32Mirror = value.invoke("toBMP32", listOf(), thread = thread) as ObjectReference
|
||||
val dataInstance = (bmp32Mirror.invoke("getData", listOf(), thread = thread) as ObjectReference).debugToLocalInstanceViaSerialization(thread = thread) as IntArray
|
||||
return Bitmap32(width, height, RgbaArray(dataInstance.copyOf()), premultiplied)
|
||||
}
|
||||
|
||||
@@ -13,30 +13,20 @@ import java.awt.*
|
||||
import javax.swing.*
|
||||
import kotlin.jvm.internal.*
|
||||
|
||||
class BitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendererImpl("BitmapDebugNodeRenderer") {
|
||||
override fun isApplicable(type: Type?): Boolean {
|
||||
if (type == null) return false
|
||||
if (type is ClassType) {
|
||||
//println("TYPE: ${type.signature()} ${type.name()} :: ${Bitmap::class.java.name}")
|
||||
if (type.name() == Bitmap::class.java.name) return true
|
||||
return isApplicable(type.superclass())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isEnabled(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getUniqueId(): String {
|
||||
return "BitmapDebugNodeRenderer"
|
||||
class KorimBitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendererImpl(NAME) {
|
||||
companion object {
|
||||
const val NAME = "KorimBitmapDebugNodeRenderer"
|
||||
}
|
||||
override fun isApplicable(type: Type?): Boolean = type.instanceOf<Bitmap>()
|
||||
override fun isEnabled(): Boolean = true
|
||||
override fun getUniqueId(): String = NAME
|
||||
|
||||
override fun calcLabel(descriptor: ValueDescriptor, evaluationContext: EvaluationContext, listener: DescriptorLabelListener): String {
|
||||
val value = descriptor.value
|
||||
val thread = evaluationContext.suspendContext.thread?.threadReference
|
||||
if (value is ObjectReference) {
|
||||
val width = value.invoke("getWidth", listOf()).int()
|
||||
val height = value.invoke("getHeight", listOf()).int()
|
||||
val width = value.invoke("getWidth", listOf(), thread = thread).int()
|
||||
val height = value.invoke("getHeight", listOf(), thread = thread).int()
|
||||
return "${width}x${height}"
|
||||
}
|
||||
return value.toString()
|
||||
@@ -47,14 +37,10 @@ class BitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendere
|
||||
override fun calcValueIcon(descriptor: ValueDescriptor, evaluationContext: EvaluationContext, listener: DescriptorLabelListener): Icon? {
|
||||
try {
|
||||
val value = descriptor.value
|
||||
val thread = evaluationContext.suspendContext.thread?.threadReference
|
||||
val vm = value.virtualMachine()
|
||||
if (value is ObjectReference) {
|
||||
val width = value.invoke("getWidth", listOf()).int(0)
|
||||
val height = value.invoke("getHeight", listOf()).int(0)
|
||||
val premultiplied = value.invoke("getPremultiplied", listOf()).bool(false)
|
||||
val bmp32Mirror = value.invoke("toBMP32", listOf()) as ObjectReference
|
||||
val dataInstance = (bmp32Mirror.invoke("getData", listOf()) as ObjectReference).debugToLocalInstanceViaSerialization() as IntArray
|
||||
val bmp32 = Bitmap32(width, height, RgbaArray(dataInstance), premultiplied)
|
||||
val bmp32 = value.readKorimBitmap32(thread)
|
||||
return ImageIcon(bmp32.toAwt().getScaledInstance(16, 16, Image.SCALE_SMOOTH))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.soywiz.korge.intellij.debug.actions
|
||||
|
||||
import com.intellij.debugger.actions.*
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.project.*
|
||||
import com.intellij.xdebugger.impl.ui.tree.actions.*
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.*
|
||||
import com.soywiz.korge.intellij.debug.*
|
||||
import com.soywiz.korim.awt.*
|
||||
import com.soywiz.korim.bitmap.*
|
||||
import com.sun.jdi.*
|
||||
import java.awt.*
|
||||
import javax.swing.*
|
||||
|
||||
class ShowKorimBitmapAction : XDebuggerTreeActionBase(), DumbAware {
|
||||
override fun perform(node: XValueNodeImpl, nodeName: String, e: AnActionEvent?) {
|
||||
val javaValue = (node.valueContainer as JavaValue)
|
||||
val descriptor = javaValue.descriptor
|
||||
val value = descriptor.value
|
||||
if (value.type().instanceOf<Bitmap>()) {
|
||||
val thread = javaValue.evaluationContext.suspendContext.thread?.threadReference
|
||||
val value = descriptor.value as ObjectReference
|
||||
val bmp32 = value.readKorimBitmap32(thread)
|
||||
val frame = JFrame()
|
||||
frame.add(JLabel(ImageIcon(bmp32.clone().toAwt())))
|
||||
frame.pack()
|
||||
val dim: Dimension = Toolkit.getDefaultToolkit().screenSize
|
||||
frame.setLocation(dim.width / 2 - frame.size.width / 2, dim.height / 2 - frame.size.height / 2)
|
||||
frame.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val values = ViewAsGroup.getSelectedValues(e)
|
||||
e.presentation.isEnabledAndVisible = when {
|
||||
values.size >= 1 -> {
|
||||
val javaValue = values[0]
|
||||
val value = javaValue.descriptor.value
|
||||
value.type().instanceOf<Bitmap>()
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val session = e.dataContext.getData(XDebugSession.DATA_KEY) ?: error("Can't find debug session")
|
||||
val debuggerTree = e.dataContext.getData(XDebuggerTree.XDEBUGGER_TREE_KEY) ?: error("Can't find debugger tree")
|
||||
val selectionPath = debuggerTree.selectionPath
|
||||
println("ACTION!")
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
<applicationService serviceImplementation="com.soywiz.korge.intellij.config.KorgeGlobalSettings" />
|
||||
|
||||
<debugger.nodeRenderer implementation="com.soywiz.korge.intellij.debug.BitmapDebugNodeRenderer" />
|
||||
<debugger.nodeRenderer implementation="com.soywiz.korge.intellij.debug.KorimBitmapDebugNodeRenderer" />
|
||||
|
||||
</extensions>
|
||||
|
||||
@@ -133,6 +133,13 @@
|
||||
|
||||
<separator/>
|
||||
</group>
|
||||
|
||||
<group id="JavaDebuggerActions">
|
||||
<action id="Debugger.ShowKorimBitmap" class="com.soywiz.korge.intellij.debug.actions.ShowKorimBitmapAction" text="Show _Bitmap" icon="/com/soywiz/korge/intellij/icon/korge.png" use-shortcut-of="XDebugger.ShowBitmap">
|
||||
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
|
||||
</action>
|
||||
</group>
|
||||
|
||||
</actions>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user