Skip to content

chmod Calculator — Java source

Compute Unix file permissions between octal (e.g. 755), symbolic (rwxr-xr-x), and decimal - including setuid, setgid, and sticky bits. Toggle permissions interactively, fully client-side, with a shareable link.

This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// chmod-calculator — POSIX permission mode converter (octal <-> symbolic).
//
// Language: Java (Java 17, standard library only)
// Source:   CosmoDev polyglot showcase port of the Chmod Calculator tool,
//           ported from src/lib/chmod.ts (the canonical TypeScript lib) and
//           held in lock-step with cli/chmod-calculator/chmod-calculator.go.
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Design goals:
//   - Pure + deterministic; failable public API returns Optional (never throws).
//   - Functionally equivalent to the TS/Go references: same inputs -> same outputs.
//   - Self-contained: JDK only (no external dependencies).
//
// Converts between 3-4 digit octal ("755" / "4755"), 9-char symbolic
// ("rwxr-xr-x"), and the raw decimal mode, including the setuid / setgid /
// sticky special bits (the s/S and t/T markers in the exec slot).

package org.cosmolabs.cosmodev.chmod;

import java.util.Locale;
import java.util.Optional;
import java.util.OptionalInt;

// The class with main() is declared first: the Java 17 single-file source
// launcher (`java java.java`) runs the FIRST top-level type in the file.

public final class ChmodCalculator {

    private ChmodCalculator() {}

    /** Parse a 3-char rwx triplet at {@code pos}. The exec slot may carry a
     * special-bit marker: s/S (setuid in owner, setgid in group) or t/T
     * (sticky in other). Returns {@code {digit, special}} on success, or
     * {@code null} if any char is invalid. */
    private static int[] parseTriplet(String tri, Pos pos) {
        if (tri.length() != 3) return null;
        int digit = 0;
        switch (tri.charAt(0)) {
            case 'r' -> digit |= 4;
            case '-' -> { }
            default -> { return null; }
        }
        switch (tri.charAt(1)) {
            case 'w' -> digit |= 2;
            case '-' -> { }
            default -> { return null; }
        }
        int special = 0;
        char c = tri.charAt(2);
        switch (c) {
            case 'x' -> digit |= 1;
            case '-' -> { }
            case 's', 'S' -> {
                if (pos != Pos.OWNER && pos != Pos.GROUP) return null;
                if (c == 's') digit |= 1;
                special = pos == Pos.OWNER ? 4 : 2;
            }
            case 't', 'T' -> {
                if (pos != Pos.OTHER) return null;
                if (c == 't') digit |= 1;
                special = 1;
            }
            default -> { return null; }
        }
        return new int[] { digit, special };
    }

    /** Render a 0-7 digit + optional special bit as a 3-char triplet.
     * {@code marker} is 's' (owner/group) or 't' (other); upper-cased when
     * the exec bit is absent — yielding 'S' / 'T'. */
    private static String formatTriplet(int digit, boolean hasSpecial, char marker) {
        StringBuilder out = new StringBuilder(3);
        out.append((digit & 4) != 0 ? 'r' : '-');
        out.append((digit & 2) != 0 ? 'w' : '-');
        boolean exec = (digit & 1) != 0;
        if (hasSpecial && exec) {
            out.append(marker);
        } else if (hasSpecial) {
            out.append(Character.toUpperCase(marker));
        } else if (exec) {
            out.append('x');
        } else {
            out.append('-');
        }
        return out.toString();
    }

    /** Parse symbolic notation ("rwxr-xr-x") into a raw mode integer, or empty. */
    public static OptionalInt symbolicToMode(String sym) {
        String s = sym.trim();
        if (s.length() != 9) return OptionalInt.empty();
        int[] o = parseTriplet(s.substring(0, 3), Pos.OWNER);
        if (o == null) return OptionalInt.empty();
        int[] g = parseTriplet(s.substring(3, 6), Pos.GROUP);
        if (g == null) return OptionalInt.empty();
        int[] ot = parseTriplet(s.substring(6, 9), Pos.OTHER);
        if (ot == null) return OptionalInt.empty();
        int special = o[1] | g[1] | ot[1];
        return OptionalInt.of(special * 01000 + (o[0] << 6) + (g[0] << 3) + ot[0]);
    }

