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 →