Compare commits

...
Author SHA1 Message Date
Ludy87 7265d1b9fa Update PolicyExecutor.java 2026-07-09 22:52:21 +02:00
Ludy87 a1808b6c0f Update PortalInfraAuditService.java 2026-07-09 22:51:55 +02:00
LudyandGitHub e78b8fcd37 Merge branch 'main' into switch_expression 2026-07-09 22:41:35 +02:00
LudyandGitHub e263b9f507 Merge branch 'main' into switch_expression 2026-07-09 11:06:15 +02:00
Ludy87 16ac5e4822 Update MergeControllerGapTest.java 2026-07-03 16:15:00 +02:00
Ludy87 b4a1104902 more 2026-07-03 16:08:50 +02:00
LudyandGitHub 2581c11c9d Merge branch 'main' into switch_expression 2026-07-03 15:51:17 +02:00
LudyandGitHub b64f626926 Merge branch 'main' into switch_expression 2026-07-03 15:07:11 +02:00
Ludy87 76b129decb Remove unified credit interceptor system
Delete CreditErrorAdvice, UnifiedCreditInterceptor, and TeamCreditService. These classes implemented credit validation and consumption logic for the SaaS profile. This removal indicates a refactoring or replacement of the credit management system.
2026-07-03 15:06:49 +02:00
LudyandGitHub 6810155799 Merge branch 'main' into switch_expression 2026-06-14 17:33:00 +02:00
LudyandGitHub fe520d24a9 Delete CLAUDE.md~Stashed changes 2026-06-14 17:31:12 +02:00
Ludy87 351abf54a1 Warn on null form field instead of setting value
Split the combined 'case null, default' switch arm into distinct cases. Now a null PDF form field triggers a warning log, while the default branch continues to call setValue(value != null ? value : ""). This improves diagnostics and avoids attempting to set values on null fields.
2026-06-13 22:13:16 +02:00
Ludy87 cad2ac206a Update UIDataController.java 2026-06-12 13:27:58 +02:00
Ludy87 4598ae1e8d Use constructor injection in UploadLimitServiceTest
Replace reflective field injection with passing the mocked ApplicationProperties into UploadLimitService via its constructor. This simplifies the test setup, avoids reflection, and makes dependency injection explicit and more readable.
2026-06-12 13:14:41 +02:00
Ludy87 a6a6de960e Refactor switch statements to use switch expressions for improved readability and conciseness
Signed-off-by: Ludy87 <Ludy87@users.noreply.github.com>
2026-06-12 12:58:14 +02:00
LudyandGitHub 9a4af79c6d Merge branch 'main' into switch_expression 2026-06-12 12:43:49 +02:00
Ludy87 b43cd350f7 Merge branch 'Stirling-Tools:main' into main
Signed-off-by: Ludy87 <Ludy87@users.noreply.github.com>
2026-06-12 12:43:33 +02:00
LudyandGitHub 9a985abf46 Merge branch 'main' into switch_expression 2026-06-11 20:38:42 +02:00
Ludy87 837371cff0 del 2026-06-11 20:38:16 +02:00
Ludy87 da7b586313 Use deque accessors and add job ownership guard
Replace index-based List access with deque-style getFirst()/getLast() where appropriate, modernize switches to expression form, and update various small APIs (e.g. getFirst() on list-like structures). Remove an obsolete test file. Substantially enhance JobController to integrate cluster backplane and job store: add ownership cache, guardNonOwner/backplaneUnavailable helpers, sticky-410 handling, and backplane-aware flows for job status/result/file endpoints (returning 410 or retryable 503 as needed). Update tests and PDF-to-markdown flow to use PdfMarkdownConverter/TempFile/PdfDocument mocks. Misc fixes across controllers and services (SAML attribute access, audit file access, database backup handling, session registry, interceptors, and team credit service). These changes improve safety for clustered deployments and modernize collection usage and control flow.
2026-06-11 20:36:12 +02:00
Ludy87 33bfb18d7c Merge remote-tracking branch 'origin/main' into switch_expression 2026-06-11 20:28:12 +02:00
Ludy87 584fd0cb09 getFirst() 2026-05-09 20:44:13 +02:00
Ludy87 ecdb8160a9 getLast 2026-05-09 20:32:21 +02:00
Ludy87 0bd7d72d07 get(0) to getFirst 2026-05-09 20:20:12 +02:00
Ludy87 10eac6622c UIDataControllerTest 2026-05-09 20:14:25 +02:00
Ludy87 641114c5c2 expression 2026-05-09 20:11:24 +02:00
57 changed files with 333 additions and 410 deletions
@@ -73,7 +73,7 @@ public class RuntimePathConfig {
defaultWatchedFolders,
watchedFoldersDirs,
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.get(0);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.getFirst();
this.pipelineFinishedFoldersPath =
resolvePath(
defaultFinishedFolders,
@@ -60,54 +60,40 @@ public class Provider {
}
private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (name) {
case "google" -> {
return validateGoogleUsernameAttribute(usernameAttribute);
}
case "github" -> {
return validateGitHubUsernameAttribute(usernameAttribute);
}
case "keycloak" -> {
return validateKeycloakUsernameAttribute(usernameAttribute);
}
default -> {
return usernameAttribute;
}
}
return switch (name) {
case "google" -> validateGoogleUsernameAttribute(usernameAttribute);
case "github" -> validateGitHubUsernameAttribute(usernameAttribute);
case "keycloak" -> validateKeycloakUsernameAttribute(usernameAttribute);
default -> usernameAttribute;
};
}
private UsernameAttribute validateKeycloakUsernameAttribute(
UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
@Override
@@ -361,8 +361,8 @@ public class PdfMarkdownConverter {
if (xs.isEmpty()) {
return List.of(lines);
}
float minX = xs.get(0);
float maxX = xs.get(xs.size() - 1);
float minX = xs.getFirst();
float maxX = xs.getLast();
float splitAt = (minX + maxX) / 2f;
float biggestGap = 0;
for (int i = 1; i < xs.size(); i++) {
@@ -492,7 +492,7 @@ public class PdfMarkdownConverter {
List<List<Line>> anchorGroups = new ArrayList<>();
List<Line> current = new ArrayList<>();
current.add(cands.get(0));
current.add(cands.getFirst());
for (int i = 1; i < cands.size(); i++) {
float gap = cands.get(i - 1).y - cands.get(i).y;
if (gap > splitThreshold) {
@@ -513,8 +513,8 @@ public class PdfMarkdownConverter {
if (anchors.size() < 2) {
continue;
}
float top = anchors.get(0).y;
float bottom = anchors.get(anchors.size() - 1).y;
float top = anchors.getFirst().y;
float bottom = anchors.getLast().y;
// Each anchor seeds a row; absorb wrapped continuation lines (non-anchors within the
// run's vertical span, with a little slack below the last row) into the anchor above.
@@ -674,8 +674,8 @@ public class PdfMarkdownConverter {
float minGutter = Math.max(10f, charWidth * 2.5f);
List<float[]> merged = new ArrayList<>();
for (float[] band : columns) {
if (!merged.isEmpty() && band[0] - merged.get(merged.size() - 1)[1] < minGutter) {
merged.get(merged.size() - 1)[1] = band[1];
if (!merged.isEmpty() && band[0] - merged.getLast()[1] < minGutter) {
merged.getLast()[1] = band[1];
} else {
merged.add(new float[] {band[0], band[1]});
}
@@ -734,7 +734,7 @@ public class PdfMarkdownConverter {
}
}
StringBuilder sb = new StringBuilder();
sb.append(buildGfmRow(rows.get(0), widths, cols)).append('\n');
sb.append(buildGfmRow(rows.getFirst(), widths, cols)).append('\n');
sb.append('|');
for (int c = 0; c < cols; c++) {
sb.append('-').append("-".repeat(widths[c])).append('-').append('|');
@@ -910,8 +910,8 @@ public class PdfMarkdownConverter {
}
// Only merge a sentence continuation between two text paragraphs, never into/out of a
// table.
if (!(output.get(output.size() - 1) instanceof String last)
|| !(pageItems.get(0) instanceof String first)) {
if (!(output.getLast() instanceof String last)
|| !(pageItems.getFirst() instanceof String first)) {
return;
}
if (!first.isEmpty()
@@ -932,13 +932,13 @@ public class PdfMarkdownConverter {
for (Object e : elements) {
if (e instanceof TableBlock tb
&& !out.isEmpty()
&& out.get(out.size() - 1) instanceof TableBlock prev
&& out.getLast() instanceof TableBlock prev
&& columnsMatch(flatten(prev.rows()), flatten(tb.rows()))) {
List<List<Line>> merged = new ArrayList<>(prev.rows());
List<List<Line>> tail = tb.rows();
if (!tail.isEmpty()
&& !prev.rows().isEmpty()
&& rowText(tail.get(0)).equals(rowText(prev.rows().get(0)))) {
&& rowText(tail.getFirst()).equals(rowText(prev.rows().getFirst()))) {
tail = tail.subList(1, tail.size());
}
merged.addAll(tail);
@@ -971,7 +971,7 @@ public class PdfMarkdownConverter {
continue;
}
if (e instanceof TableBlock tb && !tb.rows().isEmpty()) {
return rowText(tb.rows().get(0));
return rowText(tb.rows().getFirst());
}
return null;
}
@@ -119,7 +119,7 @@ public class InternalApiClient {
boolean hasFilePart =
body.values().stream()
.flatMap(java.util.List::stream)
.anyMatch(v -> v instanceof Resource);
.anyMatch(Resource.class::isInstance);
if (isAiTool && !hasFilePart) {
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
}
@@ -140,20 +140,25 @@ public class ChecksumUtils {
for (String algorithm : algorithms) {
String key = algorithm; // keep original key for output
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32":
checksums.put(key, new CRC32());
break;
case "ADLER32":
checksums.put(key, new Adler32());
break;
default:
try {
// For MessageDigest, pass the original name (case-insensitive per JCA)
digests.put(key, MessageDigest.getInstance(algorithm));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unsupported algorithm: " + algorithm, e);
}
Object digestOrChecksum =
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32" -> new CRC32();
case "ADLER32" -> new Adler32();
default -> {
try {
// For MessageDigest, pass the original name (case-insensitive
// per JCA)
yield MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"Unsupported algorithm: " + algorithm, e);
}
}
};
if (digestOrChecksum instanceof Checksum checksum) {
checksums.put(key, checksum);
} else {
digests.put(key, (MessageDigest) digestOrChecksum);
}
}
@@ -751,7 +751,7 @@ public class ExceptionUtils {
String targetDescription;
if (errorInfo.affectedPages() != null && !errorInfo.affectedPages().isEmpty()) {
if (errorInfo.affectedPages().size() == 1) {
targetDescription = "page " + errorInfo.affectedPages().get(0);
targetDescription = "page " + errorInfo.affectedPages().getFirst();
} else {
targetDescription =
"pages "
@@ -848,7 +848,7 @@ public class ExceptionUtils {
}
// Use the first page number, or null if none found
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.get(0);
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.getFirst();
return new GhostscriptErrorInfo(
ErrorCode.GHOSTSCRIPT_PAGE_DRAWING,
@@ -114,7 +114,7 @@ public enum FormFieldTypeSupport {
return;
}
PDAnnotationWidget widget = checkBox.getWidgets().get(0);
PDAnnotationWidget widget = checkBox.getWidgets().getFirst();
PDAppearanceCharacteristicsDictionary appearanceChars =
widget.getAppearanceCharacteristics();
@@ -88,28 +88,16 @@ public class FormUtils {
* text)
*/
public String detectFieldType(PDField field) {
if (field instanceof PDSignatureField) {
return FIELD_TYPE_SIGNATURE;
}
if (field instanceof PDPushButton) {
return FIELD_TYPE_BUTTON;
}
if (field instanceof PDTextField) {
return FIELD_TYPE_TEXT;
}
if (field instanceof PDCheckBox) {
return FIELD_TYPE_CHECKBOX;
}
if (field instanceof PDComboBox) {
return FIELD_TYPE_COMBOBOX;
}
if (field instanceof PDListBox) {
return FIELD_TYPE_LISTBOX;
}
if (field instanceof PDRadioButton) {
return FIELD_TYPE_RADIO;
}
return FIELD_TYPE_TEXT;
return switch (field) {
case PDSignatureField ignored -> FIELD_TYPE_SIGNATURE;
case PDPushButton ignored -> FIELD_TYPE_BUTTON;
case PDTextField ignored -> FIELD_TYPE_TEXT;
case PDCheckBox ignored -> FIELD_TYPE_CHECKBOX;
case PDComboBox ignored -> FIELD_TYPE_COMBOBOX;
case PDListBox ignored -> FIELD_TYPE_LISTBOX;
case PDRadioButton ignored -> FIELD_TYPE_RADIO;
case null, default -> FIELD_TYPE_TEXT;
};
}
public List<FormFieldInfo> extractFormFields(PDDocument document) {
@@ -583,22 +571,17 @@ public class FormUtils {
continue;
}
String type = info.type();
Object value;
switch (type) {
case FIELD_TYPE_CHECKBOX:
value = isChecked(info.value()) ? Boolean.TRUE : Boolean.FALSE;
break;
case FIELD_TYPE_LISTBOX:
if (info.multiSelect()) {
value = new ArrayList<>();
} else {
value = safeDefault(info.value());
}
break;
case FIELD_TYPE_BUTTON, FIELD_TYPE_SIGNATURE:
continue; // skip non-fillable
default:
value = safeDefault(info.value());
Object value =
switch (type) {
case FIELD_TYPE_CHECKBOX ->
isChecked(info.value()) ? Boolean.TRUE : Boolean.FALSE;
case FIELD_TYPE_LISTBOX ->
info.multiSelect() ? new ArrayList<>() : safeDefault(info.value());
case FIELD_TYPE_BUTTON, FIELD_TYPE_SIGNATURE -> null;
default -> safeDefault(info.value());
};
if (value == null) {
continue; // skip non-fillable
}
record.put(info.name(), value);
}
@@ -949,44 +932,44 @@ public class FormUtils {
if (selection == null || selection.trim().isEmpty()) return null;
List<String> filtered =
filterChoiceSelections(List.of(selection), allowedOptions, fieldName);
return filtered.isEmpty() ? null : filtered.get(0);
return filtered.isEmpty() ? null : filtered.getFirst();
}
private void applyValueToField(PDField field, String value, boolean strict) throws IOException {
try {
if (field instanceof PDTextField textField) {
setTextValue(textField, value);
} else if (field instanceof PDCheckBox checkBox) {
LinkedHashSet<String> candidateStates = collectCheckBoxStates(checkBox);
boolean shouldCheck = shouldCheckBoxBeChecked(value, candidateStates);
try {
if (shouldCheck) {
checkBox.check();
} else {
checkBox.unCheck();
}
} catch (IOException checkProblem) {
log.warn(
"Failed to set checkbox state for '{}': {}",
field.getFullyQualifiedName(),
checkProblem.getMessage(),
checkProblem);
if (strict) {
throw checkProblem;
switch (field) {
case PDTextField textField -> setTextValue(textField, value);
case PDCheckBox checkBox -> {
LinkedHashSet<String> candidateStates = collectCheckBoxStates(checkBox);
boolean shouldCheck = shouldCheckBoxBeChecked(value, candidateStates);
try {
if (shouldCheck) {
checkBox.check();
} else {
checkBox.unCheck();
}
} catch (IOException checkProblem) {
log.warn(
"Failed to set checkbox state for '{}': {}",
field.getFullyQualifiedName(),
checkProblem.getMessage(),
checkProblem);
if (strict) {
throw checkProblem;
}
}
}
} else if (field instanceof PDRadioButton radioButton) {
if (value != null && !value.isBlank()) {
radioButton.setValue(value);
case PDRadioButton radioButton -> {
if (value != null && !value.isBlank()) {
radioButton.setValue(value);
}
}
} else if (field instanceof PDChoice choiceField) {
applyChoiceValue(choiceField, value);
} else if (field instanceof PDPushButton) {
log.debug("Ignore Push button");
} else if (field instanceof PDSignatureField) {
log.debug("Skipping signature field '{}'", field.getFullyQualifiedName());
} else {
field.setValue(value != null ? value : "");
case PDChoice choiceField -> applyChoiceValue(choiceField, value);
case PDPushButton ignored -> log.debug("Ignore Push button");
case PDSignatureField ignored ->
log.debug("Skipping signature field '{}'", field.getFullyQualifiedName());
case null -> log.warn("Attempted to set value on null field");
default -> field.setValue(value != null ? value : "");
}
} catch (Exception e) {
log.warn(
@@ -1306,37 +1289,42 @@ public class FormUtils {
List<String> resolveOptions(PDTerminalField field) {
try {
if (field instanceof PDChoice choice) {
LinkedHashSet<String> allowed = new LinkedHashSet<>();
List<String> exportValues = choice.getOptionsExportValues();
List<String> displayValues = choice.getOptionsDisplayValues();
return switch (field) {
case PDChoice choice -> {
LinkedHashSet<String> allowed = new LinkedHashSet<>();
List<String> exportValues = choice.getOptionsExportValues();
List<String> displayValues = choice.getOptionsDisplayValues();
if (exportValues != null) {
exportValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
if (exportValues != null) {
exportValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
}
if (displayValues != null) {
displayValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
}
yield new ArrayList<>(allowed);
}
if (displayValues != null) {
displayValues.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.forEach(allowed::add);
case PDRadioButton radio -> {
List<String> exports = radio.getExportValues();
yield exports != null && !exports.isEmpty()
? new ArrayList<>(exports)
: Collections.emptyList();
}
return new ArrayList<>(allowed);
} else if (field instanceof PDRadioButton radio) {
List<String> exports = radio.getExportValues();
if (exports != null && !exports.isEmpty()) {
return new ArrayList<>(exports);
case PDCheckBox checkBox -> {
List<String> exports = checkBox.getExportValues();
yield exports != null && !exports.isEmpty()
? new ArrayList<>(exports)
: Collections.emptyList();
}
} else if (field instanceof PDCheckBox checkBox) {
List<String> exports = checkBox.getExportValues();
if (exports != null && !exports.isEmpty()) {
return new ArrayList<>(exports);
}
}
case null, default -> Collections.emptyList();
};
} catch (Exception e) {
log.debug(
"Failed to resolve options for field '{}': {}",
@@ -1465,7 +1453,7 @@ public class FormUtils {
// Only check options for choice-type fields (combobox, listbox, radio)
if (CHOICE_FIELD_TYPES.contains(type) && options != null && !options.isEmpty()) {
String optionCandidate = cleanLabel(options.get(0));
String optionCandidate = cleanLabel(options.getFirst());
if (optionCandidate != null && !looksGeneric(optionCandidate)) {
return optionCandidate;
}
@@ -1557,7 +1545,7 @@ public class FormUtils {
continue;
}
PDAnnotationWidget widget = widgets.get(0);
PDAnnotationWidget widget = widgets.getFirst();
PDRectangle originalRectangle = cloneRectangle(widget.getRectangle());
PDPage page = resolveWidgetPage(document, widget, null);
if (page == null || originalRectangle == null) {
@@ -2446,19 +2434,19 @@ public class FormUtils {
private static int firstWidgetPageIndex(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getPageIndex()
? f.getWidgets().getFirst().getPageIndex()
: -1;
}
private static float firstWidgetY(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getY()
? f.getWidgets().getFirst().getY()
: 0;
}
private static float firstWidgetX(FormFieldWithCoordinates f) {
return (f.getWidgets() != null && !f.getWidgets().isEmpty())
? f.getWidgets().get(0).getX()
? f.getWidgets().getFirst().getX()
: 0;
}
@@ -26,29 +26,27 @@ import lombok.extern.slf4j.Slf4j;
public class ImageProcessingUtils {
static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
BufferedImage convertedImage;
switch (colorType) {
case "greyscale":
convertedImage =
return switch (colorType) {
case "greyscale" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
case "blackwhite":
convertedImage =
yield convertedImage;
}
case "blackwhite" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_BINARY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
default: // full color
convertedImage = sourceImage;
break;
}
return convertedImage;
yield convertedImage;
}
default -> sourceImage;
};
}
public static byte[] getImageData(BufferedImage image) {
@@ -330,7 +330,7 @@ public class PDFToFile {
if (outputFiles.size() == 1) {
// Return single output file
File outputFile = outputFiles.get(0);
File outputFile = outputFiles.getFirst();
if ("txt:Text".equals(outputFormat)) {
outputFormat = "txt";
}
@@ -307,7 +307,7 @@ public class ProcessExecutor {
boolean isQpdf =
commandToRun != null
&& !commandToRun.isEmpty()
&& commandToRun.get(0).contains("qpdf");
&& commandToRun.getFirst().contains("qpdf");
if (!outputLines.isEmpty()) {
String outputMessage = String.join("\n", outputLines);
@@ -370,7 +370,7 @@ public class ProcessExecutor {
}
// Check if this is a UNO conversion by looking for unoconvert executable
String executable = command.get(0);
String executable = command.getFirst();
if (executable != null) {
// Extract basename from path for matching
String basename = executable;
@@ -504,7 +504,7 @@ public class ProcessExecutor {
}
// Validate executable (first argument)
String executable = command.get(0);
String executable = command.getFirst();
if (executable == null || executable.isBlank()) {
throw new IllegalArgumentException("Command executable must not be empty");
}
@@ -114,7 +114,7 @@ public class YamlHelper {
for (NodeTuple tuple : mappingNode.getValue()) {
ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;
if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {
if (keyNode == null || !keyNode.getValue().equals(keys.getFirst())) {
updatedTuples.add(tuple);
continue;
}
@@ -721,7 +721,7 @@ class PDFToFileTest {
.thenAnswer(
invocation -> {
List<String> args = invocation.getArgument(0);
String outputPath = args.get(args.size() - 1);
String outputPath = args.getLast();
Files.write(Path.of(outputPath), "Fake DOCX content".getBytes());
return mockExecutorResult;
});
@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -22,7 +21,11 @@ import stirling.software.SPDF.controller.web.UploadLimitService;
@Slf4j
public class MultipartConfiguration {
@Autowired private UploadLimitService uploadLimitService;
private final UploadLimitService uploadLimitService;
public MultipartConfiguration(UploadLimitService uploadLimitService) {
this.uploadLimitService = uploadLimitService;
}
/**
* Creates MultipartConfigElement that respects fileUploadLimit from settings.yml or environment
@@ -294,24 +294,25 @@ public class BookletImpositionController {
// Apply rotation if needed (rotate about origin), then translate to keep in cell
switch (rot) {
case 90:
case 90 -> {
cs.transform(Matrix.getRotateInstance(Math.PI / 2, 0, 0));
// After 90° CCW, the content spans x in [-r.getHeight(), 0] and y in [0,
// r.getWidth()]
cs.transform(Matrix.getTranslateInstance(0, -r.getWidth()));
break;
case 180:
}
case 180 -> {
cs.transform(Matrix.getRotateInstance(Math.PI, 0, 0));
cs.transform(Matrix.getTranslateInstance(-r.getWidth(), -r.getHeight()));
break;
case 270:
}
case 270 -> {
cs.transform(Matrix.getRotateInstance(3 * Math.PI / 2, 0, 0));
// After 270° CCW, the content spans x in [0, r.getHeight()] and y in
// [-r.getWidth(), 0]
cs.transform(Matrix.getTranslateInstance(-r.getHeight(), 0));
break;
default:
}
default -> {
// 0°: no-op
}
}
// Reuse LayerUtility passed from caller
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -58,8 +57,6 @@ import stirling.software.jpdfium.doc.PdfBookmarkEditor.BookmarkTree;
@Slf4j
@RequiredArgsConstructor
public class MergeController {
private static final Pattern QUOTE_WRAP_PATTERN = Pattern.compile("^\"|\"$");
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final TempFileManager tempFileManager;
@@ -161,30 +158,6 @@ public class MergeController {
};
}
private String[] parseClientFileIds(String clientFileIds) {
if (clientFileIds == null || clientFileIds.trim().isEmpty()) {
return new String[0];
}
try {
String trimmed = clientFileIds.trim();
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
String inside = trimmed.substring(1, trimmed.length() - 1).trim();
if (inside.isEmpty()) {
return new String[0];
}
String[] parts = inside.split(",");
String[] result = new String[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = QUOTE_WRAP_PATTERN.matcher(parts[i].trim()).replaceAll("");
}
return result;
}
} catch (Exception e) {
log.warn("Failed to parse client file IDs: {}", clientFileIds, e);
}
return new String[0];
}
private void addTableOfContents(PDDocument mergedDocument, MultipartFile[] files) {
PDDocumentOutline outline = new PDDocumentOutline();
mergedDocument.getDocumentCatalog().setDocumentOutline(outline);
@@ -125,17 +125,14 @@ public class UIDataController {
pipelineConfigs.add(content);
}
for (String config : pipelineConfigs) {
for (int i = 0; i < pipelineConfigs.size(); i++) {
String config = pipelineConfigs.get(i);
Map<String, Object> jsonContent =
objectMapper.readValue(
config, new TypeReference<Map<String, Object>>() {});
String name = (String) jsonContent.get("name");
if (name == null || name.length() < 1) {
String filename =
jsonFiles
.get(pipelineConfigs.indexOf(config))
.getFileName()
.toString();
String filename = jsonFiles.get(i).getFileName().toString();
name = filename.substring(0, filename.lastIndexOf('.'));
}
Map<String, String> configWithName = new HashMap<>();
@@ -301,20 +298,14 @@ public class UIDataController {
}
private static String getFormatFromExtension(String extension) {
switch (extension) {
case "ttf":
return "truetype";
case "woff":
return "woff";
case "woff2":
return "woff2";
case "eot":
return "embedded-opentype";
case "svg":
return "svg";
default:
return "";
}
return switch (extension) {
case "ttf" -> "truetype";
case "woff" -> "woff";
case "woff2" -> "woff2";
case "eot" -> "embedded-opentype";
case "svg" -> "svg";
default -> "";
};
}
}
}
@@ -187,7 +187,7 @@ public class ConvertImgPDFController {
}
if (webpFiles.size() == 1) {
Path webpFilePath = webpFiles.get(0);
Path webpFilePath = webpFiles.getFirst();
byte[] webpBytes = Files.readAllBytes(webpFilePath);
Files.deleteIfExists(tempFile);
tempFile = null;
@@ -147,7 +147,7 @@ public class ConvertSvgToPDF {
String outputFilename =
filenames.isEmpty()
? "combined_svgs.pdf"
: GeneralUtils.generateFilename(filenames.get(0), "_combined.pdf");
: GeneralUtils.generateFilename(filenames.getFirst(), "_combined.pdf");
log.info("Successfully combined {} SVGs into single PDF", sanitizedSvgs.size());
@@ -203,7 +203,7 @@ public class ConvertSvgToPDF {
try {
if (convertedPdfs.size() == 1) {
ConvertedPdf pdf = convertedPdfs.get(0);
ConvertedPdf pdf = convertedPdfs.getFirst();
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
try {
Files.write(tempOut.getPath(), pdf.content);
@@ -218,7 +218,7 @@ public class ConvertSvgToPDF {
filenames.isEmpty()
? "converted_svgs.zip"
: GeneralUtils.generateFilename(
filenames.get(0), "_converted_svgs.zip");
filenames.getFirst(), "_converted_svgs.zip");
TempFile zipFile = createZipFromPdfs(convertedPdfs);
return WebResponseUtils.zipFileToWebResponse(zipFile, zipFilename);
} catch (IOException e) {
@@ -82,7 +82,7 @@ public class ExtractCSVController {
if (csvEntries.isEmpty()) {
return ResponseEntity.noContent().build();
} else if (csvEntries.size() == 1) {
return createCsvResponse(csvEntries.get(0), baseName);
return createCsvResponse(csvEntries.getFirst(), baseName);
} else {
return createZipResponse(csvEntries, baseName);
}
@@ -113,7 +113,9 @@ public class AutoRenameController {
mergedLineInfos.sort(
Comparator.comparing((LineInfo li) -> li.fontSize).reversed());
String title =
mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text;
mergedLineInfos.isEmpty()
? null
: mergedLineInfos.getFirst().text;
return title != null
? title
@@ -333,7 +333,7 @@ public class AutoSplitPdfController {
}
if (!splitDocuments.isEmpty() && !isValidQrCode) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
splitDocuments.getLast().addPage(document.getPage(page));
} else if (page == 0) {
PDDocument firstDocument = new PDDocument();
firstDocument.addPage(document.getPage(page));
@@ -267,7 +267,7 @@ public class CompressController {
if (references.isEmpty()) continue;
// Get the first instance of this image
PDImageXObject originalImage = getOriginalImage(doc, references.get(0));
PDImageXObject originalImage = getOriginalImage(doc, references.getFirst());
// Track original size
int originalSize = (int) originalImage.getCOSObject().getLength();
@@ -1167,7 +1167,7 @@ public class CompressController {
List<ImageReference> references = entry.getValue();
if (references.isEmpty()) continue;
PDImageXObject originalImage = getOriginalImage(doc, references.get(0));
PDImageXObject originalImage = getOriginalImage(doc, references.getFirst());
int originalSize = (int) originalImage.getCOSObject().getLength();
stats.totalOriginalBytes += originalSize;
@@ -211,7 +211,7 @@ public class ExtractImageScansController {
} else {
// Return the processed image as a response
byte[] imageBytes = processedImageBytes.get(0);
byte[] imageBytes = processedImageBytes.getFirst();
finalOutput = tempFileManager.createManagedTempFile(".png");
try (OutputStream out = Files.newOutputStream(finalOutput.getPath())) {
out.write(imageBytes);
@@ -404,7 +404,7 @@ public class CertSignController {
PDAcroForm acroForm = new PDAcroForm(doc);
doc.getDocumentCatalog().setAcroForm(acroForm);
PDSignatureField signatureField = new PDSignatureField(acroForm);
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
PDAnnotationWidget widget = signatureField.getWidgets().getFirst();
List<PDField> acroFormFields = acroForm.getFields();
acroForm.setSignaturesExist(true);
acroForm.setAppendOnly(true);
@@ -634,7 +634,7 @@ class RedactExecuteService {
PageColumnLayout layout =
PageColumnLayout.fromLineBoxes(extractor.getLineBoxes(), pageWidth);
if (layout.columnCount() > 1) {
float[] g = layout.gutters().get(0);
float[] g = layout.gutters().getFirst();
log.info(
"[redact/execute] page {} layout: 2 cols, gutter x=[{}, {}]",
pageIdx + 1,
@@ -60,7 +60,7 @@ public class RemoveCertSignController {
// Remove signature fields safely
List<PDField> fieldsToRemove =
acroForm.getFields().stream()
.filter(field -> field instanceof PDSignatureField)
.filter(PDSignatureField.class::isInstance)
.toList();
if (!fieldsToRemove.isEmpty()) {
@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.web;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@@ -13,7 +12,11 @@ import stirling.software.common.model.ApplicationProperties;
@Slf4j
public class UploadLimitService {
@Autowired private ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
public UploadLimitService(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
public long getUploadLimit() {
String raw =
@@ -2688,7 +2688,7 @@ public class PdfJsonConversionService {
// Find which page the field is on
PDAnnotationWidget widget =
field.getWidgets().isEmpty() ? null : field.getWidgets().get(0);
field.getWidgets().isEmpty() ? null : field.getWidgets().getFirst();
if (widget != null) {
PDPage fieldPage = widget.getPage();
if (fieldPage != null) {
@@ -3164,7 +3164,7 @@ public class PdfJsonConversionService {
&& imageObjectNames != null
&& !imageObjectNames.isEmpty()
&& !targetTokens.isEmpty()) {
Object previous = targetTokens.get(targetTokens.size() - 1);
Object previous = targetTokens.getLast();
if (previous instanceof COSName cosName
&& imageObjectNames.contains(cosName.getName())) {
targetTokens.remove(targetTokens.size() - 1);
@@ -5246,7 +5246,7 @@ public class PdfJsonConversionService {
throws IOException {
if (OperatorName.DRAW_OBJECT.equals(operator.getName())
&& !operands.isEmpty()
&& operands.get(0) instanceof COSName name) {
&& operands.getFirst() instanceof COSName name) {
currentXObjectName = name;
}
super.processOperator(operator, operands);
@@ -420,7 +420,7 @@ public class PdfJsonImageService {
throws IOException {
if (OperatorName.DRAW_OBJECT.equals(operator.getName())
&& !operands.isEmpty()
&& operands.get(0) instanceof COSName name) {
&& operands.getFirst() instanceof COSName name) {
currentXObjectName = name;
}
super.processOperator(operator, operands);
@@ -137,7 +137,7 @@ public class JobController {
if (result.hasFiles() && !result.hasMultipleFiles()) {
try {
List<ResultFile> files = result.getAllResultFiles();
ResultFile singleFile = files.get(0);
ResultFile singleFile = files.getFirst();
byte[] fileContent = fileStorage.retrieveBytes(singleFile.getFileId());
return ResponseEntity.ok()
@@ -4,8 +4,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -24,10 +22,7 @@ class MultipartConfigurationTest {
void setUp() throws Exception {
// Manually constructed config with a mocked service, so Spring env overrides do not apply.
uploadLimitService = mock(UploadLimitService.class);
configuration = new MultipartConfiguration();
Field field = MultipartConfiguration.class.getDeclaredField("uploadLimitService");
field.setAccessible(true);
field.set(configuration, uploadLimitService);
configuration = new MultipartConfiguration(uploadLimitService);
}
@Nested
@@ -84,12 +84,6 @@ class MergeControllerGapTest {
return (MultipartFile[]) m.invoke(null, files, fileOrder);
}
private String[] parseClientFileIds(String value) throws Exception {
Method m = MergeController.class.getDeclaredMethod("parseClientFileIds", String.class);
m.setAccessible(true);
return (String[]) m.invoke(mergeController, value);
}
private long getPdfDateTimeSafe(MultipartFile file) throws Exception {
Method m =
MergeController.class.getDeclaredMethod("getPdfDateTimeSafe", MultipartFile.class);
@@ -331,58 +325,6 @@ class MergeControllerGapTest {
}
}
// ---- parseClientFileIds -------------------------------------------------
@Nested
@DisplayName("parseClientFileIds")
class ParseClientFileIds {
@Test
@DisplayName("null input returns empty array")
void nullReturnsEmpty() throws Exception {
assertEquals(0, parseClientFileIds(null).length);
}
@Test
@DisplayName("blank input returns empty array")
void blankReturnsEmpty() throws Exception {
assertEquals(0, parseClientFileIds(" ").length);
}
@Test
@DisplayName("empty JSON array returns empty array")
void emptyArrayReturnsEmpty() throws Exception {
assertEquals(0, parseClientFileIds("[]").length);
assertEquals(0, parseClientFileIds("[ ]").length);
}
@Test
@DisplayName("non-array text returns empty array")
void nonArrayReturnsEmpty() throws Exception {
assertEquals(0, parseClientFileIds("not-an-array").length);
}
@Test
@DisplayName("parses quoted, comma-separated ids and strips surrounding quotes")
void parsesQuotedIds() throws Exception {
String[] result = parseClientFileIds("[\"id1\", \"id2\",\"id3\"]");
assertArrayEquals(new String[] {"id1", "id2", "id3"}, result);
}
@Test
@DisplayName("parses unquoted ids as-is after trimming")
void parsesUnquotedIds() throws Exception {
String[] result = parseClientFileIds("[a, b , c]");
assertArrayEquals(new String[] {"a", "b", "c"}, result);
}
@Test
@DisplayName("single element array yields a one-element result")
void singleElement() throws Exception {
assertArrayEquals(new String[] {"only"}, parseClientFileIds("[\"only\"]"));
}
}
// ---- reorderFilesByProvidedOrder ----------------------------------------
@Nested
@@ -0,0 +1,66 @@
package stirling.software.SPDF.controller.api;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.http.ResponseEntity;
import stirling.software.SPDF.service.SharedSignatureService;
import stirling.software.common.configuration.RuntimePathConfig;
import stirling.software.common.model.ApplicationProperties;
import tools.jackson.databind.ObjectMapper;
class UIDataControllerTest {
@TempDir Path tempDir;
@Test
void getPipelineData_usesEachSourceFilenameWhenJsonContentIsIdentical() throws Exception {
Path configDir = tempDir.resolve("defaultWebUIConfigs");
Files.createDirectories(configDir);
String configJson = "{\"steps\":[]}";
Files.writeString(configDir.resolve("first-config.json"), configJson);
Files.writeString(configDir.resolve("second-config.json"), configJson);
ApplicationProperties applicationProperties = mock(ApplicationProperties.class);
SharedSignatureService signatureService = mock(SharedSignatureService.class);
RuntimePathConfig runtimePathConfig = mock(RuntimePathConfig.class);
when(runtimePathConfig.getPipelineDefaultWebUiConfigs()).thenReturn(configDir.toString());
UIDataController controller =
new UIDataController(
applicationProperties,
signatureService,
null,
new DefaultResourceLoader(),
runtimePathConfig,
new ObjectMapper());
ResponseEntity<UIDataController.PipelineData> response = controller.getPipelineData();
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
UIDataController.PipelineData body = response.getBody();
assertThat(body).isNotNull();
List<Map<String, String>> configsWithNames = body.getPipelineConfigsWithNames();
assertThat(configsWithNames).hasSize(2);
assertThat(configsWithNames)
.extracting(entry -> entry.get("name"))
.containsExactlyInAnyOrder("first-config", "second-config");
assertThat(configsWithNames)
.extracting(entry -> entry.get("json"))
.containsOnly(configJson);
}
}
@@ -204,7 +204,7 @@ class ConvertOfficeControllerTest {
inv -> {
// unoconvert writes directly to the output path (last arg)
List<String> command = inv.getArgument(0);
Path out = Path.of(command.get(command.size() - 1));
Path out = Path.of(command.getLast());
Files.writeString(out, "%PDF-1.4 produced");
return result;
});
@@ -239,7 +239,7 @@ class ConvertOfficeControllerTest {
inv -> {
// soffice writes <basename>.pdf into the --outdir (workDir)
List<String> command = inv.getArgument(0);
Path inputPath = Path.of(command.get(command.size() - 1));
Path inputPath = Path.of(command.getLast());
Path out = inputPath.getParent().resolve("report.pdf");
Files.writeString(out, "%PDF soffice");
return result;
@@ -311,7 +311,7 @@ class ConvertOfficeControllerTest {
.thenAnswer(
inv -> {
List<String> command = inv.getArgument(0);
Path inputPath = Path.of(command.get(command.size() - 1));
Path inputPath = Path.of(command.getLast());
Path out = inputPath.getParent().resolve("report.pdf");
Files.write(out, new byte[0]);
return result;
@@ -344,7 +344,7 @@ class ConvertOfficeControllerTest {
.thenAnswer(
inv -> {
List<String> command = inv.getArgument(0);
Path inputPath = Path.of(command.get(command.size() - 1));
Path inputPath = Path.of(command.getLast());
Path out = inputPath.getParent().resolve("page.pdf");
Files.writeString(out, "%PDF html");
return result;
@@ -398,7 +398,7 @@ class ConvertOfficeControllerTest {
.thenAnswer(
inv -> {
List<String> command = inv.getArgument(0);
Path inputPath = Path.of(command.get(command.size() - 1));
Path inputPath = Path.of(command.getLast());
Path out = inputPath.getParent().resolve("report.pdf");
Files.writeString(out, "%PDF produced");
return result;
@@ -197,7 +197,7 @@ class ConvertPDFToPDFAMoreTest {
// qpdf normalize/clean writes its (last-arg) output file
if (command.contains("--normalize-content=y")) {
// qpdf produced file is the last argument
Path out = Path.of(command.get(command.size() - 1));
Path out = Path.of(command.getLast());
Files.write(out, simplePdfBytes());
}
return okResult;
@@ -262,7 +262,7 @@ class ConvertPdfToVideoControllerTest {
assertTrue(command.contains("+faststart"));
assertFalse(command.contains("libvpx-vp9"));
// Output path is always the last argument.
assertEquals(backing.getAbsolutePath(), command.get(command.size() - 1));
assertEquals(backing.getAbsolutePath(), command.getLast());
}
@Test
@@ -278,7 +278,7 @@ class ConvertPdfToVideoControllerTest {
assertTrue(command.contains("30"));
assertFalse(command.contains("libx264"));
assertFalse(command.contains("+faststart"));
assertEquals(backing.getAbsolutePath(), command.get(command.size() - 1));
assertEquals(backing.getAbsolutePath(), command.getLast());
}
@Test
@@ -195,7 +195,7 @@ class CompressControllerMoreTest {
// The qpdf output path is the last argument of the command.
private static Path qpdfOutputPath(List<String> command) {
return Path.of(command.get(command.size() - 1));
return Path.of(command.getLast());
}
/** Stub gs to write a valid PDF to its output file and report success. */
@@ -162,7 +162,7 @@ class RemoveImagesControllerTest {
/** Counts every PDImageXObject reachable through page + nested form resources. */
private int countImagesInSavedOutput() throws IOException {
assertFalse(savedTempFiles.isEmpty(), "expected the controller to create a temp file");
File out = savedTempFiles.get(savedTempFiles.size() - 1);
File out = savedTempFiles.getLast();
try (PDDocument doc = Loader.loadPDF(out)) {
int count = 0;
for (PDPage page : doc.getPages()) {
@@ -245,7 +245,7 @@ class RemoveImagesControllerTest {
assertEquals(0, countImagesInSavedOutput());
// page count must be preserved
File out = savedTempFiles.get(savedTempFiles.size() - 1);
File out = savedTempFiles.getLast();
try (PDDocument result = Loader.loadPDF(out)) {
assertEquals(3, result.getNumberOfPages());
}
@@ -98,12 +98,8 @@ class RepairControllerMoreTest {
}
}
/**
* Writes a valid PDF to the path at the given command index, mimicking a successful tool run.
*/
private static void writeValidPdfTo(List<String> command, int outputPathIndex)
throws Exception {
Path out = Path.of(command.get(outputPathIndex));
/** Writes a valid PDF to the given output path, mimicking a successful tool run. */
private static void writeValidPdfTo(Path out) throws Exception {
byte[] pdf = buildPdfBytes(1);
Files.write(out, pdf);
}
@@ -133,7 +129,7 @@ class RepairControllerMoreTest {
.thenAnswer(
inv -> {
List<String> cmd = inv.getArgument(0);
writeValidPdfTo(cmd, 2);
writeValidPdfTo(Path.of(cmd.get(2)));
return okResult;
});
@@ -176,7 +172,7 @@ class RepairControllerMoreTest {
.thenAnswer(
inv -> {
List<String> cmd = inv.getArgument(0);
writeValidPdfTo(cmd, cmd.size() - 1);
writeValidPdfTo(Path.of(cmd.getLast()));
return okResult;
});
@@ -216,7 +212,7 @@ class RepairControllerMoreTest {
.thenAnswer(
inv -> {
List<String> cmd = inv.getArgument(0);
writeValidPdfTo(cmd, cmd.size() - 1);
writeValidPdfTo(Path.of(cmd.getLast()));
return okResult;
});
@@ -256,7 +252,7 @@ class RepairControllerMoreTest {
.thenAnswer(
inv -> {
List<String> cmd = inv.getArgument(0);
writeValidPdfTo(cmd, cmd.size() - 1);
writeValidPdfTo(Path.of(cmd.getLast()));
return okResult;
});
@@ -49,15 +49,7 @@ class UploadLimitServiceTest {
systemProps = mock(ApplicationProperties.System.class);
when(applicationProperties.getSystem()).thenReturn(systemProps);
uploadLimitService = new UploadLimitService();
// inject mock
try {
var field = UploadLimitService.class.getDeclaredField("applicationProperties");
field.setAccessible(true);
field.set(uploadLimitService, applicationProperties);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
uploadLimitService = new UploadLimitService(applicationProperties);
}
@ParameterizedTest(name = "getReadableUploadLimit case #{index}: rawValue={0}, expected={1}")
@@ -143,19 +143,12 @@ public class AuditRestController {
@RequestParam(value = "period", defaultValue = "week") String period) {
// Calculate days based on period
int days;
switch (period.toLowerCase()) {
case "day":
days = 1;
break;
case "month":
days = 30;
break;
case "week":
default:
days = 7;
break;
}
int days =
switch (period.toLowerCase()) {
case "day" -> 1;
case "month" -> 30;
default -> 7;
};
// Get events from the specified period
Instant startDate = Instant.now().minus(java.time.Duration.ofDays(days));
@@ -269,19 +262,12 @@ public class AuditRestController {
@RequestParam(value = "period", defaultValue = "week") String period) {
// Calculate days based on period
int days;
switch (period.toLowerCase()) {
case "day":
days = 1;
break;
case "month":
days = 30;
break;
case "week":
default:
days = 7;
break;
}
int days =
switch (period.toLowerCase()) {
case "day" -> 1;
case "month" -> 30;
default -> 7;
};
// Get events from the specified period and previous period
Instant now = Instant.now();
@@ -754,7 +740,7 @@ public class AuditRestController {
List<Map<String, Object>> files =
(List<Map<String, Object>>) eventData.get("files");
if (files != null && !files.isEmpty()) {
Map<String, Object> firstFile = files.get(0);
Map<String, Object> firstFile = files.getFirst();
data.put("documentname", String.valueOf(firstFile.getOrDefault("name", "")));
data.put("author", String.valueOf(firstFile.getOrDefault("pdfAuthor", "")));
data.put("filehash", String.valueOf(firstFile.getOrDefault("fileHash", "")));
@@ -131,7 +131,7 @@ public class PolicyExecutor {
// One call over all inputs. The outputs derive from a single input only when exactly
// one entered; otherwise (a genuine merge) there is no single source.
ToolResult r = callEndpoint(step, inputFiles, supportingFiles);
Integer origin = inputOrigins.size() == 1 ? inputOrigins.get(0) : null;
Integer origin = inputOrigins.size() == 1 ? inputOrigins.getFirst() : null;
for (Resource file : r.files()) {
files.add(file);
origins.add(origin);
@@ -2,7 +2,6 @@ package stirling.software.proprietary.security.configuration;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
@@ -19,7 +18,6 @@ public class CacheConfig {
private final ApplicationProperties applicationProperties;
@Autowired
public CacheConfig(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@@ -61,7 +61,7 @@ public class CustomSaml2ResponseAuthenticationConverter
@Override
public Saml2Authentication convert(ResponseToken responseToken) {
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
Assertion assertion = responseToken.getResponse().getAssertions().getFirst();
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Debug log with actual values
@@ -113,6 +113,6 @@ public class CustomSaml2ResponseAuthenticationConverter
private String getFirstAttributeValue(Map<String, List<Object>> attributes, String name) {
List<Object> values = attributes.get(name);
return values != null && !values.isEmpty() ? values.get(0).toString() : null;
return values != null && !values.isEmpty() ? values.getFirst().toString() : null;
}
}
@@ -229,7 +229,7 @@ public class DatabaseService implements DatabaseServiceInterface {
List<FileInfo> backupList = this.getBackupList();
backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed());
Path latestExport = Path.of(backupList.get(0).getFilePath());
Path latestExport = Path.of(backupList.getFirst().getFilePath());
executeDatabaseScript(latestExport);
}
@@ -373,7 +373,7 @@ public class DatabaseService implements DatabaseServiceInterface {
List<FileInfo> backupList = this.getBackupList();
List<Pair<FileInfo, Boolean>> deletedFiles = new ArrayList<>();
if (!backupList.isEmpty()) {
FileInfo lastBackup = backupList.get(backupList.size() - 1);
FileInfo lastBackup = backupList.getLast();
try {
Files.deleteIfExists(Path.of(lastBackup.getFilePath()));
deletedFiles.add(Pair.of(lastBackup, true));
@@ -396,7 +396,7 @@ public class DatabaseService implements DatabaseServiceInterface {
Comparator.comparing(
p -> p.getFileName().substring(7, p.getFileName().length() - 4)));
FileInfo oldestFile = filteredBackupList.get(0);
FileInfo oldestFile = filteredBackupList.getFirst();
Files.deleteIfExists(Path.of(oldestFile.getFilePath()));
log.info("Deleted oldest backup: {}", oldestFile.getFileName());
} catch (IOException e) {
@@ -261,8 +261,8 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
nativeCache.asMap().size());
return nativeCache.asMap().values().stream()
.filter(value -> value instanceof JwtVerificationKey)
.map(value -> (JwtVerificationKey) value)
.filter(JwtVerificationKey.class::isInstance)
.map(JwtVerificationKey.class::cast)
.filter(
key -> {
boolean eligible = key.getCreatedAt().isBefore(cutoffDate);
@@ -183,6 +183,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
allSessions.sort((s1, s2) -> s2.getLastRequest().compareTo(s1.getLastRequest()));
// The first session in the list is the latest session for the given principal name
return Optional.of(allSessions.get(0));
return Optional.of(allSessions.getFirst());
}
}
@@ -230,7 +230,7 @@ public class PortalInfraAuditService {
Object files = data.get("files");
if (files instanceof List<?> list
&& !list.isEmpty()
&& list.get(0) instanceof Map<?, ?> f) {
&& list.getFirst() instanceof Map<?, ?> f) {
Object name = ((Map<String, Object>) f).get("name");
return name != null ? String.valueOf(name) : null;
}
@@ -119,20 +119,18 @@ public class UnifiedAccessControlService {
public ShareAccessRole getEffectiveRole(WorkflowParticipant participant) {
ParticipantStatus status = participant.getStatus();
switch (status) {
case SIGNED:
case DECLINED:
// After action completed, downgrade to read-only
return ShareAccessRole.VIEWER;
case PENDING:
case NOTIFIED:
case VIEWED:
// Active participants retain their assigned role
return participant.getAccessRole();
default:
return switch (status) {
case SIGNED, DECLINED ->
// After action completed, downgrade to read-only
ShareAccessRole.VIEWER;
case PENDING, NOTIFIED, VIEWED ->
// Active participants retain their assigned role
participant.getAccessRole();
default -> {
log.warn("Unknown participant status: {}", status);
return ShareAccessRole.VIEWER;
}
yield ShareAccessRole.VIEWER;
}
};
}
/** Checks if a user can access a specific file */
@@ -152,7 +152,7 @@ public class AccountLinkController {
if (rows.isEmpty()) {
return new LeaderTeam(null, null, HttpStatus.FORBIDDEN);
}
TeamMembership m = rows.get(0);
TeamMembership m = rows.getFirst();
if (m.getRole() != TeamRole.LEADER) {
return new LeaderTeam(null, null, HttpStatus.FORBIDDEN);
}
@@ -105,7 +105,7 @@ public class PaygInvoicesController {
if (rows.isEmpty()) {
return ResponseEntity.ok(List.of());
}
Long teamId = rows.get(0).getTeam().getId();
Long teamId = rows.getFirst().getTeam().getId();
// No PAYG extension row OR no Stripe customer id team has never subscribed no
// invoices. Empty list, not 404 the UI distinguishes "no invoices yet" from a
@@ -85,7 +85,7 @@ public class PaygPaymentMethodController {
if (rows.isEmpty()) {
return ResponseEntity.ok(PaymentMethodResponse.absent());
}
Long teamId = rows.get(0).getTeam().getId();
Long teamId = rows.getFirst().getTeam().getId();
Optional<PaygTeamExtensions> ext = extRepo.findById(teamId);
if (ext.isEmpty() || ext.get().getStripeCustomerId() == null) {
@@ -361,7 +361,7 @@ public class PaygWalletController {
private Optional<TeamMembership> primaryMembership(Long userId) {
List<TeamMembership> rows = memberRepo.findPrimaryMembership(userId);
return rows.isEmpty() ? Optional.empty() : Optional.of(rows.get(0));
return rows.isEmpty() ? Optional.empty() : Optional.of(rows.getFirst());
}
private List<MemberRow> buildMemberRows(
@@ -258,7 +258,7 @@ public class JobChargeService {
List<Path> paths = inputs.stream().map(JobInput::path).toList();
DocumentMetrics metrics =
multiparts.size() == 1
? classifier.classify(multiparts.get(0), paths.get(0), policy)
? classifier.classify(multiparts.getFirst(), paths.getFirst(), policy)
: classifier.classify(multiparts, paths, policy);
// Apply the policy-level minChargeUnits floor per design § 3.4. The classifier returns
// raw docUnits with a "non-empty input → ≥1" floor; the charge formula's
@@ -75,7 +75,7 @@ public class JpaJobLineageStore implements JobLineageStore {
List<LineageMatch> matches =
hashRepository.findOpenJobsForSignatures(
userId, JobStatus.OPEN, since, storageKeys, Limit.of(1));
return matches.isEmpty() ? Optional.empty() : Optional.of(matches.get(0));
return matches.isEmpty() ? Optional.empty() : Optional.of(matches.getFirst());
}
@Override
@@ -284,7 +284,7 @@ public class SupabaseAuthenticationFilter extends OncePerRequestFilter {
if (!(raw instanceof List<?> amrList) || amrList.isEmpty()) {
return WEB;
}
Object first = amrList.get(0);
Object first = amrList.getFirst();
if (!(first instanceof Map<?, ?> entry)) {
return WEB;
}