    /** Parse a 3-4 digit octal string ("755" / "4755") into a raw mode, or empty. */
    public static OptionalInt octalToMode(String octal) {
        String s = octal.trim();
        if (s.length() != 3 && s.length() != 4) return OptionalInt.empty();
        int value = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c < '0' || c > '7') return OptionalInt.empty();
            value = value * 8 + (c - '0');
        }
        return OptionalInt.of(value);
    }

    /** Render a raw mode as 9-char symbolic notation. */
    public static String modeToSymbolic(int mode) {
        int special = (mode >> 9) & 7;
        return formatTriplet((mode >> 6) & 7, (special & 4) != 0, 's')
                + formatTriplet((mode >> 3) & 7, (special & 2) != 0, 's')
                + formatTriplet(mode & 7, (special & 1) != 0, 't');
    }

    /** Render a raw mode as a 4-digit zero-padded octal string. */
    public static String modeToOctal(int mode) {
        return String.format(Locale.ROOT, "%04o", mode & 07777);
    }

    private static ChmodResult buildResult(int mode) {
        int special = (mode >> 9) & 7;
        return new ChmodResult(
                modeToOctal(mode),
                modeToSymbolic(mode),
                mode & 07777,
                (special & 4) != 0,
                (special & 2) != 0,
                (special & 1) != 0);
    }

    /** Build a full result from symbolic notation, or empty if invalid. */
    public static Optional<ChmodResult> fromSymbolic(String sym) {
        OptionalInt mode = symbolicToMode(sym);
        return mode.isPresent() ? Optional.of(buildResult(mode.getAsInt())) : Optional.empty();
    }

    /** Build a full result from an octal string, or empty if invalid. */
    public static Optional<ChmodResult> fromOctal(String octal) {
        OptionalInt mode = octalToMode(octal);
        return mode.isPresent() ? Optional.of(buildResult(mode.getAsInt())) : Optional.empty();
    }

    // --- showcase assertions (the canonical suite lives in src/lib) ------------------
    private static void verify(boolean condition, String what) {
        if (!condition) throw new IllegalStateException("showcase check failed: " + what);
    }

    public static void main(String[] args) {
        ChmodResult r = fromOctal("755").orElseThrow();
        verify("0755".equals(r.octal()), "755 octal");
        verify("rwxr-xr-x".equals(r.symbolic()), "755 symbolic");
        verify(r.decimal() == 0755, "755 decimal");
        verify(!r.setuid() && !r.setgid() && !r.sticky(), "755 no special bits");

        verify("0755".equals(fromSymbolic("rwxr-xr-x").orElseThrow().octal()), "symbolic round-trip");

        ChmodResult su = fromOctal("4755").orElseThrow(); // setuid over rwxr-xr-x -> exec slot becomes 's'
        verify("rwsr-xr-x".equals(su.symbolic()), "4755 renders 's' marker");
        verify(su.decimal() == 04755, "4755 decimal");
        verify(su.setuid() && !su.sticky(), "4755 setuid only");

        ChmodResult st = fromOctal("1644").orElseThrow(); // sticky over rw-r--r--, no exec -> marker 'T'
        verify("rw-r--r-T".equals(st.symbolic()), "1644 renders 'T' marker");
        verify(st.decimal() == 01644, "1644 decimal");
        verify(st.sticky() && !st.setuid(), "1644 sticky only");

        verify(octalToMode("999").isEmpty(), "'9' is not an octal digit");
        verify(symbolicToMode("rwx").isEmpty(), "wrong length rejected");
        verify("---------".equals(fromOctal("0000").orElseThrow().symbolic()), "zero round-trip");

        System.out.println("All chmod showcase tests passed.");
    }
}

/** Which permission class a triplet describes — governs which special-bit
 * marker (s/S for owner+group, t/T for other) is legal in its exec slot. */
enum Pos { OWNER, GROUP, OTHER }

/** Full chmod breakdown — the Java mirror of the TS {@code ChmodResult} /
 * Go {@code Result} structs. */
record ChmodResult(
        String octal,    // 4-digit zero-padded octal, e.g. "0755".
        String symbolic, // 9-char rwxrwxrwx with special markers, e.g. "rwsr-xr-x".
        int decimal,     // Raw integer mode (0-4095).
        boolean setuid,
        boolean setgid,
        boolean sticky) {}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →