Skip to content

Instantly share code, notes, and snippets.

@jensim
Last active May 26, 2019 06:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jensim/bce141c1eaee4af22fc2ccedc7c87183 to your computer and use it in GitHub Desktop.
Save jensim/bce141c1eaee4af22fc2ccedc7c87183 to your computer and use it in GitHub Desktop.
Extract any and all class references from a jar, and its nested jars recursively with the beauty of Kotlin. Standalone version from https://github.com/jensim/refleKt/blob/master/src/main/kotlin/se/jensim/reflekt/internal/JarFileClassLocator.kt
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
object JarFileClassLocator {
private val classRegexp = Regex("^.*/([A-Z]+[A-Za-z0-9]${"$"})*[A-Z]+[A-Za-z0-9]*\\.class$")
private fun String.fileToClassRef() = dropLast(6).replace("/", ".").replace("$", ".")
val classFiles: Set<String> by lazy {
JarFileClassLocator::class.java.protectionDomain.codeSource
?.let { ZipFile(File(it.location.toURI())) }
?.let { getClasses(it) }
.orEmpty()
}
fun getClasses(jarFile: ZipFile): Set<String> =
getClassFiles(jarFile, emptySet(), jarFile.entries()?.toList().orEmpty())
.map { it.fileToClassRef() }.toSet()
private tailrec fun getClassFiles(zipFile: ZipFile, foundClassFiles: Set<String>, jarFileEntries: Collection<ZipEntry>): Set<String> {
if (jarFileEntries.isEmpty()) {
return foundClassFiles
}
val newClasses = jarFileEntries.map { it.name }.filter { it.matches(classRegexp) }
val jars = jarFileEntries.filter { !it.isDirectory && it.name.endsWith(".jar") }
.map { ZipInputStream(zipFile.getInputStream(it)) }
.flatMap { it.getEntries() }
return getClassFiles(zipFile, foundClassFiles + newClasses, jars)
}
private fun ZipInputStream.getEntries():List<ZipEntry> = try{
use {
generateSequence { it.nextEntry }.toList()
}
}catch (e:Exception){
emptyList()
}
}
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
class JarFileClassLocatorTest {
@Rule
@JvmField
val tmpDir: TemporaryFolder = TemporaryFolder()
@Test
fun getClassFileFromNestedJar() {
val nestedJarPath = "${tmpDir.root}/nested.jar"
val findMeClass = "FindMe.class"
writeToZip(findMeClass, nestedJarPath, "i am a class".toByteArray())
val nestedJarData = File(tmpDir.root, "nested.jar").readBytes()
writeToZip("nested.jar", "${tmpDir.root}/outer.jar", nestedJarData)
val classes = JarFileClassLocator.getClasses(ZipFile(File("${tmpDir.root}/outer.jar")))
assertEquals(classes, setOf("com.example.FindMe"))
}
private fun writeToZip(entryName: String, zipFile: String, data: ByteArray) {
ZipOutputStream(FileOutputStream(File(zipFile))).use {
it.putNextEntry(ZipEntry("com/example/$entryName"))
it.write(data)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment