DiagramTextWrapper.java
package com.taxonomy.export;
import java.util.ArrayList;
import java.util.List;
/** Deterministically wraps diagram labels without silently dropping the final visible line. */
public final class DiagramTextWrapper {
private static final String ELLIPSIS = "...";
private static final String DEFAULT_FALLBACK = "Unnamed architecture element";
private DiagramTextWrapper() {
}
public static List<String> wrap(String value,
int maximumCharacters,
int maximumLines,
String fallback) {
if (maximumCharacters < 1) {
throw new IllegalArgumentException("maximumCharacters must be positive");
}
if (maximumLines < 1) {
throw new IllegalArgumentException("maximumLines must be positive");
}
String normalized = normalize(value, fallback);
List<String> lines = new ArrayList<>(maximumLines);
String remaining = normalized;
while (!remaining.isEmpty() && lines.size() < maximumLines) {
if (lines.size() == maximumLines - 1) {
lines.add(ellipsize(remaining, maximumCharacters));
break;
}
if (remaining.length() <= maximumCharacters) {
lines.add(remaining);
break;
}
int split = remaining.lastIndexOf(' ', maximumCharacters);
if (split <= 0) {
split = maximumCharacters;
}
String line = remaining.substring(0, split).strip();
if (!line.isEmpty()) {
lines.add(line);
}
remaining = remaining.substring(split).strip();
}
return List.copyOf(lines);
}
private static String normalize(String value, String fallback) {
String candidate = value == null || value.isBlank() ? fallback : value;
if (candidate == null || candidate.isBlank()) {
candidate = DEFAULT_FALLBACK;
}
return candidate.strip().replaceAll("\\s+", " ");
}
private static String ellipsize(String value, int maximumCharacters) {
if (value.length() <= maximumCharacters) {
return value;
}
if (maximumCharacters <= ELLIPSIS.length()) {
return ELLIPSIS.substring(0, maximumCharacters);
}
String prefix = value.substring(0, maximumCharacters - ELLIPSIS.length()).stripTrailing();
return prefix + ELLIPSIS;
}
}