[Git 만들어보기 - Geet] geet object들 class화하여 리팩토링하기

송준섭 Junseop Song·2024년 3월 18일
post-thumbnail

2024-01-21 구현

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()))
    }
}

0개의 댓글