Setiap file di sistem operasi punya metadata: ukuran, waktu modifikasi, pemilik, grup, izin akses, dan lain-lain. Bab ini membahas cara membaca dan menulis atribut file di Kotlin menggunakan API NIO.2 (BasicFileAttributes, PosixFileAttributes, Files.getAttribute) serta perbandingannya dengan API klasik java.io.File. Pemahaman ini penting untuk utility yang inspeksi metadata, scheduler yang mengecek waktu modifikasi, atau security tool yang memverifikasi izin.

Atribut Dasar: BasicFileAttributes #

BasicFileAttributes adalah antarmuka yang berisi atribut umum di semua sistem file. Diakses via Files.readAttributes(path, BasicFileAttributes::class.java). Atributnya mencakup:

  • size() — ukuran file dalam byte.
  • isRegularFile(), isDirectory(), isSymbolicLink(), isOther() — tipe file.
  • lastModifiedTime(), creationTime(), lastAccessTime() — timestamp sebagai FileTime.
  • fileKey() — identifier unik file (filesystem-dependent).
kotlin
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.PosixFileAttributes
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset

const val DEMO_DIR = "/tmp/kotlin-tutorial-attributes"

fun main() {
    File(DEMO_DIR).deleteRecursively()
    File(DEMO_DIR).mkdirs()

    // Siapkan file demo dengan ukuran tetap
    val file = File("$DEMO_DIR/data.txt")
    file.writeText("isi file dengan beberapa byte data")
    // Set waktu deterministik untuk lastModified, creationTime, lastAccessTime
    val waktuTetap = Instant.parse("2026-01-15T08:30:00Z")
    val waktuTetapFile = FileTime.from(waktuTetap)
    file.setLastModified(waktuTetap.toEpochMilli())
    val pathAwal: Path = file.toPath()
    Files.setAttribute(pathAwal, "creationTime", waktuTetapFile)
    Files.setAttribute(pathAwal, "lastAccessTime", waktuTetapFile)

    println("=== 1. BasicFileAttributes: atribut dasar ===")
    val path: Path = file.toPath()
    val basic = Files.readAttributes(path, BasicFileAttributes::class.java)
    println("size = ${basic.size()} bytes")
    println("isRegularFile = ${basic.isRegularFile}")
    println("isDirectory = ${basic.isDirectory}")
    println("isSymbolicLink = ${basic.isSymbolicLink}")
    println("isOther = ${basic.isOther}")
    println("fileKey.class = ${basic.fileKey()?.javaClass?.simpleName ?: "null"}")

    println()
    println("=== 2. FileTime: waktu modifikasi dan akses ===")
    println("lastModifiedTime = ${basic.lastModifiedTime().toString().substringBefore('.')}")
    println("lastAccessTime = ${basic.lastAccessTime().toString().substringBefore('.')}")
    println("(creationTime tidak ditampilkan: tidak semua FS mendukung setAttribute)")

    println()
    println("=== 3. Files.getAttribute: akses per-atribut ===")
    val size = Files.getAttribute(path, "size")
    val isReg = Files.getAttribute(path, "isRegularFile")
    val isDir = Files.getAttribute(path, "isDirectory")
    val lastMod = Files.getAttribute(path, "lastModifiedTime")
    println("size = $size")
    println("isRegularFile = $isReg")
    println("isDirectory = $isDir")
    println("lastModifiedTime = ${(lastMod as FileTime).toString().substringBefore('.')}")

    println()
    println("=== 4. Files.setAttribute: ubah atribut ===")
    val waktuBaru = FileTime.from(Instant.parse("2026-02-20T10:15:30Z"))
    Files.setAttribute(path, "lastModifiedTime", waktuBaru)
    val lastModSetelah = Files.getAttribute(path, "lastModifiedTime") as FileTime
    println("lastModifiedTime setelah setAttribute = ${lastModSetelah.toString().substringBefore('.')}")

    println()
    println("=== 5. Konversi FileTime ke LocalDateTime ===")
    val ldt = LocalDateTime.ofInstant(basic.lastModifiedTime().toInstant(), ZoneOffset.UTC)
    println("LocalDateTime (UTC) = $ldt")

    println()
    println("=== 6. PosixFileAttributes: atribut POSIX ===")
    val posix = Files.readAttributes(path, PosixFileAttributes::class.java)
    println("owner = ${posix.owner().name}")
    println("group = ${posix.group().name}")
    println("permissions (sorted) = ${posix.permissions().sortedBy { it.name }}")

    println()
    println("=== 7. Files.getPosixFilePermissions: izin POSIX ===")
    val perms = Files.getPosixFilePermissions(path).sortedBy { it.name }
    println("perms (sorted set) = $perms")
    println("perms (string rwx) = ${posixPermissionsToString(perms.toSet())}")

    println()
    println("=== 8. File (java.io) attributes: alternative API ===")
    println("File.length = ${file.length()}")
    println("File.lastModified (epoch millis) = ${file.lastModified()}")
    println("File.canRead = ${file.canRead()}")
    println("File.canWrite = ${file.canWrite()}")
    println("File.canExecute = ${file.canExecute()}")
    println("File.isHidden = ${file.isHidden}")
    println("File.totalSpace (parent) = ${File(DEMO_DIR).totalSpace > 0}")
    println("File.freeSpace (parent) = ${File(DEMO_DIR).freeSpace > 0}")

    println()
    println("=== 9. Direktori atribut ===")
    val dir = File(DEMO_DIR)
    val dirPath = dir.toPath()
    val dirBasic = Files.readAttributes(dirPath, BasicFileAttributes::class.java)
    println("dir isDirectory (NIO) = ${dirBasic.isDirectory}")
    println("dir isRegularFile (NIO) = ${dirBasic.isRegularFile}")
    println("dir size (NIO) = ${dirBasic.size()} (ukuran direktori entry)")

    println()
    println("=== 10. Bersih-bersih ===")
    File(DEMO_DIR).deleteRecursively()
    println("demo dir dihapus = ${!File(DEMO_DIR).exists()}")
}

fun posixPermissionsToString(perms: Set<PosixFilePermission>): String {
    val sb = StringBuilder()
    sb.append(permChar(perms, PosixFilePermission.OWNER_READ, 'r'))
    sb.append(permChar(perms, PosixFilePermission.OWNER_WRITE, 'w'))
    sb.append(permChar(perms, PosixFilePermission.OWNER_EXECUTE, 'x'))
    sb.append(permChar(perms, PosixFilePermission.GROUP_READ, 'r'))
    sb.append(permChar(perms, PosixFilePermission.GROUP_WRITE, 'w'))
    sb.append(permChar(perms, PosixFilePermission.GROUP_EXECUTE, 'x'))
    sb.append(permChar(perms, PosixFilePermission.OTHERS_READ, 'r'))
    sb.append(permChar(perms, PosixFilePermission.OTHERS_WRITE, 'w'))
    sb.append(permChar(perms, PosixFilePermission.OTHERS_EXECUTE, 'x'))
    return sb.toString()
}

fun permChar(perms: Set<PosixFilePermission>, p: PosixFilePermission, c: Char): Char =
    if (p in perms) c else '-'

Output yang Dihasilkan:

text
=== 1. BasicFileAttributes: atribut dasar ===
size = 34 bytes
isRegularFile = true
isDirectory = false
isSymbolicLink = false
isOther = false
fileKey.class = UnixFileKey

=== 2. FileTime: waktu modifikasi dan akses ===
lastModifiedTime = 2026-01-15T08:30:00Z
lastAccessTime = 2026-01-15T08:30:00Z
(creationTime tidak ditampilkan: tidak semua FS mendukung setAttribute)

=== 3. Files.getAttribute: akses per-atribut ===
size = 34
isRegularFile = true
isDirectory = false
lastModifiedTime = 2026-01-15T08:30:00Z

=== 4. Files.setAttribute: ubah atribut ===
lastModifiedTime setelah setAttribute = 2026-02-20T10:15:30Z

=== 5. Konversi FileTime ke LocalDateTime ===
LocalDateTime (UTC) = 2026-01-15T08:30

=== 6. PosixFileAttributes: atribut POSIX ===
owner = z
group = z
permissions (sorted) = [GROUP_READ, GROUP_WRITE, OTHERS_READ, OWNER_READ, OWNER_WRITE]

=== 7. Files.getPosixFilePermissions: izin POSIX ===
perms (sorted set) = [GROUP_READ, GROUP_WRITE, OTHERS_READ, OWNER_READ, OWNER_WRITE]
perms (string rwx) = rw-rw-r--

=== 8. File (java.io) attributes: alternative API ===
File.length = 34
File.lastModified (epoch millis) = 1771582530000
File.canRead = true
File.canWrite = true
File.canExecute = false
File.isHidden = false
File.totalSpace (parent) = true
File.freeSpace (parent) = true

=== 9. Direktori atribut ===
dir isDirectory (NIO) = true
dir isRegularFile (NIO) = false
dir size (NIO) = 4096 (ukuran direktori entry)

=== 10. Bersih-bersih ===
demo dir dihapus = true

Mari kita bedah bagian-bagian penting:

  1. Bagian 1 (BasicFileAttributes): Files.readAttributes(path, BasicFileAttributes::class.java) membaca semua atribut dalam satu syscall — lebih efisien daripada memanggil Files.getAttribute beberapa kali. fileKey() mengembalikan objek unik per file (di Linux berupa UnixFileKey berisi device ID + inode). Penting: fileKey bisa berbeda-beda antar sistem file, jadi tidak semua path memiliki fileKey yang berguna.
  2. Bagian 2 (FileTime): FileTime adalah representasi timestamp di NIO.2. Bisa dikonversi ke Instant via .toInstant(). Penting: creationTime di banyak filesystem Linux tidak bisa di-set via setAttribute — panggilan berhasil tetapi nilai tidak berubah. Inilah alasan kita tidak menampilkannya di output.
  3. Bagian 3 (Files.getAttribute): alternatif satu-atribut. Berguna saat hanya butuh satu atribut — tidak perlu membaca semua. Format string seperti "size", "isRegularFile", "lastModifiedTime", "posix:owner" (untuk POSIX-specific).
  4. Bagian 4 (Files.setAttribute): menulis atribut. Tidak semua atribut bisa diubah (mis. size tidak bisa di-set langsung; gunakan truncate file). Yang umum diubah: lastModifiedTime, lastAccessTime.
  5. Bagian 5 (LocalDateTime): konversi FileTime ke LocalDateTime untuk display. ZoneOffset.UTC untuk zona waktu UTC; ganti dengan ZoneId.systemDefault() untuk zona lokal.
  6. Bagian 6 (PosixFileAttributes): subtype BasicFileAttributes untuk sistem POSIX (Linux, macOS). Menambahkan owner() (UserPrincipal), group() (GroupPrincipal), dan permissions() (Set<PosixFilePermission>). Pemilik file di sandbox adalah z — pengguna yang menjalankan proses.
  7. Bagian 7 (Files.getPosixFilePermissions): shortcut untuk membaca hanya izin. Format rw-rw-r-- adalah standar ls -l: 3 grup (owner, group, others) × 3 karakter (read, write, execute). File demo memiliki rw- (owner), rw- (group), r-- (others).
  8. Bagian 8 (java.io.File API): alternatif klasik. File.length(), File.lastModified() (epoch millis), canRead/canWrite/canExecute. Lebih sederhana tetapi tidak mendukung owner, group, atau izin POSIX granular.
  9. Bagian 9 (atribut direktori): direktori punya size() yang biasanya 4096 byte (satu blok filesystem di Linux ext4). isDirectory = true, isRegularFile = false. Size direktori tidak mencerminkan ukuran isi — untuk itu gunakan walkTopDown().map { it.length() }.sum().

Atribut Lain di BasicFileAttributes #

Tabel ringkas atribut yang tersedia:

AtributTipeBisa di-set?
sizeLong (bytes)Tidak (gunakan truncate)
isRegularFileBooleanTidak
isDirectoryBooleanTidak
isSymbolicLinkBooleanTidak
lastModifiedTimeFileTimeYa
lastAccessTimeFileTimeYa (filesystem-dependent)
creationTimeFileTimeYa (filesystem-dependent)
fileKeyObject?Tidak

POSIX Permissions #

Izin POSIX terdiri dari 9 bagian dalam 3 grup:

OwnerGroupOthersKarakter
readreadreadr (atau -)
writewritewritew (atau -)
executeexecuteexecutex (atau -)

Format string rw-rw-r-- dibaca: owner rw-, group rw-, others r--. Angka oktal (mis. 664) merepresentasikan izin yang sama.

Referensi Lanjutan #

Selanjutnya #

Bab berikutnya, File Permissions, akan membahas lebih dalam modifikasi izin POSIX: PosixFilePermissions.fromString("rw-r--r--"), Files.setPosixFilePermissions, dan dasar ACL di Windows.