
Blob 개체를 만들때에는 문제가 없었지만, Tree 개체를 만드려고 하니 Blob 개체만 신경써서 만들었던 코드들이 문제가 되기 시작했다.
그래서 GeetObject라는 클래스를 만들고, 그 클래스를 상속하는 GeetBlob, GeetTree 등의 클래스를 만들어 통일성 및 재사용성을 높이고자 하였다.
// geet/objects/GeetObject
package geet.objects
import geet.util.messageDigest
open class GeetObject(
val type: String,
val name: String,
var content: String,
) {
open val hashString: String
get() {
val header = "${type} ${content.length}\u0000"
val store = header + content
val hash = messageDigest.digest(store.toByteArray())
return hash.joinToString("") {
String.format("%02x", it)
}
}
}
hashString 값을 얻는 로직은 세 개체 모두 같고 헤더에 들어가는 개체 타입과 내용만 다르기 때문에 재사용할 수 있다고 생각하여 가장 상위 클래스인 GeetObject 클래스에 구현해주었다.
그렇게 구현한 GeetBlob, GeetTree 클래스는 다음과 같다.
// geet/objects/GeetBlob
package geet.objects
class GeetBlob(
type: String = "blob",
name: String,
content: String,
): GeetObject(type, name, content) { }
// geet/objects/GeetTree
package geet.objects
class GeetTree(
type: String = "tree",
name: String,
val objects: List<GeetObject>,
): GeetObject(type, name, content = "") {
init {
content = objects.joinToString("") {
"${it.type} ${it.hashString} ${it.name}\u0000"
}
}
}
Tree 개체는 개체에 담긴 개체들에 따라 내용이 바뀌어서 init 블록에서 content 프로퍼티를 다시 초기화해주었다.
그리고 지금까지 구현했던 함수들에 클래스들을 적용하였다.
그 전에 미리 GeetUtil.kt 하나에 모여있던 함수들을 목적에 맞게 HashObjectUtil.kt, CatFileUtil.kt 등으로 분리해주었다.
또한 상수로 저장되어서 재사용될 가능성이 있는 것들은 const.kt에 따로 빼주었다.
그 후 클래스들을 적용한 함수들은 다음과 같다.
// geet/util/const.kt
package geet.util
import java.security.MessageDigest
val messageDigest: MessageDigest = java.security.MessageDigest.getInstance("SHA-1")
// geet/util/GeetUtil.kt
package geet.util
import ..
fun isGeetObjectType(type: String): Boolean {
val typeLowerCase = type.lowercase()
return typeLowerCase == "blob" ||
typeLowerCase == "tree" ||
typeLowerCase == "commit" ||
typeLowerCase == "tag"
}
fun compressToZlib(contents: String): String {
val inputData = contents.toByteArray()
val outputStream = ByteArrayOutputStream()
val deflater = Deflater()
val deflaterOutputStream = DeflaterOutputStream(outputStream, deflater)
deflaterOutputStream.write(inputData)
deflaterOutputStream.close()
return Base64.getEncoder().encodeToString(outputStream.toByteArray())
}
fun decompressFromZlib(zlibContents: String): String {
val decodedByteArray = Base64.getDecoder().decode(zlibContents)
val inputStream = ByteArrayInputStream(decodedByteArray)
val inflater = Inflater()
val inflaterInputStream = InflaterInputStream(inputStream, inflater)
val outputStream = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length: Int
while (inflaterInputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
}
return outputStream.toString()
}
// geet/util/HashObjectUtil.kt
package geet.util
import geet.commands.plumbing.GeetHashObjectOptions
import geet.exception.NotFoundException
import geet.objects.GeetBlob
import java.io.File
fun createHashObject(options: GeetHashObjectOptions) {
val file = File(options.path)
if (!file.exists()) {
throw NotFoundException("파일을 찾을 수 없습니다. : ${options.path}")
}
when (options.type) { // 생성 개체 타입에 따라 분리, 일단은 blob 개체에 대해서만 생성
"blob" -> createBlobObject(options, file)
}
}
fun createBlobObject(options: GeetHashObjectOptions, file: File) {
// GeetBlob 인스턴스 생성
val blobObject = GeetBlob(name = file.name, content = file.readText())
println(blobObject.hashString)
if (options.write) { // -w 옵션이 있는 경우
val dirName = blobObject.hashString.substring(0, 2)
val fileName = blobObject.hashString.substring(2)
val compressedContents = compressToZlib(blobObject.content)
File(".geet/objects/$dirName").mkdirs()
File(".geet/objects/$dirName/$fileName").writeText(compressedContents)
println("개체가 저장되었습니다.")
}
}
// geet/util/CatFileUtil.kt
package geet.util
import geet.commands.plumbing.GeetCatFileOptions
import geet.exception.NotFoundException
import java.io.File
fun catGeetObject(catFileOptions: GeetCatFileOptions) {
val dirPath = ".geet/objects/${catFileOptions.objectPath.substring(0, 2)}"
val fileName = catFileOptions.objectPath.substring(2)
val file = File("$dirPath/$fileName")
if (!file.exists()) {
throw NotFoundException("개체를 찾을 수 없습니다. : ${catFileOptions.objectPath}")
}
if (catFileOptions.option == "-p") {
print(decompressFromZlib(file.readText()))
}
}