Skip to content

Commit

Permalink
Add comment support and fix inner class renames
Browse files Browse the repository at this point in the history
  • Loading branch information
Oshawk committed May 26, 2024
1 parent 1b0909e commit 4517de2
Show file tree
Hide file tree
Showing 5 changed files with 424 additions and 142 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
TODO: Windows make scriipts

## JADX Collaboration

A plugin to enable users to collaborate on a JADX-GUI project.
Expand All @@ -24,7 +26,7 @@ Start by creating a remote git repository that you and your collaborators will h

Clone the remote repository to create a local version. If the repository has no commits, create one and push it (`git commit --allow-empty -m 'Initial commit' && git push`). *If you already have a local repository for JADX collaboration, this step can be skipped.*

Download a pair of `pull` and `push` scripts from [here](/scripts) (`.ps1` for Windows and `.sh` for Mac or Linux). If you are using Mac or Linux, ensure the scripts are executable (`chmod +x pull.sh push.sh`). *If you already have pre-pull and post-push scripts downloaded, this step can be skipped.*
Download a pair of `pull` and `push` scripts from [here](/scripts) (`.ps1` for Windows and `.sh` for Mac or Linux). If you are on Windows you may need to run `Set-ExecutionPolicy Bypass` in an administrative terminal. If you are using Mac or Linux, ensure the scripts are executable (`chmod +x pull.sh push.sh`). *If you already have pre-pull and post-push scripts downloaded, this step can be skipped.*

Now, in JADX-GUI:

Expand Down
74 changes: 59 additions & 15 deletions src/main/kotlin/uk/oshawk/jadx/collaboration/ConflictResolvers.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package uk.oshawk.jadx.collaboration

import jadx.api.plugins.JadxPluginContext
import org.jetbrains.kotlin.backend.common.push
import java.awt.GridLayout
import javax.swing.*
import javax.swing.text.SimpleAttributeSet
import javax.swing.text.StyleConstants
import kotlin.math.max
import kotlin.math.min

const val WRAP = 20

fun getMainWindow(context: JadxPluginContext): JFrame {
// TODO: This needs to be replaced. It is a travesty.
Expand All @@ -23,11 +27,11 @@ fun getMainWindow(context: JadxPluginContext): JFrame {
return mainWindow
}

fun useLocalConflictResolver(context: JadxPluginContext, remote: RepositoryRename, local: RepositoryRename): Boolean {
fun useLocalConflictResolver(context: JadxPluginContext, remote: RepositoryItem, local: RepositoryItem): Boolean {
return false
}

class ConflictModal(parent: JFrame, remote: RepositoryRename, local: RepositoryRename) :
class ConflictModal(parent: JFrame, remote: RepositoryItem, local: RepositoryItem) :
JDialog(parent, "Conflict!", true) {
var result: Boolean? = null

Expand All @@ -44,13 +48,34 @@ class ConflictModal(parent: JFrame, remote: RepositoryRename, local: RepositoryR
StyleConstants.setFontFamily(normalFont, "Monospaced")
StyleConstants.setBold(normalFont, false)

val remoteNewName = remote.newName ?: "NULL"
val localNewName = local.newName ?: "NULL"
val remoteChanges = mutableListOf<Pair<String, String>>()
val localChanges = mutableListOf<Pair<String, String>>()
when {
remote is RepositoryRename && local is RepositoryRename -> {
remoteChanges.push(Pair("New Name:", "${remote.newName}"))
localChanges.push(Pair("New Name:", "${local.newName}"))
}

remote is RepositoryComment && local is RepositoryComment -> {
remoteChanges.push(Pair("Comment:", "${remote.comment}"))
localChanges.push(Pair("Comment:", "${local.comment}"))

remoteChanges.push(Pair("Style:", "${remote.style}"))
localChanges.push(Pair("Style:", "${local.style}"))
}

else -> {
assert(false)
}
}

var width = 21
var width = 0
width = max(width, remote.identifier.nodeRef.declaringClass.length)
width = max(width, remote.identifier.nodeRef.shortId?.length ?: 4)
width = max(width, max(remoteNewName.length, localNewName.length) * 2 + 3)
for ((key, value) in remoteChanges + localChanges) {
width = max(width, key.length * 2 + 3)
width = max(width, min(value.length, WRAP) * 2 + 3) // Wrap after WRAP characters.
}
width = width or 1 // Make odd for equal width columns.

val text = JTextPane()
Expand All @@ -67,14 +92,33 @@ class ConflictModal(parent: JFrame, remote: RepositoryRename, local: RepositoryR
text.document.insertString(text.document.length, "${remote.identifier.codeRef?.index}\n", normalFont)

text.document.insertString(text.document.length, "-".repeat(width) + "\n", normalFont)
text.document.insertString(text.document.length, "New Name:", boldFont)
text.document.insertString(text.document.length, " ".repeat(width / 2 - 9) + "| ", normalFont)
text.document.insertString(text.document.length, "New Name:\n", boldFont)
text.document.insertString(
text.document.length,
remoteNewName + " ".repeat(width / 2 - remoteNewName.length) + "| $localNewName",
normalFont
)
for ((remoteChange, localChange) in remoteChanges zip localChanges) {
val (remoteKey, remoteValue) = remoteChange
val (localKey, localValue) = localChange

text.document.insertString(text.document.length, remoteKey, boldFont)
text.document.insertString(
text.document.length,
" ".repeat(width / 2 - remoteKey.length) + "| ",
normalFont
)
text.document.insertString(text.document.length, "${localKey}\n", boldFont)

var remainingRemoteValue = remoteValue
var remainingLocalValue = localValue
while (remainingRemoteValue.isNotEmpty() || remainingLocalValue.isNotEmpty()) {
val currentRemoteValue = remainingRemoteValue.take(WRAP)
val currentLocalValue = remainingLocalValue.take(WRAP)
remainingRemoteValue = remainingRemoteValue.drop(WRAP)
remainingLocalValue = remainingLocalValue.drop(WRAP)
text.document.insertString(
text.document.length,
currentRemoteValue + " ".repeat(width / 2 - currentRemoteValue.length) + "| $currentLocalValue\n",
normalFont
)
}

}

add(text)

Expand Down Expand Up @@ -106,7 +150,7 @@ class ConflictModal(parent: JFrame, remote: RepositoryRename, local: RepositoryR
}
}

fun dialogConflictResolver(context: JadxPluginContext, remote: RepositoryRename, local: RepositoryRename): Boolean? {
fun dialogConflictResolver(context: JadxPluginContext, remote: RepositoryItem, local: RepositoryItem): Boolean? {
return ConflictModal(getMainWindow(context), remote, local).showModal()
}

157 changes: 150 additions & 7 deletions src/main/kotlin/uk/oshawk/jadx/collaboration/DataTypes.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package uk.oshawk.jadx.collaboration

import jadx.api.data.CodeRefType
import jadx.api.data.ICodeRename
import jadx.api.data.IJavaCodeRef
import jadx.api.data.IJavaNodeRef
import jadx.api.data.*
import jadx.api.data.impl.JadxCodeComment
import jadx.api.data.impl.JadxCodeRef
import jadx.api.data.impl.JadxCodeRename
import jadx.api.data.impl.JadxNodeRef
Expand Down Expand Up @@ -46,30 +44,175 @@ class Identifier(val nodeRef: NodeRef, val codeRef: CodeRef?) : Comparable<Ident
override fun compareTo(other: Identifier) = compareValuesBy(this, other, { it.nodeRef }, { it.codeRef })
}

interface ProjectItem {
val identifier: Identifier

fun repositoryItem(versionVector: MutableMap<UUID, Long>): RepositoryItem

fun matches(projectItem: ProjectItem): Boolean

fun matches(repositoryItem: RepositoryItem): Boolean
}

@NoArgs
class ProjectRename(val identifier: Identifier, val newName: String) : Comparable<ProjectRename> {
class ProjectRename(override val identifier: Identifier, val newName: String) : ProjectItem {
constructor(rename: ICodeRename) : this(
Identifier(NodeRef(rename.nodeRef), rename.codeRef?.let { CodeRef(it) }),
rename.newName
)

override fun compareTo(other: ProjectRename) = compareValuesBy(this, other, { it.identifier }, { it.newName })
override fun repositoryItem(versionVector: MutableMap<UUID, Long>) =
RepositoryRename(identifier, versionVector, newName)

override fun matches(projectItem: ProjectItem): Boolean {
if (projectItem !is ProjectRename) {
return false
}

return projectItem.newName == newName
}

override fun matches(repositoryItem: RepositoryItem): Boolean {
if (repositoryItem !is RepositoryRename) {
return false
}

return repositoryItem.newName == newName
}

fun convert(): JadxCodeRename {
return JadxCodeRename(identifier.nodeRef.convert(), identifier.codeRef?.convert(), newName)
}
}

@NoArgs
class RepositoryRename(val identifier: Identifier, val newName: String?, val versionVector: MutableMap<UUID, Long>)
class ProjectComment(override val identifier: Identifier, val comment: String, val style: CommentStyle) : ProjectItem {
constructor(comment: ICodeComment) : this(
Identifier(NodeRef(comment.nodeRef), comment.codeRef?.let { CodeRef(it) }),
comment.comment,
comment.style
)

override fun repositoryItem(versionVector: MutableMap<UUID, Long>) =
RepositoryComment(identifier, versionVector, comment, style)

override fun matches(projectItem: ProjectItem): Boolean {
if (projectItem !is ProjectComment) {
return false
}

return projectItem.comment == comment && projectItem.style == style
}

override fun matches(repositoryItem: RepositoryItem): Boolean {
if (repositoryItem !is RepositoryComment) {
return false
}

return repositoryItem.comment == comment && repositoryItem.style == style
}

fun convert(): JadxCodeComment {
return JadxCodeComment(identifier.nodeRef.convert(), identifier.codeRef?.convert(), comment, style)
}
}

interface RepositoryItem {
val identifier: Identifier
val versionVector: MutableMap<UUID, Long>

fun deleted(versionVector: MutableMap<UUID, Long>): RepositoryItem

fun updated(versionVector: MutableMap<UUID, Long>): RepositoryItem

fun matches(projectItem: ProjectItem): Boolean

fun matches(repositoryItem: RepositoryItem): Boolean
}

@NoArgs
class RepositoryRename(
override val identifier: Identifier,
override val versionVector: MutableMap<UUID, Long>,
val newName: String?
) : RepositoryItem {
override fun deleted(versionVector: MutableMap<UUID, Long>) = RepositoryRename(identifier, versionVector, null)

override fun updated(versionVector: MutableMap<UUID, Long>) = RepositoryRename(identifier, versionVector, newName)

override fun matches(projectItem: ProjectItem): Boolean {
if (projectItem !is ProjectRename) {
return false
}

return projectItem.newName == newName
}

override fun matches(repositoryItem: RepositoryItem): Boolean {
if (repositoryItem !is RepositoryRename) {
return false
}

return repositoryItem.newName == newName
}

fun convert(): ProjectRename? {
if (newName == null) {
return null
}

return ProjectRename(identifier, newName)
}
}

@NoArgs
class RepositoryComment(
override val identifier: Identifier,
override val versionVector: MutableMap<UUID, Long>,
val comment: String?,
val style: CommentStyle?
) : RepositoryItem {
override fun deleted(versionVector: MutableMap<UUID, Long>) =
RepositoryComment(identifier, versionVector, null, null)

override fun updated(versionVector: MutableMap<UUID, Long>) =
RepositoryComment(identifier, versionVector, comment, style)

override fun matches(projectItem: ProjectItem): Boolean {
if (projectItem !is ProjectComment) {
return false
}

return projectItem.comment == comment && projectItem.style == style
}

override fun matches(repositoryItem: RepositoryItem): Boolean {
if (repositoryItem !is RepositoryComment) {
return false
}

return repositoryItem.comment == comment && repositoryItem.style == style
}

fun convert(): ProjectComment? {
if (comment == null || style == null) {
assert(comment == null && style == null) // Both or neither should be null.
return null
}

return ProjectComment(identifier, comment, style)
}
}

@NoArgs
class LocalRepository {
var renames = mutableListOf<RepositoryRename>()
var comments = mutableListOf<RepositoryComment>()
val uuid = UUID.randomUUID()
}

@NoArgs
class RemoteRepository {
var renames = mutableListOf<RepositoryRename>()
var comments = mutableListOf<RepositoryComment>()
}
Loading

0 comments on commit 4517de2

Please sign in to comment.