Box-Shadow Generator — Java source
Design layered CSS box-shadows with offset, blur, spread, color, and inset. Live preview and copy-ready CSS.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// box-shadow-generator — Java polyglot showcase port.
//
// Pure CSS box-shadow builder. Formats one or more shadow layers and joins
// them into a single CSS box-shadow value. Deterministic, dependency-free,
// and never throws on bad input: invalid colors quietly fall back to a
// neutral translucent black, so a single bad color never breaks the whole
// stack.
//
// This is the Java sibling of src/lib/boxShadow.ts (the canonical
// TypeScript that powers the live tool). The public surface mirrors the TS:
// a ShadowLayer record plus parseColor, formatLayer, and buildBoxShadow.
// (The class is package-private so the showcase filename java.java compiles
// as-is — a public class would demand BoxShadowGenerator.java.)
//
// Language: Java 17 (standard library only).
// Ported from src/lib/boxShadow.ts.
// Source: CosmoDev polyglot showcase port.
// License: display source — part of CosmoDev's polyglot tool pages.
package org.cosmolabs.cosmodev.boxshadow;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
/** Pure CSS box-shadow builder — the Java sibling of the canonical TS lib. */
final class BoxShadowGenerator {
/** A single layer in a CSS box-shadow stack. */
record ShadowLayer(
boolean inset, // draw the shadow inside the box
double offsetX, // horizontal offset in px
double offsetY, // vertical offset in px
double blur, // blur radius in px
double spread, // spread distance in px
String color // any CSS color (named, hex, rgb(), hsl(), ...)
) {}
/**
* Outcome of validating a color string. {@code error} is {@code null}
* when {@code ok} is true, mirroring the TypeScript
* {@code { ok: boolean; error: string | null }} shape.
*/
record ColorResult(boolean ok, String error) {
static ColorResult okValue() {
return new ColorResult(true, null);
}
static ColorResult errValue(String message) {
return new ColorResult(false, message);
}
}
// CSS named colors accepted without further inspection. parseColor()
// lower-cases its input first, so membership is effectively
// case-insensitive.
private static final Set<String> NAMED_COLORS = Set.of(
"transparent", "black", "white", "red", "green", "blue", "yellow",
"orange", "purple", "pink", "gray", "grey", "brown", "cyan", "magenta");
// Color-shape patterns. The input is already lower-cased + stripped
// before these run, so the hex classes use only [0-9a-f]. The contents
// of rgb()/hsl() are not validated beyond a well-formed wrapper,
// matching the live tool.
private static final Pattern HEX_SHORT = // #rgb or #rrggbb
Pattern.compile("^#[0-9a-f]{3}([0-9a-f]{3})?$");
private static final Pattern HEX_ALPHA = // #rrggbbaa
Pattern.compile("^#[0-9a-f]{8}$");
private static final Pattern RGB_FN = // rgb() / rgba()
Pattern.compile("^rgba?\\([^)]+\\)$");
private static final Pattern HSL_FN = // hsl() / hsla()
Pattern.compile("^hsla?\\([^)]+\\)$");
private BoxShadowGenerator() {}
/**
* Validate a CSS color string. Accepts the curated named-color set plus
* hex (#rgb, #rrggbb, #rrggbbaa), rgb()/rgba(), and hsl()/hsla() forms.
* The functional notations are checked for well-formed wrappers only,
* not their numeric contents.
*/
static ColorResult parseColor(String color) {
// Lower-case + strip once so every shape check below sees a canonical form.
String c = (color == null ? "" : color).strip().toLowerCase(Locale.ROOT);
if (c.isEmpty()) return ColorResult.errValue("empty color");
if (NAMED_COLORS.contains(c)) return ColorResult.okValue();
if (HEX_SHORT.matcher(c).matches()
|| HEX_ALPHA.matcher(c).matches()
|| RGB_FN.matcher(c).matches()
|| HSL_FN.matcher(c).matches()) {
return ColorResult.okValue();
}
return ColorResult.errValue("invalid color: " + color);
}
// Keep a color when it parses; otherwise substitute a neutral
// translucent black. This is what makes buildBoxShadow total over
// arbitrary input.
private static String normalizeColor(String color) {
return parseColor(color).ok() ? color.strip() : "rgba(0,0,0,0.5)";
}
// Render a number with JavaScript parity: whole numbers drop the
// trailing ".0" (so 5.0 -> "5", 5.5 -> "5.5"). Double.toString keeps the
// short form for fractions, mirroring JS template-literal coercion.
private static String formatNumber(double value) {
if (value == Math.rint(value) && Math.abs(value) < 1e15) {
return String.valueOf((long) value);
}
return String.valueOf(value);
}
/**
* Render one shadow layer as its CSS fragment, e.g.
* "inset 4px 8px 16px 0px #1a2b3c" or "0px 2px 4px 0px rgba(0,0,0,0.5)".
*/
static String formatLayer(ShadowLayer layer) {
String prefix = layer.inset() ? "inset " : "";
return prefix + formatNumber(layer.offsetX()) + "px "
+ formatNumber(layer.offsetY()) + "px "
+ formatNumber(layer.blur()) + "px "
+ formatNumber(layer.spread()) + "px "
+ normalizeColor(layer.color());
}
/**
* Compose a full CSS box-shadow declaration from an ordered list of
* layers (the first layer renders on top). An empty list yields the CSS
* keyword "none", matching the property's default value.
*/
static String buildBoxShadow(List<ShadowLayer> layers) {
if (layers.isEmpty()) return "none";
StringBuilder out = new StringBuilder();
for (int i = 0; i < layers.size(); i++) {
if (i > 0) out.append(", ");
out.append(formatLayer(layers.get(i)));
}
return out.toString();
}
}
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 →