From b3b430db72d0065fcc63c8b647a4c89aa089ab32 Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 21 Sep 2025 14:16:28 -0600 Subject: [PATCH 01/74] - Added domain error handling hierarchy (DomainException, Violation, ErrorCode, etc.) - Introduced base Rule framework (Rule, CompositeRule) with reusable validation rules - Implemented ProjectName and ProjectDescription policies and value objects - Established initial domain model root (ProjectBlueprint) Note: This is a WIP commit. The branch is under active development and not yet production ready. --- .../domain/error/code/ErrorCode.java | 5 ++ .../domain/error/code/ErrorKeys.java | 9 +++ .../initializr/domain/error/code/Field.java | 17 +++++ .../domain/error/code/Violation.java | 13 ++++ .../exception/DomainConflictException.java | 9 +++ .../error/exception/DomainException.java | 26 ++++++++ .../exception/DomainViolationException.java | 9 +++ .../domain/model/ProjectBlueprint.java | 3 + .../model/value/ProjectDescription.java | 13 ++++ .../domain/model/value/ProjectName.java | 9 +++ .../policy/ProjectDescriptionPolicy.java | 42 ++++++++++++ .../domain/policy/ProjectNamePolicy.java | 64 +++++++++++++++++++ .../domain/policy/rule/AllowedCharsRule.java | 30 +++++++++ .../domain/policy/rule/LengthBetweenRule.java | 26 ++++++++ .../domain/policy/rule/NotBlankRule.java | 22 +++++++ .../domain/policy/rule/RegexMatchRule.java | 28 ++++++++ .../domain/policy/rule/ReservedNamesRule.java | 33 ++++++++++ .../policy/rule/base/CompositeRule.java | 31 +++++++++ .../domain/policy/rule/base/Rule.java | 6 ++ src/main/resources/messages.properties | 4 ++ 20 files changed, 399 insertions(+) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorCode.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorKeys.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainViolationException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/AllowedCharsRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/LengthBetweenRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NotBlankRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/RegexMatchRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedNamesRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/CompositeRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/Rule.java create mode 100644 src/main/resources/messages.properties diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorCode.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorCode.java new file mode 100644 index 0000000..5be504b --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorCode.java @@ -0,0 +1,5 @@ +package io.github.bsayli.codegen.initializr.domain.error.code; + +public interface ErrorCode { + String key(); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorKeys.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorKeys.java new file mode 100644 index 0000000..55f61ba --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/ErrorKeys.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.error.code; + +public final class ErrorKeys { + private ErrorKeys() {} + + public static ErrorCode compose(Field field, Violation v) { + return () -> field.key() + v.suffix; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java new file mode 100644 index 0000000..cfe9f74 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java @@ -0,0 +1,17 @@ +package io.github.bsayli.codegen.initializr.domain.error.code; + +public enum Field implements ErrorCode { + PROJECT_NAME("project.name"), + PROJECT_DESCRIPTION("project.description"); + + private final String key; + + Field(String key) { + this.key = key; + } + + @Override + public String key() { + return key; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java new file mode 100644 index 0000000..686c604 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.domain.error.code; + +public enum Violation { + NOT_BLANK(".not.blank"), + LENGTH(".length"), + INVALID_CHARS(".invalid.chars"), + RESERVED(".reserved"); + public final String suffix; + + Violation(String s) { + this.suffix = s; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java new file mode 100644 index 0000000..9f44f59 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.error.exception; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; + +public class DomainConflictException extends DomainException { + public DomainConflictException(ErrorCode code, Object... args) { + super(code, args); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java new file mode 100644 index 0000000..61ddc57 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java @@ -0,0 +1,26 @@ +package io.github.bsayli.codegen.initializr.domain.error.exception; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; + +public abstract class DomainException extends RuntimeException { + private final ErrorCode code; + private final transient Object[] args; + + protected DomainException(ErrorCode code, Object... args) { + super(code.key()); + this.code = code; + this.args = args; + } + + public ErrorCode getCode() { + return code; + } + + public String getMessageKey() { + return code.key(); + } + + public Object[] getArgs() { + return args; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainViolationException.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainViolationException.java new file mode 100644 index 0000000..3090969 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainViolationException.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.error.exception; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; + +public class DomainViolationException extends DomainException { + public DomainViolationException(ErrorCode code, Object... args) { + super(code, args); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java new file mode 100644 index 0000000..f1e37b5 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java @@ -0,0 +1,3 @@ +package io.github.bsayli.codegen.initializr.domain.model; + +public class ProjectBlueprint {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java new file mode 100644 index 0000000..bb2adee --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.policy.ProjectDescriptionPolicy; + +public record ProjectDescription(String value) { + public ProjectDescription { + value = ProjectDescriptionPolicy.enforce(value); + } + + public boolean isEmpty() { + return value.isEmpty(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java new file mode 100644 index 0000000..9ff5e41 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.policy.ProjectNamePolicy; + +public record ProjectName(String value) { + public ProjectName { + value = ProjectNamePolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java new file mode 100644 index 0000000..c5059f9 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java @@ -0,0 +1,42 @@ +package io.github.bsayli.codegen.initializr.domain.policy; + +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_DESCRIPTION; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; + +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.regex.Pattern; + +public final class ProjectDescriptionPolicy { + + private static final int MIN = 0; + private static final int MAX = 280; + + private static final Pattern NO_CONTROL_CHARS = Pattern.compile("^[\\P{Cntrl}]*$"); + + private ProjectDescriptionPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) return ""; + return raw.trim() + .replaceAll("\\s+", " ") + .toLowerCase(Locale.ROOT); + } + + private static void validate(String value) { + Rule rule = CompositeRule.of( + new LengthBetweenRule(MIN, MAX, PROJECT_DESCRIPTION), + new RegexMatchRule(NO_CONTROL_CHARS, PROJECT_DESCRIPTION, INVALID_CHARS) + ); + rule.check(value); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java new file mode 100644 index 0000000..9b0a2ff --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java @@ -0,0 +1,64 @@ +package io.github.bsayli.codegen.initializr.domain.policy; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_NAME; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.NOT_BLANK; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.ReservedNamesRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public final class ProjectNamePolicy { + + private static final int MIN = 3; + private static final int MAX = 40; + + private static final Pattern ALLOWED = Pattern.compile("^[a-z][a-z0-9-]{2,39}$"); + private static final Set RESERVED_BASE = Set.of("con", "prn", "aux", "nul"); + private static final Set RESERVED = + Stream.concat( + RESERVED_BASE.stream(), + Stream.concat( + IntStream.rangeClosed(1, 9).mapToObj(i -> "com" + i), + IntStream.rangeClosed(1, 9).mapToObj(i -> "lpt" + i))) + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); + + private ProjectNamePolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(PROJECT_NAME, NOT_BLANK)); + return raw.trim() + .replaceAll("\\s+", "-") + .replace('_', '-') + .toLowerCase(Locale.ROOT) + .replaceAll("-{2,}", "-"); + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new NotBlankRule(PROJECT_NAME), + new LengthBetweenRule(MIN, MAX, PROJECT_NAME), + new RegexMatchRule(ALLOWED, PROJECT_NAME, INVALID_CHARS), + new ReservedNamesRule(RESERVED, PROJECT_NAME)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/AllowedCharsRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/AllowedCharsRule.java new file mode 100644 index 0000000..ec1129a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/AllowedCharsRule.java @@ -0,0 +1,30 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Objects; +import java.util.regex.Pattern; + +public final class AllowedCharsRule implements Rule { + private final Pattern allowed; + private final Field field; + private final Violation violation; + + public AllowedCharsRule(String allowedCharClassRegex, Field field, Violation violation) { + Objects.requireNonNull(allowedCharClassRegex); + this.allowed = Pattern.compile("^(?:" + allowedCharClassRegex + ")+$"); + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null || !allowed.matcher(value).matches()) { + throw new DomainViolationException(compose(field, violation)); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/LengthBetweenRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/LengthBetweenRule.java new file mode 100644 index 0000000..0cf0ed8 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/LengthBetweenRule.java @@ -0,0 +1,26 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.LENGTH; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; + +public final class LengthBetweenRule implements Rule { + private final int min; + private final int max; + private final Field field; + + public LengthBetweenRule(int min, int max, Field field) { + this.min = min; + this.max = max; + this.field = field; + } + + @Override + public void check(String v) { + if (v == null || v.length() < min || v.length() > max) + throw new DomainViolationException(compose(field, LENGTH), min, max); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NotBlankRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NotBlankRule.java new file mode 100644 index 0000000..a3068a9 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NotBlankRule.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.NOT_BLANK; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; + +public final class NotBlankRule implements Rule { + private final Field field; + + public NotBlankRule(Field field) { + this.field = field; + } + + @Override + public void check(String value) { + if (value == null || value.isBlank()) + throw new DomainViolationException(compose(field, NOT_BLANK)); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/RegexMatchRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/RegexMatchRule.java new file mode 100644 index 0000000..b9d5baf --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/RegexMatchRule.java @@ -0,0 +1,28 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.regex.Pattern; + +public final class RegexMatchRule implements Rule { + private final Pattern pattern; + private final Field field; + private final Violation violation; + + public RegexMatchRule(Pattern pattern, Field field, Violation violation) { + this.pattern = pattern; + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null || !pattern.matcher(value).matches()) { + throw new DomainViolationException(compose(field, violation)); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedNamesRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedNamesRule.java new file mode 100644 index 0000000..3dd286b --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedNamesRule.java @@ -0,0 +1,33 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +public final class ReservedNamesRule implements Rule { + private final Set reservedLower; + private final Field field; + + public ReservedNamesRule(Set reserved, Field field) { + this.reservedLower = + reserved.stream() + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); + this.field = field; + } + + @Override + public void check(String value) { + if (value == null) throw new DomainViolationException(compose(field, Violation.RESERVED)); + String lower = value.toLowerCase(Locale.ROOT); + if (reservedLower.contains(lower)) { + throw new DomainViolationException(compose(field, Violation.RESERVED), value); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/CompositeRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/CompositeRule.java new file mode 100644 index 0000000..6c2daba --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/CompositeRule.java @@ -0,0 +1,31 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule.base; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class CompositeRule implements Rule { + + private final List> rules; + + private CompositeRule(List> rules) { + this.rules = List.copyOf(rules); + } + + @SafeVarargs + public static CompositeRule of(Rule... rules) { + List> list = Arrays.asList(rules); + return new CompositeRule<>(list); + } + + public static CompositeRule of(List> rules) { + return new CompositeRule<>(new ArrayList<>(rules)); + } + + @Override + public void check(T value) { + for (Rule r : rules) { + r.check(value); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/Rule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/Rule.java new file mode 100644 index 0000000..45a97f8 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/base/Rule.java @@ -0,0 +1,6 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule.base; + +@FunctionalInterface +public interface Rule { + void check(T value); +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties new file mode 100644 index 0000000..2ae50cf --- /dev/null +++ b/src/main/resources/messages.properties @@ -0,0 +1,4 @@ +project.name.not.blank=Project name is required +project.name.length=Project name must be between {0} and {1} characters +project.name.invalid.chars=Only [a-z][a-z0-9-] allowed +project.name.reserved=Reserved project name: {0} \ No newline at end of file From 34cf8229269520e49f98beea0aff1ed2a8d4a37d Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 21 Sep 2025 18:51:38 -0600 Subject: [PATCH 02/74] feat(domain): introduce ProjectBlueprint aggregate and validation rule framework - Added ProjectBlueprint aggregate root with core value objects (Name, Description, Identity, PackageName) - Introduced ProjectIdentity to encapsulate GroupId + ArtifactId - Implemented comprehensive policy classes: * ProjectNamePolicy with rich validation (length, allowed chars, reserved names, structural rules) * ArtifactIdPolicy, GroupIdPolicy, PackageNamePolicy, ProjectDescriptionPolicy - Added reusable validation rules: * NotBlankRule, LengthBetweenRule, AllowedCharsRule, StartsWithLetterRule * NoEdgeCharRule, NoConsecutiveCharRule, ReservedNamesRule * DotSeparatedSegmentsRule, ReservedPrefixRule, RegexMatchRule - Unified error handling with DomainViolationException and message key integration - Prepared i18n-ready error messages in messages.properties --- .../initializr/domain/error/code/Field.java | 11 +++- .../domain/error/code/Violation.java | 10 ++- .../error/exception/DomainException.java | 2 +- .../domain/model/ProjectBlueprint.java | 37 ++++++++++- .../domain/model/value/ArtifactId.java | 9 +++ .../domain/model/value/GroupId.java | 9 +++ .../domain/model/value/PackageName.java | 9 +++ .../domain/model/value/ProjectIdentity.java | 22 +++++++ .../domain/policy/ArtifactIdPolicy.java | 51 +++++++++++++++ .../domain/policy/GroupIdPolicy.java | 44 +++++++++++++ .../domain/policy/PackageNamePolicy.java | 63 +++++++++++++++++++ .../policy/ProjectDescriptionPolicy.java | 54 ++++++++-------- .../domain/policy/ProjectNamePolicy.java | 47 +++++++------- .../policy/rule/DotSeparatedSegmentsRule.java | 37 +++++++++++ .../policy/rule/NoConsecutiveCharRule.java | 32 ++++++++++ .../domain/policy/rule/NoEdgeCharRule.java | 30 +++++++++ .../policy/rule/ReservedPrefixRule.java | 36 +++++++++++ .../policy/rule/StartsWithLetterRule.java | 29 +++++++++ src/main/resources/messages.properties | 38 ++++++++++- 19 files changed, 510 insertions(+), 60 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/DotSeparatedSegmentsRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoConsecutiveCharRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoEdgeCharRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/StartsWithLetterRule.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java index cfe9f74..133b11f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java @@ -1,8 +1,11 @@ package io.github.bsayli.codegen.initializr.domain.error.code; public enum Field implements ErrorCode { - PROJECT_NAME("project.name"), - PROJECT_DESCRIPTION("project.description"); + PROJECT_NAME(project("name")), + PROJECT_DESCRIPTION(project("description")), + GROUP_ID(project("group-id")), + ARTIFACT_ID(project("artifact-id")), + PACKAGE_NAME(project("package-id")); private final String key; @@ -10,6 +13,10 @@ public enum Field implements ErrorCode { this.key = key; } + private static String project(String suffix) { + return "project." + suffix; + } + @Override public String key() { return key; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java index 686c604..ca420ce 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Violation.java @@ -4,7 +4,15 @@ public enum Violation { NOT_BLANK(".not.blank"), LENGTH(".length"), INVALID_CHARS(".invalid.chars"), - RESERVED(".reserved"); + RESERVED(".reserved"), + + STARTS_WITH_LETTER(".starts.with.letter"), + EDGE_CHAR(".edge.char"), + CONSECUTIVE_CHAR(".consecutive.char"), + SEGMENT_FORMAT(".segment.format"), + RESERVED_PREFIX(".reserved.prefix"), + CONTROL_CHARS(".control.chars"); + public final String suffix; Violation(String s) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java index 61ddc57..7f05066 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainException.java @@ -3,7 +3,7 @@ import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; public abstract class DomainException extends RuntimeException { - private final ErrorCode code; + private final transient ErrorCode code; private final transient Object[] args; protected DomainException(ErrorCode code, Object... args) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java index f1e37b5..18c0835 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java @@ -1,3 +1,38 @@ package io.github.bsayli.codegen.initializr.domain.model; -public class ProjectBlueprint {} +import io.github.bsayli.codegen.initializr.domain.model.value.*; + +public class ProjectBlueprint { + + private final ProjectName name; + private final ProjectDescription description; + private final ProjectIdentity projectIdentity; + private final PackageName packageName; + + public ProjectBlueprint( + ProjectName name, + ProjectDescription description, + ProjectIdentity projectIdentity, + PackageName packageName) { + this.name = name; + this.description = description; + this.projectIdentity = projectIdentity; + this.packageName = packageName; + } + + public ProjectName getName() { + return name; + } + + public ProjectDescription getDescription() { + return description; + } + + public PackageName getPackageName() { + return packageName; + } + + public ProjectIdentity getProjectIdentity() { + return projectIdentity; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java new file mode 100644 index 0000000..74458cd --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.policy.ArtifactIdPolicy; + +public record ArtifactId(String value) { + public ArtifactId { + value = ArtifactIdPolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java new file mode 100644 index 0000000..6dbbc6a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.policy.GroupIdPolicy; + +public record GroupId(String value) { + public GroupId { + value = GroupIdPolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java new file mode 100644 index 0000000..b4317a2 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.policy.PackageNamePolicy; + +public record PackageName(String value) { + public PackageName { + value = PackageNamePolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java new file mode 100644 index 0000000..ecef040 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.domain.model.value; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.springframework.lang.NonNull; + +public record ProjectIdentity(GroupId groupId, ArtifactId artifactId) { + + private static final ErrorCode IDENTITY_REQUIRED = () -> "project.identity.not.blank"; + + public ProjectIdentity { + if (groupId == null || artifactId == null) { + throw new DomainViolationException(IDENTITY_REQUIRED); + } + } + + @Override + @NonNull + public String toString() { + return groupId.value() + ":" + artifactId.value(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java new file mode 100644 index 0000000..d0cb0ba --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java @@ -0,0 +1,51 @@ +package io.github.bsayli.codegen.initializr.domain.policy; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.ARTIFACT_ID; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.AllowedCharsRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NoConsecutiveCharRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NoEdgeCharRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.StartsWithLetterRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; + +public final class ArtifactIdPolicy { + + private static final int MIN = 3; + private static final int MAX = 50; + + private ArtifactIdPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(ARTIFACT_ID, NOT_BLANK)); + return raw.trim() + .replaceAll("\\s+", "-") + .replace('_', '-') + .toLowerCase(Locale.ROOT) + .replaceAll("-{2,}", "-"); + } + + private static void validate(String value) { + Rule rule = CompositeRule.of( + new NotBlankRule(ARTIFACT_ID), + new LengthBetweenRule(MIN, MAX, ARTIFACT_ID), + new AllowedCharsRule("[a-z0-9-]", ARTIFACT_ID, INVALID_CHARS), + new StartsWithLetterRule(ARTIFACT_ID, STARTS_WITH_LETTER), + new NoEdgeCharRule('-', ARTIFACT_ID, EDGE_CHAR), + new NoConsecutiveCharRule('-', ARTIFACT_ID, CONSECUTIVE_CHAR) + ); + rule.check(value); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java new file mode 100644 index 0000000..e1152fe --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java @@ -0,0 +1,44 @@ +package io.github.bsayli.codegen.initializr.domain.policy; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.GROUP_ID; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.DotSeparatedSegmentsRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.regex.Pattern; + +public final class GroupIdPolicy { + + private static final int MIN = 3; + private static final int MAX = 100; + + private static final Pattern SEGMENT = Pattern.compile("^[a-z][a-z0-9]*$"); + + private GroupIdPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(GROUP_ID, NOT_BLANK)); + return raw.trim().replaceAll("\\s+", "").toLowerCase(Locale.ROOT); + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new NotBlankRule(GROUP_ID), + new LengthBetweenRule(MIN, MAX, GROUP_ID), + new DotSeparatedSegmentsRule(SEGMENT, GROUP_ID, SEGMENT_FORMAT)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java new file mode 100644 index 0000000..945451c --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java @@ -0,0 +1,63 @@ +package io.github.bsayli.codegen.initializr.domain.policy; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PACKAGE_NAME; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.DotSeparatedSegmentsRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.ReservedPrefixRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +public final class PackageNamePolicy { + + private static final int MIN = 3; + private static final int MAX = 255; + + private static final Pattern SEGMENT = Pattern.compile("^[a-z][a-z0-9]*$"); + + private static final Pattern SEP_CHARS = Pattern.compile("[\\s_\\-]+"); + private static final Pattern MULTI_DOTS = Pattern.compile("\\.{2,}"); + private static final Pattern LEADING_DOTS = Pattern.compile("^\\.+"); + private static final Pattern TRAILING_DOTS = Pattern.compile("\\.+$"); + + private static final Set RESERVED_PREFIXES = Set.of( + "java", "javax", "sun", "com.sun" + ); + + private PackageNamePolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(PACKAGE_NAME, NOT_BLANK)); + + String s = raw.trim(); + s = SEP_CHARS.matcher(s).replaceAll("."); + s = MULTI_DOTS.matcher(s).replaceAll("."); + s = LEADING_DOTS.matcher(s).replaceAll(""); + s = TRAILING_DOTS.matcher(s).replaceAll(""); + s = s.toLowerCase(Locale.ROOT); + return s; + } + + private static void validate(String value) { + Rule rule = CompositeRule.of( + new NotBlankRule(PACKAGE_NAME), + new LengthBetweenRule(MIN, MAX, PACKAGE_NAME), + new DotSeparatedSegmentsRule(SEGMENT, PACKAGE_NAME, SEGMENT_FORMAT), + new ReservedPrefixRule(RESERVED_PREFIXES, PACKAGE_NAME, RESERVED_PREFIX) + ); + rule.check(value); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java index c5059f9..2a9e71e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java @@ -12,31 +12,29 @@ public final class ProjectDescriptionPolicy { - private static final int MIN = 0; - private static final int MAX = 280; - - private static final Pattern NO_CONTROL_CHARS = Pattern.compile("^[\\P{Cntrl}]*$"); - - private ProjectDescriptionPolicy() {} - - public static String enforce(String raw) { - String n = normalize(raw); - validate(n); - return n; - } - - private static String normalize(String raw) { - if (raw == null) return ""; - return raw.trim() - .replaceAll("\\s+", " ") - .toLowerCase(Locale.ROOT); - } - - private static void validate(String value) { - Rule rule = CompositeRule.of( - new LengthBetweenRule(MIN, MAX, PROJECT_DESCRIPTION), - new RegexMatchRule(NO_CONTROL_CHARS, PROJECT_DESCRIPTION, INVALID_CHARS) - ); - rule.check(value); - } -} \ No newline at end of file + private static final int MIN = 0; + private static final int MAX = 280; + + private static final Pattern NO_CONTROL_CHARS = Pattern.compile("^\\P{Cntrl}*$"); + + private ProjectDescriptionPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) return ""; + return raw.trim().replaceAll("\\s+", " ").toLowerCase(Locale.ROOT); + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new LengthBetweenRule(MIN, MAX, PROJECT_DESCRIPTION), + new RegexMatchRule(NO_CONTROL_CHARS, PROJECT_DESCRIPTION, INVALID_CHARS)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java index 9b0a2ff..efe73a9 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java @@ -2,19 +2,14 @@ import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_NAME; -import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; -import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.NOT_BLANK; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.ReservedNamesRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.*; import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; import java.util.Locale; import java.util.Set; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -24,16 +19,15 @@ public final class ProjectNamePolicy { private static final int MIN = 3; private static final int MAX = 40; - private static final Pattern ALLOWED = Pattern.compile("^[a-z][a-z0-9-]{2,39}$"); private static final Set RESERVED_BASE = Set.of("con", "prn", "aux", "nul"); - private static final Set RESERVED = - Stream.concat( - RESERVED_BASE.stream(), - Stream.concat( - IntStream.rangeClosed(1, 9).mapToObj(i -> "com" + i), - IntStream.rangeClosed(1, 9).mapToObj(i -> "lpt" + i))) - .map(s -> s.toLowerCase(Locale.ROOT)) - .collect(Collectors.toUnmodifiableSet()); + private static final Set RESERVED_NAMES = + Stream.concat( + RESERVED_BASE.stream(), + Stream.concat( + IntStream.rangeClosed(1, 9).mapToObj(i -> "com" + i), + IntStream.rangeClosed(1, 9).mapToObj(i -> "lpt" + i))) + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); private ProjectNamePolicy() {} @@ -46,19 +40,22 @@ public static String enforce(String raw) { private static String normalize(String raw) { if (raw == null) throw new DomainViolationException(compose(PROJECT_NAME, NOT_BLANK)); return raw.trim() - .replaceAll("\\s+", "-") - .replace('_', '-') - .toLowerCase(Locale.ROOT) - .replaceAll("-{2,}", "-"); + .replaceAll("\\s+", "-") + .replace('_', '-') + .toLowerCase(Locale.ROOT) + .replaceAll("-{2,}", "-"); } private static void validate(String value) { - Rule rule = - CompositeRule.of( + Rule rule = CompositeRule.of( new NotBlankRule(PROJECT_NAME), new LengthBetweenRule(MIN, MAX, PROJECT_NAME), - new RegexMatchRule(ALLOWED, PROJECT_NAME, INVALID_CHARS), - new ReservedNamesRule(RESERVED, PROJECT_NAME)); + new AllowedCharsRule("[a-z0-9-]", PROJECT_NAME, INVALID_CHARS), + new StartsWithLetterRule(PROJECT_NAME, STARTS_WITH_LETTER), + new NoEdgeCharRule('-', PROJECT_NAME, EDGE_CHAR), + new NoConsecutiveCharRule('-', PROJECT_NAME, CONSECUTIVE_CHAR), + new ReservedNamesRule(RESERVED_NAMES, PROJECT_NAME) + ); rule.check(value); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/DotSeparatedSegmentsRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/DotSeparatedSegmentsRule.java new file mode 100644 index 0000000..70da0af --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/DotSeparatedSegmentsRule.java @@ -0,0 +1,37 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.regex.Pattern; + +public final class DotSeparatedSegmentsRule implements Rule { + private final Pattern segmentPattern; + private final Field field; + private final Violation violation; + + public DotSeparatedSegmentsRule(Pattern segmentPattern, Field field, Violation violation) { + this.segmentPattern = segmentPattern; + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null) { + throw new DomainViolationException(compose(field, violation)); + } + String[] parts = value.split("\\.", -1); + if (parts.length == 0) { + throw new DomainViolationException(compose(field, violation)); + } + for (String p : parts) { + if (p.isEmpty() || !segmentPattern.matcher(p).matches()) { + throw new DomainViolationException(compose(field, violation), p); + } + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoConsecutiveCharRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoConsecutiveCharRule.java new file mode 100644 index 0000000..655c067 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoConsecutiveCharRule.java @@ -0,0 +1,32 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; + +public final class NoConsecutiveCharRule implements Rule { + private final char ch; + private final Field field; + private final Violation violation; + + public NoConsecutiveCharRule(char ch, Field field, Violation violation) { + this.ch = ch; + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null) { + throw new DomainViolationException(compose(field, violation)); + } + for (int i = 1; i < value.length(); i++) { + if (value.charAt(i) == ch && value.charAt(i - 1) == ch) { + throw new DomainViolationException(compose(field, violation)); + } + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoEdgeCharRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoEdgeCharRule.java new file mode 100644 index 0000000..8abba28 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/NoEdgeCharRule.java @@ -0,0 +1,30 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; + +public final class NoEdgeCharRule implements Rule { + private final char edgeChar; + private final Field field; + private final Violation violation; + + public NoEdgeCharRule(char edgeChar, Field field, Violation violation) { + this.edgeChar = edgeChar; + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null || value.isEmpty()) { + throw new DomainViolationException(compose(field, violation)); + } + if (value.charAt(0) == edgeChar || value.charAt(value.length() - 1) == edgeChar) { + throw new DomainViolationException(compose(field, violation)); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java new file mode 100644 index 0000000..a363e37 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java @@ -0,0 +1,36 @@ +// src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.Set; + +public final class ReservedPrefixRule implements Rule { + private final Set reservedPrefixesLower; + private final Field field; + private final Violation violation; + + public ReservedPrefixRule(Set reservedPrefixes, Field field, Violation violation) { + this.reservedPrefixesLower = reservedPrefixes.stream() + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null) throw new DomainViolationException(compose(field, violation)); + String lower = value.toLowerCase(Locale.ROOT); + for (String p : reservedPrefixesLower) { + if (lower.equals(p) || lower.startsWith(p + ".")) { + throw new DomainViolationException(compose(field, violation), p); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/StartsWithLetterRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/StartsWithLetterRule.java new file mode 100644 index 0000000..f6cae65 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/StartsWithLetterRule.java @@ -0,0 +1,29 @@ +package io.github.bsayli.codegen.initializr.domain.policy.rule; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; + +import io.github.bsayli.codegen.initializr.domain.error.code.Field; +import io.github.bsayli.codegen.initializr.domain.error.code.Violation; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; + +public final class StartsWithLetterRule implements Rule { + private final Field field; + private final Violation violation; + + public StartsWithLetterRule(Field field, Violation violation) { + this.field = field; + this.violation = violation; + } + + @Override + public void check(String value) { + if (value == null || value.isEmpty()) { + throw new DomainViolationException(compose(field, violation)); + } + char c = value.charAt(0); + if (c < 'a' || c > 'z') { + throw new DomainViolationException(compose(field, violation)); + } + } +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 2ae50cf..b80aed0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,4 +1,38 @@ +# ================================ +# --- GROUP ID --- +# ================================ +project.group-id.not.blank=GroupId is required +project.group-id.length=GroupId must be between {0} and {1} characters +project.group-id.segment.format=GroupId must be dot-separated segments like 'com.example' (segment: [a-z][a-z0-9]*) +# ================================ +# --- DESCRIPTION --- +# ================================ +project.description.length=Description must be between {0} and {1} characters +project.description.control.chars=Description contains invalid control characters +# ================================ +# --- PROJECT NAME --- +# ================================ project.name.not.blank=Project name is required project.name.length=Project name must be between {0} and {1} characters -project.name.invalid.chars=Only [a-z][a-z0-9-] allowed -project.name.reserved=Reserved project name: {0} \ No newline at end of file +project.name.invalid.chars=Only lowercase letters, digits and '-' are allowed +project.name.starts.with.letter=Project name must start with a letter +project.name.edge.char=Project name must not start or end with '-' +project.name.consecutive.char=Project name must not contain consecutive '-' characters +project.name.reserved=Reserved project name: {0} +# ================================ +# --- ARTIFACT ID --- +# ================================ +project.artifact-id.not.blank=ArtifactId is required +project.artifact-id.length=ArtifactId must be between {0} and {1} characters +project.artifact-id.invalid.chars=Only lowercase letters, digits and '-' are allowed +project.artifact-id.starts.with.letter=ArtifactId must start with a letter +project.artifact-id.edge.char=ArtifactId must not start or end with '-' +project.artifact-id.consecutive.char=ArtifactId must not contain consecutive '-' characters +# ================================ +# --- PACKAGE NAME --- +# ================================ +project.package-name.not.blank=Package name is required +project.package-name.length=Package name must be between {0} and {1} characters +project.package-name.segment.format=Package name must be dot-separated segments like 'com.example' (segment: [a-z][a-z0-9]*) +project.package-name.reserved.prefix=Reserved package prefixes are not allowed (java, javax, sun, com.sun) +project.identity.not.blank=Project identity requires both GroupId and ArtifactId \ No newline at end of file From 9d10c5d47a536982ac89f674aa2bd40836aa2334 Mon Sep 17 00:00:00 2001 From: bsayli Date: Mon, 22 Sep 2025 12:52:23 -0600 Subject: [PATCH 03/74] feat(domain): add PlatformTarget compatibility rules and selectors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced CompatibilityPolicy with Spring Boot ↔ Java support matrix - Implemented PlatformTargetSelector with default and fallback selection logic - Added enums for JavaVersion and SpringBootVersion (21, 25 and 3.5.6, 3.4.10 supported) - Enforced build options and platform target validation via DomainViolationException - Extended messages.properties with build-options and platform-target error keys --- README.md | 6 +- .../codegen/initializr/cli/CliRunner.java | 6 +- .../domain/model/ProjectBlueprint.java | 5 +- .../domain/model/value/ArtifactId.java | 9 --- .../domain/model/value/GroupId.java | 9 --- .../domain/model/value/PackageName.java | 9 --- .../domain/model/value/ProjectName.java | 9 --- .../model/value/identity/ArtifactId.java | 9 +++ .../domain/model/value/identity/GroupId.java | 9 +++ .../value/{ => identity}/ProjectIdentity.java | 2 +- .../{ => naming}/ProjectDescription.java | 4 +- .../model/value/naming/ProjectName.java | 9 +++ .../domain/model/value/pkg/PackageName.java | 9 +++ .../value/tech/options/BuildOptions.java | 9 +++ .../model/value/tech/options/BuildTool.java | 5 ++ .../model/value/tech/options/Framework.java | 5 ++ .../model/value/tech/options/Language.java | 5 ++ .../value/tech/platform/JavaVersion.java | 20 ++++++ .../value/tech/platform/PlatformTarget.java | 14 ++++ .../tech/platform/SpringBootVersion.java | 16 +++++ .../domain/policy/ArtifactIdPolicy.java | 51 ------------- .../domain/policy/PackageNamePolicy.java | 63 ---------------- .../policy/identity/ArtifactIdPolicy.java | 51 +++++++++++++ .../policy/{ => identity}/GroupIdPolicy.java | 2 +- .../ProjectDescriptionPolicy.java | 2 +- .../{ => naming}/ProjectNamePolicy.java | 32 ++++----- .../domain/policy/pkg/PackageNamePolicy.java | 61 ++++++++++++++++ .../policy/rule/ReservedPrefixRule.java | 41 +++++------ .../policy/tech/BuildOptionsPolicy.java | 19 +++++ .../policy/tech/CompatibilityPolicy.java | 71 +++++++++++++++++++ .../policy/tech/PlatformTargetSelector.java | 49 +++++++++++++ .../projectgeneration/model/ProjectType.java | 6 +- .../model/techstack/BuildTool.java | 6 -- .../model/techstack/Framework.java | 6 -- .../model/techstack/Language.java | 6 -- ...ProjectGeneratorRegistryConfiguration.java | 6 +- src/main/resources/messages.properties | 12 +++- .../SimpleProjectGeneratorRegistryTest.java | 18 +---- .../ProjectGenerationServiceImplTest.java | 34 +-------- 39 files changed, 436 insertions(+), 269 deletions(-) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactId.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupId.java rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/{ => identity}/ProjectIdentity.java (89%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/{ => naming}/ProjectDescription.java (55%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectName.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageName.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/JavaVersion.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTarget.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/SpringBootVersion.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/ArtifactIdPolicy.java rename src/main/java/io/github/bsayli/codegen/initializr/domain/policy/{ => identity}/GroupIdPolicy.java (96%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/policy/{ => naming}/ProjectDescriptionPolicy.java (95%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/policy/{ => naming}/ProjectNamePolicy.java (70%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/pkg/PackageNamePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/BuildTool.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Framework.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Language.java diff --git a/README.md b/README.md index 44ca577..f2353f1 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,9 @@ import java.util.List; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGenerationService; // Assume ProjectGenerationService is injected or obtained from Spring context diff --git a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java b/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java index a3241c2..f29ed0c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java @@ -1,11 +1,11 @@ package io.github.bsayli.codegen.initializr.cli; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGenerationService; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java index 18c0835..73ada73 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java @@ -1,6 +1,9 @@ package io.github.bsayli.codegen.initializr.domain.model; -import io.github.bsayli.codegen.initializr.domain.model.value.*; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; public class ProjectBlueprint { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java deleted file mode 100644 index 74458cd..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ArtifactId.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; - -import io.github.bsayli.codegen.initializr.domain.policy.ArtifactIdPolicy; - -public record ArtifactId(String value) { - public ArtifactId { - value = ArtifactIdPolicy.enforce(value); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java deleted file mode 100644 index 6dbbc6a..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/GroupId.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; - -import io.github.bsayli.codegen.initializr.domain.policy.GroupIdPolicy; - -public record GroupId(String value) { - public GroupId { - value = GroupIdPolicy.enforce(value); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java deleted file mode 100644 index b4317a2..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/PackageName.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; - -import io.github.bsayli.codegen.initializr.domain.policy.PackageNamePolicy; - -public record PackageName(String value) { - public PackageName { - value = PackageNamePolicy.enforce(value); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java deleted file mode 100644 index 9ff5e41..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectName.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; - -import io.github.bsayli.codegen.initializr.domain.policy.ProjectNamePolicy; - -public record ProjectName(String value) { - public ProjectName { - value = ProjectNamePolicy.enforce(value); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactId.java new file mode 100644 index 0000000..659b347 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactId.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.identity; + +import io.github.bsayli.codegen.initializr.domain.policy.identity.ArtifactIdPolicy; + +public record ArtifactId(String value) { + public ArtifactId { + value = ArtifactIdPolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupId.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupId.java new file mode 100644 index 0000000..3afe424 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupId.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.identity; + +import io.github.bsayli.codegen.initializr.domain.policy.identity.GroupIdPolicy; + +public record GroupId(String value) { + public GroupId { + value = GroupIdPolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java similarity index 89% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java index ecef040..d2d1c6e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectIdentity.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; +package io.github.bsayli.codegen.initializr.domain.model.value.identity; import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescription.java similarity index 55% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescription.java index bb2adee..38a44e5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/ProjectDescription.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescription.java @@ -1,6 +1,6 @@ -package io.github.bsayli.codegen.initializr.domain.model.value; +package io.github.bsayli.codegen.initializr.domain.model.value.naming; -import io.github.bsayli.codegen.initializr.domain.policy.ProjectDescriptionPolicy; +import io.github.bsayli.codegen.initializr.domain.policy.naming.ProjectDescriptionPolicy; public record ProjectDescription(String value) { public ProjectDescription { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectName.java new file mode 100644 index 0000000..d8f6adb --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectName.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.naming; + +import io.github.bsayli.codegen.initializr.domain.policy.naming.ProjectNamePolicy; + +public record ProjectName(String value) { + public ProjectName { + value = ProjectNamePolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageName.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageName.java new file mode 100644 index 0000000..94b2233 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageName.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.pkg; + +import io.github.bsayli.codegen.initializr.domain.policy.pkg.PackageNamePolicy; + +public record PackageName(String value) { + public PackageName { + value = PackageNamePolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java new file mode 100644 index 0000000..d1e83b3 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; + +import io.github.bsayli.codegen.initializr.domain.policy.tech.BuildOptionsPolicy; + +public record BuildOptions(Framework framework, BuildTool buildTool, Language language) { + public BuildOptions { + BuildOptionsPolicy.enforce(this); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java new file mode 100644 index 0000000..5761ccd --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java @@ -0,0 +1,5 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; + +public enum BuildTool { + MAVEN +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java new file mode 100644 index 0000000..5bcc494 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java @@ -0,0 +1,5 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; + +public enum Framework { + SPRING_BOOT +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java new file mode 100644 index 0000000..9954460 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java @@ -0,0 +1,5 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; + +public enum Language { + JAVA +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/JavaVersion.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/JavaVersion.java new file mode 100644 index 0000000..8b67d61 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/JavaVersion.java @@ -0,0 +1,20 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; + +public enum JavaVersion { + JAVA_21(21), + JAVA_25(25); + + private final int major; + + JavaVersion(int major) { + this.major = major; + } + + public int major() { + return major; + } + + public String asString() { + return String.valueOf(major); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTarget.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTarget.java new file mode 100644 index 0000000..1180d78 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTarget.java @@ -0,0 +1,14 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; + +public record PlatformTarget(JavaVersion java, SpringBootVersion springBoot) { + private static final ErrorCode TARGET_REQUIRED = () -> "platform.target.not.blank"; + + public PlatformTarget { + if (java == null || springBoot == null) { + throw new DomainViolationException(TARGET_REQUIRED); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/SpringBootVersion.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/SpringBootVersion.java new file mode 100644 index 0000000..87becfb --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/SpringBootVersion.java @@ -0,0 +1,16 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; + +public enum SpringBootVersion { + V3_5_6("3.5.6"), + V3_4_10("3.4.10"); + + private final String value; + + SpringBootVersion(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java deleted file mode 100644 index d0cb0ba..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ArtifactIdPolicy.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.policy; - -import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; -import static io.github.bsayli.codegen.initializr.domain.error.code.Field.ARTIFACT_ID; -import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; - -import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import io.github.bsayli.codegen.initializr.domain.policy.rule.AllowedCharsRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.NoConsecutiveCharRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.NoEdgeCharRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.StartsWithLetterRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; -import java.util.Locale; - -public final class ArtifactIdPolicy { - - private static final int MIN = 3; - private static final int MAX = 50; - - private ArtifactIdPolicy() {} - - public static String enforce(String raw) { - String n = normalize(raw); - validate(n); - return n; - } - - private static String normalize(String raw) { - if (raw == null) throw new DomainViolationException(compose(ARTIFACT_ID, NOT_BLANK)); - return raw.trim() - .replaceAll("\\s+", "-") - .replace('_', '-') - .toLowerCase(Locale.ROOT) - .replaceAll("-{2,}", "-"); - } - - private static void validate(String value) { - Rule rule = CompositeRule.of( - new NotBlankRule(ARTIFACT_ID), - new LengthBetweenRule(MIN, MAX, ARTIFACT_ID), - new AllowedCharsRule("[a-z0-9-]", ARTIFACT_ID, INVALID_CHARS), - new StartsWithLetterRule(ARTIFACT_ID, STARTS_WITH_LETTER), - new NoEdgeCharRule('-', ARTIFACT_ID, EDGE_CHAR), - new NoConsecutiveCharRule('-', ARTIFACT_ID, CONSECUTIVE_CHAR) - ); - rule.check(value); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java deleted file mode 100644 index 945451c..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/PackageNamePolicy.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.policy; - -import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; -import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PACKAGE_NAME; -import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; - -import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import io.github.bsayli.codegen.initializr.domain.policy.rule.DotSeparatedSegmentsRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.ReservedPrefixRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; -import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; -import java.util.Locale; -import java.util.Set; -import java.util.regex.Pattern; - -public final class PackageNamePolicy { - - private static final int MIN = 3; - private static final int MAX = 255; - - private static final Pattern SEGMENT = Pattern.compile("^[a-z][a-z0-9]*$"); - - private static final Pattern SEP_CHARS = Pattern.compile("[\\s_\\-]+"); - private static final Pattern MULTI_DOTS = Pattern.compile("\\.{2,}"); - private static final Pattern LEADING_DOTS = Pattern.compile("^\\.+"); - private static final Pattern TRAILING_DOTS = Pattern.compile("\\.+$"); - - private static final Set RESERVED_PREFIXES = Set.of( - "java", "javax", "sun", "com.sun" - ); - - private PackageNamePolicy() {} - - public static String enforce(String raw) { - String n = normalize(raw); - validate(n); - return n; - } - - private static String normalize(String raw) { - if (raw == null) throw new DomainViolationException(compose(PACKAGE_NAME, NOT_BLANK)); - - String s = raw.trim(); - s = SEP_CHARS.matcher(s).replaceAll("."); - s = MULTI_DOTS.matcher(s).replaceAll("."); - s = LEADING_DOTS.matcher(s).replaceAll(""); - s = TRAILING_DOTS.matcher(s).replaceAll(""); - s = s.toLowerCase(Locale.ROOT); - return s; - } - - private static void validate(String value) { - Rule rule = CompositeRule.of( - new NotBlankRule(PACKAGE_NAME), - new LengthBetweenRule(MIN, MAX, PACKAGE_NAME), - new DotSeparatedSegmentsRule(SEGMENT, PACKAGE_NAME, SEGMENT_FORMAT), - new ReservedPrefixRule(RESERVED_PREFIXES, PACKAGE_NAME, RESERVED_PREFIX) - ); - rule.check(value); - } -} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/ArtifactIdPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/ArtifactIdPolicy.java new file mode 100644 index 0000000..0bbbe78 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/ArtifactIdPolicy.java @@ -0,0 +1,51 @@ +package io.github.bsayli.codegen.initializr.domain.policy.identity; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.ARTIFACT_ID; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.AllowedCharsRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NoConsecutiveCharRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NoEdgeCharRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.StartsWithLetterRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; + +public final class ArtifactIdPolicy { + + private static final int MIN = 3; + private static final int MAX = 50; + + private ArtifactIdPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(ARTIFACT_ID, NOT_BLANK)); + return raw.trim() + .replaceAll("\\s+", "-") + .replace('_', '-') + .toLowerCase(Locale.ROOT) + .replaceAll("-{2,}", "-"); + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new NotBlankRule(ARTIFACT_ID), + new LengthBetweenRule(MIN, MAX, ARTIFACT_ID), + new AllowedCharsRule("[a-z0-9-]", ARTIFACT_ID, INVALID_CHARS), + new StartsWithLetterRule(ARTIFACT_ID, STARTS_WITH_LETTER), + new NoEdgeCharRule('-', ARTIFACT_ID, EDGE_CHAR), + new NoConsecutiveCharRule('-', ARTIFACT_ID, CONSECUTIVE_CHAR)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/GroupIdPolicy.java similarity index 96% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/GroupIdPolicy.java index e1152fe..95d01e3 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/GroupIdPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/identity/GroupIdPolicy.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.policy; +package io.github.bsayli.codegen.initializr.domain.policy.identity; import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; import static io.github.bsayli.codegen.initializr.domain.error.code.Field.GROUP_ID; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java similarity index 95% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java index 2a9e71e..4c542e2 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectDescriptionPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.policy; +package io.github.bsayli.codegen.initializr.domain.policy.naming; import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_DESCRIPTION; import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectNamePolicy.java similarity index 70% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectNamePolicy.java index efe73a9..e923570 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/ProjectNamePolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectNamePolicy.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.policy; +package io.github.bsayli.codegen.initializr.domain.policy.naming; import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_NAME; @@ -21,13 +21,13 @@ public final class ProjectNamePolicy { private static final Set RESERVED_BASE = Set.of("con", "prn", "aux", "nul"); private static final Set RESERVED_NAMES = - Stream.concat( - RESERVED_BASE.stream(), - Stream.concat( - IntStream.rangeClosed(1, 9).mapToObj(i -> "com" + i), - IntStream.rangeClosed(1, 9).mapToObj(i -> "lpt" + i))) - .map(s -> s.toLowerCase(Locale.ROOT)) - .collect(Collectors.toUnmodifiableSet()); + Stream.concat( + RESERVED_BASE.stream(), + Stream.concat( + IntStream.rangeClosed(1, 9).mapToObj(i -> "com" + i), + IntStream.rangeClosed(1, 9).mapToObj(i -> "lpt" + i))) + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); private ProjectNamePolicy() {} @@ -40,22 +40,22 @@ public static String enforce(String raw) { private static String normalize(String raw) { if (raw == null) throw new DomainViolationException(compose(PROJECT_NAME, NOT_BLANK)); return raw.trim() - .replaceAll("\\s+", "-") - .replace('_', '-') - .toLowerCase(Locale.ROOT) - .replaceAll("-{2,}", "-"); + .replaceAll("\\s+", "-") + .replace('_', '-') + .toLowerCase(Locale.ROOT) + .replaceAll("-{2,}", "-"); } private static void validate(String value) { - Rule rule = CompositeRule.of( + Rule rule = + CompositeRule.of( new NotBlankRule(PROJECT_NAME), new LengthBetweenRule(MIN, MAX, PROJECT_NAME), new AllowedCharsRule("[a-z0-9-]", PROJECT_NAME, INVALID_CHARS), new StartsWithLetterRule(PROJECT_NAME, STARTS_WITH_LETTER), new NoEdgeCharRule('-', PROJECT_NAME, EDGE_CHAR), new NoConsecutiveCharRule('-', PROJECT_NAME, CONSECUTIVE_CHAR), - new ReservedNamesRule(RESERVED_NAMES, PROJECT_NAME) - ); + new ReservedNamesRule(RESERVED_NAMES, PROJECT_NAME)); rule.check(value); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/pkg/PackageNamePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/pkg/PackageNamePolicy.java new file mode 100644 index 0000000..d86b319 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/pkg/PackageNamePolicy.java @@ -0,0 +1,61 @@ +package io.github.bsayli.codegen.initializr.domain.policy.pkg; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PACKAGE_NAME; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.*; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.DotSeparatedSegmentsRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.NotBlankRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.ReservedPrefixRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; + +public final class PackageNamePolicy { + + private static final int MIN = 3; + private static final int MAX = 255; + + private static final Pattern SEGMENT = Pattern.compile("^[a-z][a-z0-9]*$"); + + private static final Pattern SEP_CHARS = Pattern.compile("[\\s_\\-]+"); + private static final Pattern MULTI_DOTS = Pattern.compile("\\.{2,}"); + private static final Pattern LEADING_DOTS = Pattern.compile("^\\.+"); + private static final Pattern TRAILING_DOTS = Pattern.compile("\\.+$"); + + private static final Set RESERVED_PREFIXES = Set.of("java", "javax", "sun", "com.sun"); + + private PackageNamePolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null) throw new DomainViolationException(compose(PACKAGE_NAME, NOT_BLANK)); + + String s = raw.trim(); + s = SEP_CHARS.matcher(s).replaceAll("."); + s = MULTI_DOTS.matcher(s).replaceAll("."); + s = LEADING_DOTS.matcher(s).replaceAll(""); + s = TRAILING_DOTS.matcher(s).replaceAll(""); + s = s.toLowerCase(Locale.ROOT); + return s; + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new NotBlankRule(PACKAGE_NAME), + new LengthBetweenRule(MIN, MAX, PACKAGE_NAME), + new DotSeparatedSegmentsRule(SEGMENT, PACKAGE_NAME, SEGMENT_FORMAT), + new ReservedPrefixRule(RESERVED_PREFIXES, PACKAGE_NAME, RESERVED_PREFIX)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java index a363e37..086e731 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java @@ -11,26 +11,27 @@ import java.util.Set; public final class ReservedPrefixRule implements Rule { - private final Set reservedPrefixesLower; - private final Field field; - private final Violation violation; + private final Set reservedPrefixesLower; + private final Field field; + private final Violation violation; - public ReservedPrefixRule(Set reservedPrefixes, Field field, Violation violation) { - this.reservedPrefixesLower = reservedPrefixes.stream() - .map(s -> s.toLowerCase(Locale.ROOT)) - .collect(java.util.stream.Collectors.toUnmodifiableSet()); - this.field = field; - this.violation = violation; - } + public ReservedPrefixRule(Set reservedPrefixes, Field field, Violation violation) { + this.reservedPrefixesLower = + reservedPrefixes.stream() + .map(s -> s.toLowerCase(Locale.ROOT)) + .collect(java.util.stream.Collectors.toUnmodifiableSet()); + this.field = field; + this.violation = violation; + } - @Override - public void check(String value) { - if (value == null) throw new DomainViolationException(compose(field, violation)); - String lower = value.toLowerCase(Locale.ROOT); - for (String p : reservedPrefixesLower) { - if (lower.equals(p) || lower.startsWith(p + ".")) { - throw new DomainViolationException(compose(field, violation), p); - } - } + @Override + public void check(String value) { + if (value == null) throw new DomainViolationException(compose(field, violation)); + String lower = value.toLowerCase(Locale.ROOT); + for (String p : reservedPrefixesLower) { + if (lower.equals(p) || lower.startsWith(p + ".")) { + throw new DomainViolationException(compose(field, violation), p); + } } -} \ No newline at end of file + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java new file mode 100644 index 0000000..50b1a49 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java @@ -0,0 +1,19 @@ +package io.github.bsayli.codegen.initializr.domain.policy.tech; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; + +public final class BuildOptionsPolicy { + + private BuildOptionsPolicy() {} + + public static BuildOptions enforce(BuildOptions options) { + if (options == null + || options.framework() == null + || options.buildTool() == null + || options.language() == null) { + throw new DomainViolationException(() -> "project.build-options.not.blank"); + } + return options; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java new file mode 100644 index 0000000..f4e47ea --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java @@ -0,0 +1,71 @@ +package io.github.bsayli.codegen.initializr.domain.policy.tech; + +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_21; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_25; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_4_10; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_5_6; +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class CompatibilityPolicy { + + private static final ErrorCode TARGET_MISSING = () -> "platform.target.missing"; + private static final ErrorCode OPTIONS_UNSUPPORTED = () -> "platform.target.unsupported.options"; + private static final ErrorCode TARGET_INCOMPATIBLE = () -> "platform.target.incompatible"; + + private static final Map> SPRINGBOOT_JAVA_SUPPORT = Map.ofEntries( + entry(V3_5_6, EnumSet.of(JAVA_21, JAVA_25)), + entry(V3_4_10, EnumSet.of(JAVA_21)) + ); + + private CompatibilityPolicy() {} + + public static void ensureCompatible(BuildOptions options, PlatformTarget target) { + if (options == null || target == null) { + throw new DomainViolationException(TARGET_MISSING); + } + + if (options.framework() != Framework.SPRING_BOOT + || options.language() != Language.JAVA + || options.buildTool() != BuildTool.MAVEN) { + throw new DomainViolationException( + OPTIONS_UNSUPPORTED, options.framework(), options.language(), options.buildTool() + ); + } + + var allowed = SPRINGBOOT_JAVA_SUPPORT.getOrDefault(target.springBoot(), Set.of()); + if (!allowed.contains(target.java())) { + throw new DomainViolationException( + TARGET_INCOMPATIBLE, target.springBoot().value(), target.java().asString() + ); + } + } + + public static Set allowedJavaFor(SpringBootVersion boot) { + return SPRINGBOOT_JAVA_SUPPORT.getOrDefault(boot, Set.of()); + } + + public static List allSupportedTargets() { + List list = new ArrayList<>(); + for (var e : SPRINGBOOT_JAVA_SUPPORT.entrySet()) { + for (var j : e.getValue()) { + list.add(new PlatformTarget(j, e.getKey())); + } + } + return List.copyOf(list); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java new file mode 100644 index 0000000..bb326b7 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java @@ -0,0 +1,49 @@ +// domain/policy/tech/PlatformTargetSelector.java +package io.github.bsayli.codegen.initializr.domain.policy.tech; + +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_21; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_25; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_4_10; +import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_5_6; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import java.util.Comparator; +import java.util.List; + +public final class PlatformTargetSelector { + + private static final List BOOT_PRIORITY = List.of(V3_5_6, V3_4_10); + private static final List JAVA_PRIORITY = List.of(JAVA_21, JAVA_25); + private PlatformTargetSelector() {} + + @SuppressWarnings("unused") + public static PlatformTarget selectDefaultFor(BuildOptions options) { + var candidates = CompatibilityPolicy.allSupportedTargets(); + return candidates.stream() + .min(Comparator + .comparing((PlatformTarget t) -> BOOT_PRIORITY.indexOf(t.springBoot())) + .thenComparing(t -> JAVA_PRIORITY.indexOf(t.java()))) + .orElse(new PlatformTarget(JAVA_21, V3_5_6)); + } + + @SuppressWarnings("unused") + public static PlatformTarget selectOrDefault(BuildOptions options, + JavaVersion preferredJava, + SpringBootVersion preferredBoot) { + var requested = new PlatformTarget(preferredJava, preferredBoot); + try { + CompatibilityPolicy.ensureCompatible(options, requested); + return requested; + } catch (RuntimeException ignored) { + return selectDefaultFor(options); + } + } + + @SuppressWarnings("unused") + public static List supportedTargetsFor( BuildOptions options) { + return CompatibilityPolicy.allSupportedTargets(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java index b95ddfa..34ea810 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.projectgeneration.model; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; public record ProjectType(Framework framework, BuildTool buildTool, Language language) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/BuildTool.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/BuildTool.java deleted file mode 100644 index f0570a0..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/BuildTool.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.techstack; - -public enum BuildTool { - MAVEN, - GRADLE -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Framework.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Framework.java deleted file mode 100644 index def9f0b..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Framework.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.techstack; - -public enum Framework { - SPRING_BOOT, - QUARKUS -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Language.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Language.java deleted file mode 100644 index bf619c3..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/techstack/Language.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.techstack; - -public enum Language { - JAVA, - KOTLIN -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java index aeea919..a8e6a4a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java @@ -1,10 +1,10 @@ package io.github.bsayli.codegen.initializr.projectgeneration.registry.configuration; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; import java.util.HashMap; import java.util.Map; import org.springframework.context.annotation.Bean; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index b80aed0..080755a 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -35,4 +35,14 @@ project.package-name.not.blank=Package name is required project.package-name.length=Package name must be between {0} and {1} characters project.package-name.segment.format=Package name must be dot-separated segments like 'com.example' (segment: [a-z][a-z0-9]*) project.package-name.reserved.prefix=Reserved package prefixes are not allowed (java, javax, sun, com.sun) -project.identity.not.blank=Project identity requires both GroupId and ArtifactId \ No newline at end of file +# ================================ +# --- PROJECT IDENTITY --- +# ================================ +project.identity.not.blank=Project identity requires both GroupId and ArtifactId +# ================================ +# --- BUILD OPTIONS --- +# ================================ +project.build-options.not.blank=Build options require Framework, Build Tool, and Language +platform.target.missing=Platform target and build options must be provided +platform.target.unsupported.options=Unsupported options: framework={0}, language={1}, buildTool={2} +platform.target.incompatible=Selected platform is incompatible (springBoot={0}, java={1}) \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java index 5fa72a7..e5ea5cc 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java @@ -1,15 +1,14 @@ package io.github.bsayli.codegen.initializr.projectgeneration.registry; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.generator.springboot.maven.SpringBootMavenJavaProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,15 +33,4 @@ void testGetProjectGenerator_ExistingProjectType_ReturnsGenerator() { assertSame(SpringBootMavenJavaProjectGenerator.class, projectGenerator.getClass()); } - - @Test - void testGetProjectGenerator_NonExistingProjectType_ReturnsEmptyOptional() { - ProjectType unsupportedProjectType = - new ProjectType(Framework.QUARKUS, BuildTool.MAVEN, Language.JAVA); - - Optional generatorOptional = - registry.getProjectGenerator(unsupportedProjectType); - - assertFalse(generatorOptional.isPresent()); - } } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java index ada2506..d178672 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java @@ -1,17 +1,14 @@ package io.github.bsayli.codegen.initializr.projectgeneration.service; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.BuildTool; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Framework; -import io.github.bsayli.codegen.initializr.projectgeneration.model.techstack.Language; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -21,7 +18,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; @@ -103,30 +99,6 @@ void testGenerateProject_SupportedProjectType_GeneratesProjectAndVerifiesContent "Archived project file does not contain " + pomFileName + " file"); } - @Test - void testGenerateProject_UnsupportedProjectType_ThrowsException() throws IOException { - ProjectType quarkusMavenJavaProjectType = - new ProjectType(Framework.QUARKUS, BuildTool.MAVEN, Language.JAVA); - - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .groupId("com.codegen") - .artifactId("codegen-demo") - .name("codegen-demo") - .description("Codegen Demo Project") - .packageName("com.codegen.demo") - .dependencies(Collections.emptyList()) - .build(); - - try { - archivedProjectPath = - projectGenerationService.generateProject(quarkusMavenJavaProjectType, projectMetadata); - fail("Expected exception for unsupported project type"); - } catch (IllegalArgumentException e) { - assertEquals("Unsupported project type: " + quarkusMavenJavaProjectType, e.getMessage()); - } - } - @AfterEach void cleanup() throws IOException { if (archivedProjectPath != null && archivedProjectPath.toFile().exists()) { From c832ed656ee5a89333316bbd94a9d07d24b349b0 Mon Sep 17 00:00:00 2001 From: bsayli Date: Mon, 22 Sep 2025 13:15:00 -0600 Subject: [PATCH 04/74] Add ProjectBlueprintFactory with VO integration and compatibility checks --- .../factory/ProjectBlueprintFactory.java | 89 +++++++++++++++++++ .../domain/model/ProjectBlueprint.java | 28 ++++-- .../tech/platform/PlatformTargetFactory.java | 23 +++++ .../policy/tech/CompatibilityPolicy.java | 71 +++++++-------- .../policy/tech/PlatformTargetSelector.java | 58 ++++++------ src/main/resources/messages.properties | 4 + 6 files changed, 201 insertions(+), 72 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java new file mode 100644 index 0000000..fdafb6f --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java @@ -0,0 +1,89 @@ +package io.github.bsayli.codegen.initializr.domain.factory; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; +import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; +import java.util.Objects; + +public final class ProjectBlueprintFactory { + + private ProjectBlueprintFactory() {} + + public static ProjectBlueprint create( + ProjectIdentity identity, + ProjectName name, + ProjectDescription description, + PackageName packageName, + BuildOptions buildOptions, + PlatformTarget platformTarget) { + Objects.requireNonNull(identity); + Objects.requireNonNull(name); + Objects.requireNonNull(description); + Objects.requireNonNull(packageName); + Objects.requireNonNull(buildOptions); + Objects.requireNonNull(platformTarget); + + CompatibilityPolicy.ensureCompatible(buildOptions, platformTarget); + + return new ProjectBlueprint( + identity, name, description, packageName, buildOptions, platformTarget); + } + + public static ProjectBlueprint createWithAutoTarget( + ProjectIdentity identity, + ProjectName name, + ProjectDescription description, + PackageName packageName, + BuildOptions buildOptions) { + Objects.requireNonNull(buildOptions); + PlatformTarget target = PlatformTargetSelector.selectDefaultFor(buildOptions); + return create(identity, name, description, packageName, buildOptions, target); + } + + public static ProjectBlueprint createFromRaw( + String groupId, + String artifactId, + String projectName, + String projectDescription, + String packageName, + BuildOptions buildOptions, + JavaVersion preferredJava, + SpringBootVersion preferredBoot) { + ProjectIdentity identity = + new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); + ProjectName name = new ProjectName(projectName); + ProjectDescription description = new ProjectDescription(projectDescription); + PackageName pkg = new PackageName(packageName); + + PlatformTarget target = + PlatformTargetSelector.selectOrDefault(buildOptions, preferredJava, preferredBoot); + + return create(identity, name, description, pkg, buildOptions, target); + } + + public static ProjectBlueprint createFromRawWithAutoTarget( + String groupId, + String artifactId, + String projectName, + String projectDescription, + String packageName, + BuildOptions buildOptions) { + ProjectIdentity identity = + new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); + ProjectName name = new ProjectName(projectName); + ProjectDescription description = new ProjectDescription(projectDescription); + PackageName pkg = new PackageName(packageName); + + return createWithAutoTarget(identity, name, description, pkg, buildOptions); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java index 73ada73..4c1daeb 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java @@ -4,23 +4,35 @@ import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; public class ProjectBlueprint { + private final ProjectIdentity identity; private final ProjectName name; private final ProjectDescription description; - private final ProjectIdentity projectIdentity; private final PackageName packageName; + private final BuildOptions buildOptions; + private final PlatformTarget platformTarget; public ProjectBlueprint( + ProjectIdentity identity, ProjectName name, ProjectDescription description, - ProjectIdentity projectIdentity, - PackageName packageName) { + PackageName packageName, + BuildOptions buildOptions, + PlatformTarget platformTarget) { + this.identity = identity; this.name = name; this.description = description; - this.projectIdentity = projectIdentity; this.packageName = packageName; + this.buildOptions = buildOptions; + this.platformTarget = platformTarget; + } + + public ProjectIdentity getIdentity() { + return identity; } public ProjectName getName() { @@ -35,7 +47,11 @@ public PackageName getPackageName() { return packageName; } - public ProjectIdentity getProjectIdentity() { - return projectIdentity; + public BuildOptions getBuildOptions() { + return buildOptions; + } + + public PlatformTarget getPlatformTarget() { + return platformTarget; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java new file mode 100644 index 0000000..4bd0d93 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java @@ -0,0 +1,23 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; +import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; + +public final class PlatformTargetFactory { + private PlatformTargetFactory() {} + + public static PlatformTarget of( + BuildOptions options, JavaVersion preferredJava, SpringBootVersion preferredBoot) { + PlatformTarget t = + PlatformTargetSelector.selectOrDefault(options, preferredJava, preferredBoot); + CompatibilityPolicy.ensureCompatible(options, t); + return t; + } + + public static PlatformTarget defaultFor(BuildOptions options) { + PlatformTarget t = PlatformTargetSelector.selectDefaultFor(options); + CompatibilityPolicy.ensureCompatible(options, t); + return t; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java index f4e47ea..efdc844 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java @@ -23,49 +23,46 @@ public final class CompatibilityPolicy { - private static final ErrorCode TARGET_MISSING = () -> "platform.target.missing"; - private static final ErrorCode OPTIONS_UNSUPPORTED = () -> "platform.target.unsupported.options"; - private static final ErrorCode TARGET_INCOMPATIBLE = () -> "platform.target.incompatible"; + private static final ErrorCode TARGET_MISSING = () -> "platform.target.missing"; + private static final ErrorCode OPTIONS_UNSUPPORTED = () -> "platform.target.unsupported.options"; + private static final ErrorCode TARGET_INCOMPATIBLE = () -> "platform.target.incompatible"; - private static final Map> SPRINGBOOT_JAVA_SUPPORT = Map.ofEntries( - entry(V3_5_6, EnumSet.of(JAVA_21, JAVA_25)), - entry(V3_4_10, EnumSet.of(JAVA_21)) - ); + private static final Map> SPRINGBOOT_JAVA_SUPPORT = + Map.ofEntries( + entry(V3_5_6, EnumSet.of(JAVA_21, JAVA_25)), entry(V3_4_10, EnumSet.of(JAVA_21))); - private CompatibilityPolicy() {} + private CompatibilityPolicy() {} - public static void ensureCompatible(BuildOptions options, PlatformTarget target) { - if (options == null || target == null) { - throw new DomainViolationException(TARGET_MISSING); - } - - if (options.framework() != Framework.SPRING_BOOT - || options.language() != Language.JAVA - || options.buildTool() != BuildTool.MAVEN) { - throw new DomainViolationException( - OPTIONS_UNSUPPORTED, options.framework(), options.language(), options.buildTool() - ); - } + public static void ensureCompatible(BuildOptions options, PlatformTarget target) { + if (options == null || target == null) { + throw new DomainViolationException(TARGET_MISSING); + } - var allowed = SPRINGBOOT_JAVA_SUPPORT.getOrDefault(target.springBoot(), Set.of()); - if (!allowed.contains(target.java())) { - throw new DomainViolationException( - TARGET_INCOMPATIBLE, target.springBoot().value(), target.java().asString() - ); - } + if (options.framework() != Framework.SPRING_BOOT + || options.language() != Language.JAVA + || options.buildTool() != BuildTool.MAVEN) { + throw new DomainViolationException( + OPTIONS_UNSUPPORTED, options.framework(), options.language(), options.buildTool()); } - public static Set allowedJavaFor(SpringBootVersion boot) { - return SPRINGBOOT_JAVA_SUPPORT.getOrDefault(boot, Set.of()); + var allowed = SPRINGBOOT_JAVA_SUPPORT.getOrDefault(target.springBoot(), Set.of()); + if (!allowed.contains(target.java())) { + throw new DomainViolationException( + TARGET_INCOMPATIBLE, target.springBoot().value(), target.java().asString()); } + } + + public static Set allowedJavaFor(SpringBootVersion boot) { + return SPRINGBOOT_JAVA_SUPPORT.getOrDefault(boot, Set.of()); + } - public static List allSupportedTargets() { - List list = new ArrayList<>(); - for (var e : SPRINGBOOT_JAVA_SUPPORT.entrySet()) { - for (var j : e.getValue()) { - list.add(new PlatformTarget(j, e.getKey())); - } - } - return List.copyOf(list); + public static List allSupportedTargets() { + List list = new ArrayList<>(); + for (var e : SPRINGBOOT_JAVA_SUPPORT.entrySet()) { + for (var j : e.getValue()) { + list.add(new PlatformTarget(j, e.getKey())); + } } -} \ No newline at end of file + return List.copyOf(list); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java index bb326b7..932004c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java @@ -15,35 +15,35 @@ public final class PlatformTargetSelector { - private static final List BOOT_PRIORITY = List.of(V3_5_6, V3_4_10); - private static final List JAVA_PRIORITY = List.of(JAVA_21, JAVA_25); - private PlatformTargetSelector() {} - - @SuppressWarnings("unused") - public static PlatformTarget selectDefaultFor(BuildOptions options) { - var candidates = CompatibilityPolicy.allSupportedTargets(); - return candidates.stream() - .min(Comparator - .comparing((PlatformTarget t) -> BOOT_PRIORITY.indexOf(t.springBoot())) - .thenComparing(t -> JAVA_PRIORITY.indexOf(t.java()))) - .orElse(new PlatformTarget(JAVA_21, V3_5_6)); - } + private static final List BOOT_PRIORITY = List.of(V3_5_6, V3_4_10); + private static final List JAVA_PRIORITY = List.of(JAVA_21, JAVA_25); - @SuppressWarnings("unused") - public static PlatformTarget selectOrDefault(BuildOptions options, - JavaVersion preferredJava, - SpringBootVersion preferredBoot) { - var requested = new PlatformTarget(preferredJava, preferredBoot); - try { - CompatibilityPolicy.ensureCompatible(options, requested); - return requested; - } catch (RuntimeException ignored) { - return selectDefaultFor(options); - } - } + private PlatformTargetSelector() {} - @SuppressWarnings("unused") - public static List supportedTargetsFor( BuildOptions options) { - return CompatibilityPolicy.allSupportedTargets(); + @SuppressWarnings("unused") + public static PlatformTarget selectDefaultFor(BuildOptions options) { + var candidates = CompatibilityPolicy.allSupportedTargets(); + return candidates.stream() + .min( + Comparator.comparing((PlatformTarget t) -> BOOT_PRIORITY.indexOf(t.springBoot())) + .thenComparing(t -> JAVA_PRIORITY.indexOf(t.java()))) + .orElse(new PlatformTarget(JAVA_21, V3_5_6)); + } + + @SuppressWarnings("unused") + public static PlatformTarget selectOrDefault( + BuildOptions options, JavaVersion preferredJava, SpringBootVersion preferredBoot) { + var requested = new PlatformTarget(preferredJava, preferredBoot); + try { + CompatibilityPolicy.ensureCompatible(options, requested); + return requested; + } catch (RuntimeException ignored) { + return selectDefaultFor(options); } -} \ No newline at end of file + } + + @SuppressWarnings("unused") + public static List supportedTargetsFor(BuildOptions options) { + return CompatibilityPolicy.allSupportedTargets(); + } +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 080755a..c19946a 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -43,6 +43,10 @@ project.identity.not.blank=Project identity requires both GroupId and ArtifactId # --- BUILD OPTIONS --- # ================================ project.build-options.not.blank=Build options require Framework, Build Tool, and Language +# ================================ +# --- PLATFORM TARGET --- +# ================================ +platform.target.not.blank=Platform target requires both Java version and Spring Boot version platform.target.missing=Platform target and build options must be provided platform.target.unsupported.options=Unsupported options: framework={0}, language={1}, buildTool={2} platform.target.incompatible=Selected platform is incompatible (springBoot={0}, java={1}) \ No newline at end of file From 2312c5219a7f65c435533cdaeed869567f58cffc Mon Sep 17 00:00:00 2001 From: bsayli Date: Wed, 24 Sep 2025 13:32:04 -0600 Subject: [PATCH 05/74] feat(initializr): add application layer mapping and dependency handling with unit tests - Implemented ProjectBlueprintMapper in application.usecase.createproject to map CreateProjectCommand into domain ProjectBlueprint - Added dependency mapping with scope/version normalization - Introduced CreateProjectService orchestrating project root preparation - Added unit tests: * ProjectBlueprintMapperTest (dependency mapping, default platform target) * CreateProjectServiceTest (blueprint + filesystem interaction) This commit establishes the first end-to-end flow from command to domain blueprint with test coverage. --- README.md | 6 +- .../createproject/CreateProjectCommand.java | 19 +++ .../createproject/CreateProjectResult.java | 6 + .../createproject/CreateProjectService.java | 28 ++++ .../createproject/CreateProjectUseCase.java | 5 + .../createproject/DependencyInput.java | 3 + .../createproject/ProjectBlueprintMapper.java | 55 ++++++++ .../codegen/initializr/cli/CliRunner.java | 6 +- .../initializr/domain/error/code/Field.java | 7 +- .../factory/ProjectBlueprintFactory.java | 125 +++++++++++++----- .../domain/model/ProjectBlueprint.java | 12 +- .../model/value/dependency/Dependencies.java | 24 ++++ .../model/value/dependency/Dependency.java | 20 +++ .../dependency/DependencyCoordinates.java | 17 +++ .../value/dependency/DependencyFactory.java | 39 ++++++ .../value/dependency/DependencyScope.java | 25 ++++ .../value/dependency/DependencyVersion.java | 9 ++ .../model/value/identity/ProjectIdentity.java | 7 - .../tech/platform/PlatformTargetFactory.java | 2 +- .../tech/{options => stack}/BuildOptions.java | 4 +- .../tech/{options => stack}/BuildTool.java | 2 +- .../tech/{options => stack}/Framework.java | 2 +- .../tech/{options => stack}/Language.java | 2 +- .../policy/dependency/DependenciesPolicy.java | 51 +++++++ .../dependency/DependencyVersionPolicy.java | 44 ++++++ .../policy/tech/BuildOptionsPolicy.java | 11 +- .../policy/tech/CompatibilityPolicy.java | 8 +- .../policy/tech/PlatformTargetSelector.java | 2 +- .../port/out/ProjectFileSystemPort.java | 18 +++ .../projectgeneration/model/ProjectType.java | 6 +- ...ProjectGeneratorRegistryConfiguration.java | 6 +- src/main/resources/messages.properties | 11 +- .../CreateProjectServiceTest.java | 78 +++++++++++ .../ProjectBlueprintMapperTest.java | 98 ++++++++++++++ .../SimpleProjectGeneratorRegistryTest.java | 6 +- .../ProjectGenerationServiceImplTest.java | 6 +- 36 files changed, 694 insertions(+), 76 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependency.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinates.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyScope.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersion.java rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/{options => stack}/BuildOptions.java (76%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/{options => stack}/BuildTool.java (88%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/{options => stack}/Framework.java (88%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/{options => stack}/Language.java (88%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependenciesPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependencyVersionPolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java diff --git a/README.md b/README.md index f2353f1..8a5dd69 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,9 @@ import java.util.List; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGenerationService; // Assume ProjectGenerationService is injected or obtained from Spring context diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java new file mode 100644 index 0000000..b7c17de --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java @@ -0,0 +1,19 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import java.nio.file.Path; +import java.util.List; + +public record CreateProjectCommand( + String groupId, + String artifactId, + String projectName, + String projectDescription, + String packageName, + BuildOptions buildOptions, + JavaVersion preferredJava, + SpringBootVersion preferredBoot, + List dependencies, + Path targetDirectory) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java new file mode 100644 index 0000000..23d141a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java @@ -0,0 +1,6 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import java.nio.file.Path; + +public record CreateProjectResult(ProjectBlueprint blueprint, Path projectRoot) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java new file mode 100644 index 0000000..91abeb7 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -0,0 +1,28 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort.OnExistsPolicy; +import java.nio.file.Path; + +public class CreateProjectService implements CreateProjectUseCase { + + private final ProjectBlueprintMapper mapper; + private final ProjectFileSystemPort fs; + + public CreateProjectService(ProjectBlueprintMapper mapper, ProjectFileSystemPort fs) { + this.mapper = mapper; + this.fs = fs; + } + + @Override + public CreateProjectResult execute(CreateProjectCommand command) { + ProjectBlueprint blueprint = mapper.toDomain(command); + + String artifactId = blueprint.getIdentity().artifactId().value(); + Path targetDirectory = command.targetDirectory(); + Path projectRoot = fs.prepareProjectRoot(targetDirectory, artifactId, OnExistsPolicy.FAIL_IF_EXISTS); + + return new CreateProjectResult(blueprint, projectRoot); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java new file mode 100644 index 0000000..1177542 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java @@ -0,0 +1,5 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +public interface CreateProjectUseCase { + CreateProjectResult execute(CreateProjectCommand command); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java new file mode 100644 index 0000000..9e06939 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java @@ -0,0 +1,3 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +public record DependencyInput(String groupId, String artifactId, String version, String scope) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java new file mode 100644 index 0000000..cdb6e93 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java @@ -0,0 +1,55 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import io.github.bsayli.codegen.initializr.domain.factory.ProjectBlueprintFactory; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyScope; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import java.util.List; + +public class ProjectBlueprintMapper { + + public ProjectBlueprint toDomain(CreateProjectCommand c) { + List deps = + c.dependencies().stream() + .map( + d -> + new Dependency( + new DependencyCoordinates( + new GroupId(d.groupId()), new ArtifactId(d.artifactId())), + (d.version() == null || d.version().isBlank()) + ? null + : new DependencyVersion(d.version()), + (d.scope() == null || d.scope().isBlank()) + ? null + : DependencyScope.valueOf(d.scope().trim().toUpperCase()))) + .toList(); + + var depsWrapper = Dependencies.of(deps); + + if (c.preferredJava() != null && c.preferredBoot() != null) { + return ProjectBlueprintFactory.fromPrimitives( + c.groupId(), + c.artifactId(), + c.projectName(), + c.projectDescription(), + c.packageName(), + c.buildOptions(), + c.preferredJava(), + c.preferredBoot(), + depsWrapper); + } + return ProjectBlueprintFactory.fromPrimitivesWithAutoTarget( + c.groupId(), + c.artifactId(), + c.projectName(), + c.projectDescription(), + c.packageName(), + c.buildOptions(), + depsWrapper); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java b/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java index f29ed0c..1afb601 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java @@ -1,8 +1,8 @@ package io.github.bsayli.codegen.initializr.cli; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java index 133b11f..6db5dc6 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/code/Field.java @@ -5,7 +5,8 @@ public enum Field implements ErrorCode { PROJECT_DESCRIPTION(project("description")), GROUP_ID(project("group-id")), ARTIFACT_ID(project("artifact-id")), - PACKAGE_NAME(project("package-id")); + PACKAGE_NAME(project("package-name")), + DEPENDENCY_VERSION(dependency("version")); private final String key; @@ -17,6 +18,10 @@ private static String project(String suffix) { return "project." + suffix; } + private static String dependency(String suffix) { + return "dependency." + suffix; + } + @Override public String key() { return key; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java index fdafb6f..435fda9 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java @@ -1,56 +1,109 @@ package io.github.bsayli.codegen.initializr.domain.factory; +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PACKAGE_NAME; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_NAME; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.NOT_BLANK; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; -import java.util.Objects; +import java.util.Arrays; +import java.util.List; public final class ProjectBlueprintFactory { + private static final ErrorCode IDENTITY_REQUIRED = () -> "project.identity.not.blank"; + private static final ErrorCode BUILD_OPTIONS_REQUIRED = () -> "project.build-options.not.blank"; + private static final ErrorCode TARGET_REQUIRED = () -> "platform.target.not.blank"; + private static final ErrorCode DEPENDENCIES_REQUIRED = () -> "dependency.list.not.blank"; + private ProjectBlueprintFactory() {} - public static ProjectBlueprint create( + public static ProjectBlueprint of( ProjectIdentity identity, ProjectName name, ProjectDescription description, PackageName packageName, BuildOptions buildOptions, - PlatformTarget platformTarget) { - Objects.requireNonNull(identity); - Objects.requireNonNull(name); - Objects.requireNonNull(description); - Objects.requireNonNull(packageName); - Objects.requireNonNull(buildOptions); - Objects.requireNonNull(platformTarget); + PlatformTarget platformTarget, + Dependencies dependencies) { + ensureNotNull(identity, IDENTITY_REQUIRED); + ensureNotNull(name, compose(PROJECT_NAME, NOT_BLANK)); + ensureNotNull(packageName, compose(PACKAGE_NAME, NOT_BLANK)); + ensureNotNull(buildOptions, BUILD_OPTIONS_REQUIRED); + ensureNotNull(platformTarget, TARGET_REQUIRED); + ensureNotNull(dependencies, DEPENDENCIES_REQUIRED); CompatibilityPolicy.ensureCompatible(buildOptions, platformTarget); return new ProjectBlueprint( - identity, name, description, packageName, buildOptions, platformTarget); + identity, name, description, packageName, buildOptions, platformTarget, dependencies); + } + + public static ProjectBlueprint of( + ProjectIdentity identity, + ProjectName name, + ProjectDescription description, + PackageName packageName, + BuildOptions buildOptions, + PlatformTarget platformTarget, + List dependencies) { + return of( + identity, + name, + description, + packageName, + buildOptions, + platformTarget, + Dependencies.of(dependencies)); + } + + public static ProjectBlueprint of( + ProjectIdentity identity, + ProjectName name, + ProjectDescription description, + PackageName packageName, + BuildOptions buildOptions, + PlatformTarget platformTarget, + Dependency... deps) { + return of( + identity, + name, + description, + packageName, + buildOptions, + platformTarget, + Dependencies.of(Arrays.asList(deps))); } - public static ProjectBlueprint createWithAutoTarget( + public static ProjectBlueprint ofWithAutoTarget( ProjectIdentity identity, ProjectName name, ProjectDescription description, PackageName packageName, - BuildOptions buildOptions) { - Objects.requireNonNull(buildOptions); + BuildOptions buildOptions, + Dependencies dependencies) { + ensureNotNull(buildOptions, BUILD_OPTIONS_REQUIRED); PlatformTarget target = PlatformTargetSelector.selectDefaultFor(buildOptions); - return create(identity, name, description, packageName, buildOptions, target); + return of(identity, name, description, packageName, buildOptions, target, dependencies); } - public static ProjectBlueprint createFromRaw( + public static ProjectBlueprint fromPrimitives( String groupId, String artifactId, String projectName, @@ -58,32 +111,34 @@ public static ProjectBlueprint createFromRaw( String packageName, BuildOptions buildOptions, JavaVersion preferredJava, - SpringBootVersion preferredBoot) { - ProjectIdentity identity = - new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); - ProjectName name = new ProjectName(projectName); - ProjectDescription description = new ProjectDescription(projectDescription); - PackageName pkg = new PackageName(packageName); + SpringBootVersion preferredBoot, + Dependencies dependencies) { + var identity = new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); + var name = new ProjectName(projectName); + var description = new ProjectDescription(projectDescription); + var pkg = new PackageName(packageName); - PlatformTarget target = - PlatformTargetSelector.selectOrDefault(buildOptions, preferredJava, preferredBoot); - - return create(identity, name, description, pkg, buildOptions, target); + var target = PlatformTargetSelector.selectOrDefault(buildOptions, preferredJava, preferredBoot); + return of(identity, name, description, pkg, buildOptions, target, dependencies); } - public static ProjectBlueprint createFromRawWithAutoTarget( + public static ProjectBlueprint fromPrimitivesWithAutoTarget( String groupId, String artifactId, String projectName, String projectDescription, String packageName, - BuildOptions buildOptions) { - ProjectIdentity identity = - new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); - ProjectName name = new ProjectName(projectName); - ProjectDescription description = new ProjectDescription(projectDescription); - PackageName pkg = new PackageName(packageName); - - return createWithAutoTarget(identity, name, description, pkg, buildOptions); + BuildOptions buildOptions, + Dependencies dependencies) { + var identity = new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); + var name = new ProjectName(projectName); + var description = new ProjectDescription(projectDescription); + var pkg = new PackageName(packageName); + + return ofWithAutoTarget(identity, name, description, pkg, buildOptions, dependencies); + } + + private static void ensureNotNull(Object value, ErrorCode code) { + if (value == null) throw new DomainViolationException(code); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java index 4c1daeb..ebbdfb7 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/ProjectBlueprint.java @@ -1,11 +1,12 @@ package io.github.bsayli.codegen.initializr.domain.model; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; public class ProjectBlueprint { @@ -15,6 +16,7 @@ public class ProjectBlueprint { private final PackageName packageName; private final BuildOptions buildOptions; private final PlatformTarget platformTarget; + private final Dependencies dependencies; public ProjectBlueprint( ProjectIdentity identity, @@ -22,13 +24,15 @@ public ProjectBlueprint( ProjectDescription description, PackageName packageName, BuildOptions buildOptions, - PlatformTarget platformTarget) { + PlatformTarget platformTarget, + Dependencies dependencies) { this.identity = identity; this.name = name; this.description = description; this.packageName = packageName; this.buildOptions = buildOptions; this.platformTarget = platformTarget; + this.dependencies = dependencies; } public ProjectIdentity getIdentity() { @@ -54,4 +58,8 @@ public BuildOptions getBuildOptions() { public PlatformTarget getPlatformTarget() { return platformTarget; } + + public Dependencies getDependencies() { + return dependencies; + } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java new file mode 100644 index 0000000..35c2323 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java @@ -0,0 +1,24 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import io.github.bsayli.codegen.initializr.domain.policy.dependency.DependenciesPolicy; +import java.util.List; + +public final class Dependencies { + private final List items; + + private Dependencies(List items) { + this.items = items; + } + + public static Dependencies of(List raw) { + return new Dependencies(DependenciesPolicy.enforce(raw)); + } + + public List asList() { + return items; + } + + public boolean isEmpty() { + return items.isEmpty(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependency.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependency.java new file mode 100644 index 0000000..da7e9f8 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependency.java @@ -0,0 +1,20 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; + +public record Dependency( + DependencyCoordinates coordinates, DependencyVersion version, DependencyScope scope) { + + private static final ErrorCode COORDINATES_REQUIRED = () -> "dependency.coordinates.not.blank"; + + public Dependency { + if (coordinates == null) { + throw new DomainViolationException(COORDINATES_REQUIRED); + } + } + + public boolean isDefaultScope() { + return scope == null || scope == DependencyScope.COMPILE; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinates.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinates.java new file mode 100644 index 0000000..56d6616 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinates.java @@ -0,0 +1,17 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; + +public record DependencyCoordinates(GroupId groupId, ArtifactId artifactId) { + + private static final ErrorCode COORDINATES_REQUIRED = () -> "dependency.coordinates.not.blank"; + + public DependencyCoordinates { + if (groupId == null || artifactId == null) { + throw new DomainViolationException(COORDINATES_REQUIRED); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java new file mode 100644 index 0000000..c8f6a60 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java @@ -0,0 +1,39 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; + +public final class DependencyFactory { + + private DependencyFactory() {} + + public static DependencyCoordinates coordinates(GroupId groupId, ArtifactId artifactId) { + return new DependencyCoordinates(groupId, artifactId); + } + + public static Dependency of(DependencyCoordinates coordinates) { + return new Dependency(coordinates, null, null); + } + + public static Dependency of(DependencyCoordinates coordinates, DependencyVersion version) { + return new Dependency(coordinates, version, null); + } + + public static Dependency of( + DependencyCoordinates coordinates, DependencyVersion version, DependencyScope scope) { + return new Dependency(coordinates, version, scope); + } + + public static Dependency of(GroupId groupId, ArtifactId artifactId) { + return of(coordinates(groupId, artifactId)); + } + + public static Dependency of(GroupId groupId, ArtifactId artifactId, DependencyVersion version) { + return of(coordinates(groupId, artifactId), version); + } + + public static Dependency of( + GroupId groupId, ArtifactId artifactId, DependencyVersion version, DependencyScope scope) { + return of(coordinates(groupId, artifactId), version, scope); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyScope.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyScope.java new file mode 100644 index 0000000..9c3c451 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyScope.java @@ -0,0 +1,25 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +public enum DependencyScope { + COMPILE("compile"), + PROVIDED("provided"), + RUNTIME("runtime"), + TEST("test"), + SYSTEM("system"), + IMPORT("import"); + + private final String value; + + DependencyScope(String value) { + this.value = value; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersion.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersion.java new file mode 100644 index 0000000..2b2f93a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersion.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import io.github.bsayli.codegen.initializr.domain.policy.dependency.DependencyVersionPolicy; + +public record DependencyVersion(String value) { + public DependencyVersion { + value = DependencyVersionPolicy.enforce(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java index d2d1c6e..12203dd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentity.java @@ -2,7 +2,6 @@ import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import org.springframework.lang.NonNull; public record ProjectIdentity(GroupId groupId, ArtifactId artifactId) { @@ -13,10 +12,4 @@ public record ProjectIdentity(GroupId groupId, ArtifactId artifactId) { throw new DomainViolationException(IDENTITY_REQUIRED); } } - - @Override - @NonNull - public String toString() { - return groupId.value() + ":" + artifactId.value(); - } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java index 4bd0d93..6d71ea5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptions.java similarity index 76% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptions.java index d1e83b3..5977cf6 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildOptions.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptions.java @@ -1,9 +1,9 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; +package io.github.bsayli.codegen.initializr.domain.model.value.tech.stack; import io.github.bsayli.codegen.initializr.domain.policy.tech.BuildOptionsPolicy; public record BuildOptions(Framework framework, BuildTool buildTool, Language language) { public BuildOptions { - BuildOptionsPolicy.enforce(this); + BuildOptionsPolicy.requireNonNull(framework, buildTool, language); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildTool.java similarity index 88% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildTool.java index 5761ccd..90c950c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/BuildTool.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildTool.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; +package io.github.bsayli.codegen.initializr.domain.model.value.tech.stack; public enum BuildTool { MAVEN diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Framework.java similarity index 88% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Framework.java index 5bcc494..189e1a1 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Framework.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Framework.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; +package io.github.bsayli.codegen.initializr.domain.model.value.tech.stack; public enum Framework { SPRING_BOOT diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Language.java similarity index 88% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Language.java index 9954460..1a6b824 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/options/Language.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/Language.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.tech.options; +package io.github.bsayli.codegen.initializr.domain.model.value.tech.stack; public enum Language { JAVA diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependenciesPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependenciesPolicy.java new file mode 100644 index 0000000..741c159 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependenciesPolicy.java @@ -0,0 +1,51 @@ +package io.github.bsayli.codegen.initializr.domain.policy.dependency; + +import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import java.util.*; + +public final class DependenciesPolicy { + + private static final ErrorCode LIST_REQUIRED = () -> "dependency.list.not.blank"; + private static final ErrorCode ITEM_REQUIRED = () -> "dependency.item.not.blank"; + private static final ErrorCode DUPLICATE_COORDS = () -> "dependency.duplicate.coordinates"; + + private DependenciesPolicy() {} + + public static List enforce(List raw) { + if (raw == null) { + throw new DomainViolationException(LIST_REQUIRED); + } + if (raw.isEmpty()) { + return List.of(); + } + + Map byCoords = getDependencyMap(raw); + + List list = new ArrayList<>(byCoords.values()); + list.sort( + Comparator.comparing( + dep -> + dep.coordinates().groupId().value() + + ":" + + dep.coordinates().artifactId().value())); + return List.copyOf(list); + } + + private static Map getDependencyMap(List raw) { + Map byCoords = new LinkedHashMap<>(); + for (Dependency d : raw) { + if (d == null) { + throw new DomainViolationException(ITEM_REQUIRED); + } + var coords = d.coordinates(); + var key = coords.groupId().value() + ":" + coords.artifactId().value(); + if (byCoords.containsKey(key)) { + throw new DomainViolationException(DUPLICATE_COORDS, key); + } + byCoords.put(key, d); + } + return byCoords; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependencyVersionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependencyVersionPolicy.java new file mode 100644 index 0000000..51da245 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/dependency/DependencyVersionPolicy.java @@ -0,0 +1,44 @@ +package io.github.bsayli.codegen.initializr.domain.policy.dependency; + +import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; +import static io.github.bsayli.codegen.initializr.domain.error.code.Field.DEPENDENCY_VERSION; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.NOT_BLANK; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; +import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; +import java.util.regex.Pattern; + +public final class DependencyVersionPolicy { + + private static final Pattern ALLOWED = Pattern.compile("^[A-Za-z0-9._\\-+\\[\\](),:{}$\\s]+$"); + + private static final int MIN = 1; + private static final int MAX = 100; + + private DependencyVersionPolicy() {} + + public static String enforce(String raw) { + String n = normalize(raw); + validate(n); + return n; + } + + private static String normalize(String raw) { + if (raw == null || raw.isBlank()) { + throw new DomainViolationException(compose(DEPENDENCY_VERSION, NOT_BLANK)); + } + return raw.trim(); + } + + private static void validate(String value) { + Rule rule = + CompositeRule.of( + new LengthBetweenRule(MIN, MAX, DEPENDENCY_VERSION), + new RegexMatchRule(ALLOWED, DEPENDENCY_VERSION, INVALID_CHARS)); + rule.check(value); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java index 50b1a49..2e1af72 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/BuildOptionsPolicy.java @@ -1,7 +1,10 @@ package io.github.bsayli.codegen.initializr.domain.policy.tech; import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; public final class BuildOptionsPolicy { @@ -16,4 +19,10 @@ public static BuildOptions enforce(BuildOptions options) { } return options; } + + public static void requireNonNull(Framework framework, BuildTool buildTool, Language language) { + if (framework == null || buildTool == null || language == null) { + throw new DomainViolationException(() -> "project.build-options.not.blank"); + } + } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java index efdc844..13eb3c8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicy.java @@ -8,13 +8,13 @@ import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java index 932004c..920a91c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java @@ -6,10 +6,10 @@ import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_4_10; import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_5_6; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildOptions; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import java.util.Comparator; import java.util.List; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java new file mode 100644 index 0000000..a94d110 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java @@ -0,0 +1,18 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import java.nio.charset.Charset; +import java.nio.file.Path; + +public interface ProjectFileSystemPort { + + Path prepareProjectRoot(Path targetDirectory, String artifactId, OnExistsPolicy policy); + + void writeBytes(Path projectRoot, Path relativePath, byte[] content); + + void writeText(Path projectRoot, Path relativePath, CharSequence content, Charset charset); + + enum OnExistsPolicy { + FAIL_IF_EXISTS, + OVERWRITE + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java index 34ea810..3740414 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.projectgeneration.model; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; public record ProjectType(Framework framework, BuildTool buildTool, Language language) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java index a8e6a4a..350153c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java @@ -1,8 +1,8 @@ package io.github.bsayli.codegen.initializr.projectgeneration.registry.configuration; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import java.util.HashMap; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index c19946a..8ad3ad0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -49,4 +49,13 @@ project.build-options.not.blank=Build options require Framework, Build Tool, and platform.target.not.blank=Platform target requires both Java version and Spring Boot version platform.target.missing=Platform target and build options must be provided platform.target.unsupported.options=Unsupported options: framework={0}, language={1}, buildTool={2} -platform.target.incompatible=Selected platform is incompatible (springBoot={0}, java={1}) \ No newline at end of file +platform.target.incompatible=Selected platform is incompatible (springBoot={0}, java={1}) +# ================================ +# --- DEPENDENCY VERSION --- +# ================================ +dependency.version.not.blank=Dependency version is required +dependency.version.invalid.chars=Dependency version contains invalid characters +dependency.coordinates.not.blank=Dependency coordinates require both GroupId and ArtifactId +dependency.list.not.blank=Dependency list is required +dependency.item.not.blank=Dependency entry must not be null +dependency.duplicate.coordinates=Duplicate dependency coordinates: {0} \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java new file mode 100644 index 0000000..261b36b --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -0,0 +1,78 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort.OnExistsPolicy.FAIL_IF_EXISTS; +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("application") +@DisplayName("CreateProjectService") +class CreateProjectServiceTest { + + @Test + @DisplayName("execute() prepares project root and returns blueprint") + void creates_project_root_and_returns_blueprint() { + var mapper = new ProjectBlueprintMapper(); + var fs = new FakeFs(); + var service = new CreateProjectService(mapper, fs); + + var cmd = + new CreateProjectCommand( + "com.acme", + "demo-app", + "Demo App", + "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + JavaVersion.JAVA_21, + SpringBootVersion.V3_5_6, + List.of(), + Path.of(System.getProperty("java.io.tmpdir"))); + + var result = service.execute(cmd); + + ProjectBlueprint bp = result.blueprint(); + assertThat(bp.getIdentity().groupId().value()).isEqualTo("com.acme"); + assertThat(bp.getIdentity().artifactId().value()).isEqualTo("demo-app"); + assertThat(result.projectRoot().getFileName()).hasToString("demo-app"); + + assertThat(fs.lastPreparedRoot).isEqualTo(result.projectRoot()); + assertThat(fs.lastPolicy).isEqualTo(FAIL_IF_EXISTS); + } + + static class FakeFs implements ProjectFileSystemPort { + Path lastPreparedRoot; + OnExistsPolicy lastPolicy; + + @Override + public Path prepareProjectRoot(Path targetDirectory, String artifactId, OnExistsPolicy policy) { + this.lastPolicy = policy; + this.lastPreparedRoot = targetDirectory.resolve(artifactId); + return lastPreparedRoot; + } + + @Override + public void writeBytes(Path root, Path relative, byte[] content) { + throw new UnsupportedOperationException("FakeFs.writeBytes not expected in this test"); + } + + @Override + public void writeText( + Path root, Path relative, CharSequence content, java.nio.charset.Charset cs) { + throw new UnsupportedOperationException("FakeFs.writeText not expected in this test"); + } + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java new file mode 100644 index 0000000..174f209 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java @@ -0,0 +1,98 @@ +package io.github.bsayli.codegen.initializr.application.usecase.createproject; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyScope; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("mapper") +@DisplayName("ProjectBlueprintMapper") +class ProjectBlueprintMapperTest { + + private static ProjectBlueprint getProjectBlueprint() { + var mapper = new ProjectBlueprintMapper(); + + var inputs = List.of( + new DependencyInput("org.acme", "alpha", "", ""), + new DependencyInput("org.acme", "beta", "1.2.3", "runtime"), + new DependencyInput("org.acme", "gamma", " ", " "), + new DependencyInput("org.acme", "delta", "2.0.0-RC1", "TeSt") + ); + + var cmd = new CreateProjectCommand( + "com.acme", "demo-app", + "Demo App", "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + JavaVersion.JAVA_21, SpringBootVersion.V3_5_6, + inputs, Path.of(".") + ); + + return mapper.toDomain(cmd); + } + + @Test + @DisplayName("maps dependencies; handles blank version/scope and case-insensitive scope") + void maps_dependencies_and_handles_blank_version_and_scope() { + ProjectBlueprint bp = getProjectBlueprint(); + + assertThat(bp.getDependencies().asList()).hasSize(4); + + Map byArtifact = bp.getDependencies().asList().stream() + .collect(Collectors.toMap(d -> d.coordinates().artifactId().value(), d -> d)); + + var alpha = byArtifact.get("alpha"); + assertThat(alpha.coordinates().groupId().value()).isEqualTo("org.acme"); + assertThat(alpha.version()).isNull(); + assertThat(alpha.scope()).isNull(); + assertThat(alpha.isDefaultScope()).isTrue(); + + var beta = byArtifact.get("beta"); + assertThat(beta.version().value()).isEqualTo("1.2.3"); + assertThat(beta.scope()).isEqualTo(DependencyScope.RUNTIME); + + var gamma = byArtifact.get("gamma"); + assertThat(gamma.version()).isNull(); + assertThat(gamma.scope()).isNull(); + assertThat(gamma.isDefaultScope()).isTrue(); + + var delta = byArtifact.get("delta"); + assertThat(delta.version().value()).isEqualTo("2.0.0-RC1"); + assertThat(delta.scope()).isEqualTo(DependencyScope.TEST); + } + + @Test + @DisplayName("selects default platform target when preferences are null") + void selects_default_platform_target_when_preferences_are_null() { + var mapper = new ProjectBlueprintMapper(); + + var cmd = new CreateProjectCommand( + "com.acme", "demo-app", + "Demo App", "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + null, null, + List.of(), Path.of(".") + ); + + ProjectBlueprint bp = mapper.toDomain(cmd); + + assertThat(bp.getPlatformTarget().springBoot()).isEqualTo(SpringBootVersion.V3_5_6); + assertThat(bp.getPlatformTarget().java()).isEqualTo(JavaVersion.JAVA_21); + } +} \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java index e5ea5cc..61d4b90 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.generator.springboot.maven.SpringBootMavenJavaProjectGenerator; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java index d178672..14becc4 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.options.Language; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata; From ffbbd7c5aab549700ced1405678ba40d191c9b44 Mon Sep 17 00:00:00 2001 From: bsayli Date: Wed, 24 Sep 2025 17:22:21 -0600 Subject: [PATCH 06/74] refactor: introduce hexagonal ports for project generation - Split filesystem concerns into ProjectRootPort and ProjectWriterPort - Added ProjectArtifactsPort and StandardArtifactsAdapter - Introduced GeneratedFile with validation policy - Centralized error messages for file validation in messages.properties --- pom.xml | 34 +++++ .../adapter/out/StandardArtifactsAdapter.java | 21 +++ .../createproject/CreateProjectService.java | 48 ++++--- .../createproject/ProjectBlueprintMapper.java | 16 +-- .../bootstrap/SpringBeansConfig.java | 15 ++ .../factory/ProjectBlueprintFactory.java | 33 +---- .../tech/platform/PlatformTargetFactory.java | 23 ---- .../policy/file/GeneratedFilePolicy.java | 31 +++++ .../policy/tech/PlatformTargetSelector.java | 31 +---- .../domain/port/out/MavenPomPort.java | 8 ++ .../domain/port/out/ProjectArtifactsPort.java | 8 ++ .../port/out/ProjectFileSystemPort.java | 18 --- .../port/out/ProjectRootExistencePolicy.java | 6 + .../domain/port/out/ProjectRootPort.java | 8 ++ .../domain/port/out/ProjectWriterPort.java | 26 ++++ .../port/out/artifact/GeneratedFile.java | 52 +++++++ src/main/resources/messages.properties | 10 +- .../CreateProjectServiceTest.java | 56 ++++++-- .../ProjectBlueprintMapperTest.java | 129 ++++++++---------- 19 files changed, 357 insertions(+), 216 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/policy/file/GeneratedFilePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java diff --git a/pom.xml b/pom.xml index 025a124..447c2dc 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,40 @@ ${project.build.sourceEncoding} + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test.java + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + it-tests + + integration-test + verify + + + + **/*IT.java + + + + + + + + it + + \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java new file mode 100644 index 0000000..d7bdc82 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java @@ -0,0 +1,21 @@ +package io.github.bsayli.codegen.initializr.adapter.out; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.MavenPomPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.util.List; + +public class StandardArtifactsAdapter implements ProjectArtifactsPort { + + private final MavenPomPort mavenPomPort; + + public StandardArtifactsAdapter(MavenPomPort mavenPomPort) { + this.mavenPomPort = mavenPomPort; + } + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + return List.of(mavenPomPort.generate(blueprint)); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java index 91abeb7..5c7e9c5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -1,28 +1,42 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; +import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy.FAIL_IF_EXISTS; + import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort.OnExistsPolicy; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectWriterPort; import java.nio.file.Path; public class CreateProjectService implements CreateProjectUseCase { - private final ProjectBlueprintMapper mapper; - private final ProjectFileSystemPort fs; + private final ProjectBlueprintMapper mapper; + private final ProjectRootPort rootPort; + private final ProjectArtifactsPort artifactsPort; + private final ProjectWriterPort writerPort; + + public CreateProjectService( + ProjectBlueprintMapper mapper, + ProjectRootPort rootPort, + ProjectArtifactsPort artifactsPort, + ProjectWriterPort writerPort) { + this.mapper = mapper; + this.rootPort = rootPort; + this.artifactsPort = artifactsPort; + this.writerPort = writerPort; + } - public CreateProjectService(ProjectBlueprintMapper mapper, ProjectFileSystemPort fs) { - this.mapper = mapper; - this.fs = fs; - } + @Override + public CreateProjectResult execute(CreateProjectCommand command) { + ProjectBlueprint bp = mapper.toDomain(command); - @Override - public CreateProjectResult execute(CreateProjectCommand command) { - ProjectBlueprint blueprint = mapper.toDomain(command); + Path projectRoot = + rootPort.prepareRoot( + command.targetDirectory(), bp.getIdentity().artifactId().value(), FAIL_IF_EXISTS); - String artifactId = blueprint.getIdentity().artifactId().value(); - Path targetDirectory = command.targetDirectory(); - Path projectRoot = fs.prepareProjectRoot(targetDirectory, artifactId, OnExistsPolicy.FAIL_IF_EXISTS); + var files = artifactsPort.generate(bp); + writerPort.write(projectRoot, files); - return new CreateProjectResult(blueprint, projectRoot); - } -} \ No newline at end of file + return new CreateProjectResult(bp, projectRoot); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java index cdb6e93..a5aa545 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java @@ -31,25 +31,15 @@ public ProjectBlueprint toDomain(CreateProjectCommand c) { var depsWrapper = Dependencies.of(deps); - if (c.preferredJava() != null && c.preferredBoot() != null) { - return ProjectBlueprintFactory.fromPrimitives( - c.groupId(), - c.artifactId(), - c.projectName(), - c.projectDescription(), - c.packageName(), - c.buildOptions(), - c.preferredJava(), - c.preferredBoot(), - depsWrapper); - } - return ProjectBlueprintFactory.fromPrimitivesWithAutoTarget( + return ProjectBlueprintFactory.fromPrimitives( c.groupId(), c.artifactId(), c.projectName(), c.projectDescription(), c.packageName(), c.buildOptions(), + c.preferredJava(), + c.preferredBoot(), depsWrapper); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java new file mode 100644 index 0000000..ae7b165 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java @@ -0,0 +1,15 @@ +package io.github.bsayli.codegen.initializr.bootstrap; + +import io.github.bsayli.codegen.initializr.adapter.out.StandardArtifactsAdapter; +import io.github.bsayli.codegen.initializr.domain.port.out.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringBeansConfig { + + @Bean + ProjectArtifactsPort projectArtifactsPort(MavenPomPort mavenPomPort) { + return new StandardArtifactsAdapter(mavenPomPort); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java index 435fda9..c945a77 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java @@ -42,6 +42,7 @@ public static ProjectBlueprint of( BuildOptions buildOptions, PlatformTarget platformTarget, Dependencies dependencies) { + ensureNotNull(identity, IDENTITY_REQUIRED); ensureNotNull(name, compose(PROJECT_NAME, NOT_BLANK)); ensureNotNull(packageName, compose(PACKAGE_NAME, NOT_BLANK)); @@ -91,18 +92,6 @@ public static ProjectBlueprint of( Dependencies.of(Arrays.asList(deps))); } - public static ProjectBlueprint ofWithAutoTarget( - ProjectIdentity identity, - ProjectName name, - ProjectDescription description, - PackageName packageName, - BuildOptions buildOptions, - Dependencies dependencies) { - ensureNotNull(buildOptions, BUILD_OPTIONS_REQUIRED); - PlatformTarget target = PlatformTargetSelector.selectDefaultFor(buildOptions); - return of(identity, name, description, packageName, buildOptions, target, dependencies); - } - public static ProjectBlueprint fromPrimitives( String groupId, String artifactId, @@ -113,29 +102,15 @@ public static ProjectBlueprint fromPrimitives( JavaVersion preferredJava, SpringBootVersion preferredBoot, Dependencies dependencies) { - var identity = new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); - var name = new ProjectName(projectName); - var description = new ProjectDescription(projectDescription); - var pkg = new PackageName(packageName); - - var target = PlatformTargetSelector.selectOrDefault(buildOptions, preferredJava, preferredBoot); - return of(identity, name, description, pkg, buildOptions, target, dependencies); - } - public static ProjectBlueprint fromPrimitivesWithAutoTarget( - String groupId, - String artifactId, - String projectName, - String projectDescription, - String packageName, - BuildOptions buildOptions, - Dependencies dependencies) { var identity = new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); var name = new ProjectName(projectName); var description = new ProjectDescription(projectDescription); var pkg = new PackageName(packageName); - return ofWithAutoTarget(identity, name, description, pkg, buildOptions, dependencies); + var target = PlatformTargetSelector.select(buildOptions, preferredJava, preferredBoot); + + return of(identity, name, description, pkg, buildOptions, target, dependencies); } private static void ensureNotNull(Object value, ErrorCode code) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java deleted file mode 100644 index 6d71ea5..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; -import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; - -public final class PlatformTargetFactory { - private PlatformTargetFactory() {} - - public static PlatformTarget of( - BuildOptions options, JavaVersion preferredJava, SpringBootVersion preferredBoot) { - PlatformTarget t = - PlatformTargetSelector.selectOrDefault(options, preferredJava, preferredBoot); - CompatibilityPolicy.ensureCompatible(options, t); - return t; - } - - public static PlatformTarget defaultFor(BuildOptions options) { - PlatformTarget t = PlatformTargetSelector.selectDefaultFor(options); - CompatibilityPolicy.ensureCompatible(options, t); - return t; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/file/GeneratedFilePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/file/GeneratedFilePolicy.java new file mode 100644 index 0000000..0487239 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/file/GeneratedFilePolicy.java @@ -0,0 +1,31 @@ +package io.github.bsayli.codegen.initializr.domain.policy.file; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import java.nio.charset.Charset; +import java.nio.file.Path; + +public final class GeneratedFilePolicy { + private GeneratedFilePolicy() {} + + public static void requireRelativePath(Path path) { + if (path == null) throw new DomainViolationException(() -> "file.path.not.blank"); + if (path.isAbsolute()) + throw new DomainViolationException(() -> "file.path.absolute.not.allowed"); + if (path.getNameCount() == 0) throw new DomainViolationException(() -> "file.path.not.blank"); + for (Path part : path) { + String s = part.toString(); + if (s.isEmpty() || ".".equals(s) || "..".equals(s)) { + throw new DomainViolationException(() -> "file.path.traversal.not.allowed"); + } + } + } + + public static void requireTextContent(CharSequence content, Charset charset) { + if (content == null) throw new DomainViolationException(() -> "file.content.not.blank"); + if (charset == null) throw new DomainViolationException(() -> "file.charset.not.blank"); + } + + public static void requireBinaryContent(byte[] bytes) { + if (bytes == null) throw new DomainViolationException(() -> "file.content.not.blank"); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java index 920a91c..4ce4773 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java @@ -1,45 +1,20 @@ -// domain/policy/tech/PlatformTargetSelector.java package io.github.bsayli.codegen.initializr.domain.policy.tech; -import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_21; -import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion.JAVA_25; -import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_4_10; -import static io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion.V3_5_6; - import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import java.util.Comparator; import java.util.List; public final class PlatformTargetSelector { - private static final List BOOT_PRIORITY = List.of(V3_5_6, V3_4_10); - private static final List JAVA_PRIORITY = List.of(JAVA_21, JAVA_25); - private PlatformTargetSelector() {} - @SuppressWarnings("unused") - public static PlatformTarget selectDefaultFor(BuildOptions options) { - var candidates = CompatibilityPolicy.allSupportedTargets(); - return candidates.stream() - .min( - Comparator.comparing((PlatformTarget t) -> BOOT_PRIORITY.indexOf(t.springBoot())) - .thenComparing(t -> JAVA_PRIORITY.indexOf(t.java()))) - .orElse(new PlatformTarget(JAVA_21, V3_5_6)); - } - - @SuppressWarnings("unused") - public static PlatformTarget selectOrDefault( + public static PlatformTarget select( BuildOptions options, JavaVersion preferredJava, SpringBootVersion preferredBoot) { var requested = new PlatformTarget(preferredJava, preferredBoot); - try { - CompatibilityPolicy.ensureCompatible(options, requested); - return requested; - } catch (RuntimeException ignored) { - return selectDefaultFor(options); - } + CompatibilityPolicy.ensureCompatible(options, requested); + return requested; } @SuppressWarnings("unused") diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java new file mode 100644 index 0000000..31355f8 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface MavenPomPort { + GeneratedFile generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java new file mode 100644 index 0000000..6bac028 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface ProjectArtifactsPort { + Iterable generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java deleted file mode 100644 index a94d110..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectFileSystemPort.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; - -import java.nio.charset.Charset; -import java.nio.file.Path; - -public interface ProjectFileSystemPort { - - Path prepareProjectRoot(Path targetDirectory, String artifactId, OnExistsPolicy policy); - - void writeBytes(Path projectRoot, Path relativePath, byte[] content); - - void writeText(Path projectRoot, Path relativePath, CharSequence content, Charset charset); - - enum OnExistsPolicy { - FAIL_IF_EXISTS, - OVERWRITE - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java new file mode 100644 index 0000000..20a1178 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java @@ -0,0 +1,6 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +public enum ProjectRootExistencePolicy { + FAIL_IF_EXISTS, + OVERWRITE +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java new file mode 100644 index 0000000..7309b1d --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import java.nio.file.Path; + +public interface ProjectRootPort { + + Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistencePolicy policy); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java new file mode 100644 index 0000000..b8ab038 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java @@ -0,0 +1,26 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.charset.Charset; +import java.nio.file.Path; + +public interface ProjectWriterPort { + void writeBytes(Path projectRoot, Path relativePath, byte[] content); + + void writeText(Path projectRoot, Path relativePath, String content, Charset charset); + + default void writeText(Path root, Path relative, String content) { + writeText(root, relative, content, java.nio.charset.StandardCharsets.UTF_8); + } + + default void write(Path projectRoot, GeneratedFile file) { + switch (file) { + case GeneratedFile.Text(Path p, String c, Charset cs) -> writeText(projectRoot, p, c, cs); + case GeneratedFile.Binary(Path p, byte[] b) -> writeBytes(projectRoot, p, b); + } + } + + default void write(Path projectRoot, Iterable files) { + for (GeneratedFile f : files) write(projectRoot, f); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java new file mode 100644 index 0000000..801df73 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java @@ -0,0 +1,52 @@ +package io.github.bsayli.codegen.initializr.domain.port.out.artifact; + +import static io.github.bsayli.codegen.initializr.domain.policy.file.GeneratedFilePolicy.*; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Arrays; + +public sealed interface GeneratedFile permits GeneratedFile.Text, GeneratedFile.Binary { + + Path relativePath(); + + record Text(Path relativePath, String content, Charset charset) implements GeneratedFile { + public Text { + requireRelativePath(relativePath); + requireTextContent(content, charset); + } + } + + record Binary(Path relativePath, byte[] bytes) implements GeneratedFile { + public Binary { + requireRelativePath(relativePath); + requireBinaryContent(bytes); + bytes = Arrays.copyOf(bytes, bytes.length); + } + + @Override + public byte[] bytes() { + return Arrays.copyOf(bytes, bytes.length); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Binary(Path path, byte[] bytes1))) return false; + return relativePath.equals(path) && Arrays.equals(bytes, bytes1); + } + + @Override + public int hashCode() { + int result = relativePath.hashCode(); + result = 31 * result + Arrays.hashCode(bytes); + return result; + } + + @SuppressWarnings("NullableProblems") + @Override + public String toString() { + return "GeneratedFile.Binary[" + relativePath + ", size=" + bytes.length + "]"; + } + } +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 8ad3ad0..3271e8c 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -58,4 +58,12 @@ dependency.version.invalid.chars=Dependency version contains invalid characters dependency.coordinates.not.blank=Dependency coordinates require both GroupId and ArtifactId dependency.list.not.blank=Dependency list is required dependency.item.not.blank=Dependency entry must not be null -dependency.duplicate.coordinates=Duplicate dependency coordinates: {0} \ No newline at end of file +dependency.duplicate.coordinates=Duplicate dependency coordinates: {0} +# ================================ +# --- GENERATED FILE --- +# ================================ +file.path.not.blank=File path is required +file.path.absolute.not.allowed=Absolute file paths are not allowed +file.path.traversal.not.allowed=Path traversal segments ('.' or '..') are not allowed +file.content.not.blank=File content must not be null +file.charset.not.blank=Charset must not be null \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index 261b36b..2bbec84 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; -import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort.OnExistsPolicy.FAIL_IF_EXISTS; +import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; @@ -10,24 +10,35 @@ import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectFileSystemPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootPort; +import io.github.bsayli.codegen.initializr.domain.port.out.ProjectWriterPort; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; @Tag("unit") @Tag("application") @DisplayName("CreateProjectService") class CreateProjectServiceTest { + @TempDir Path tempDir; + @Test @DisplayName("execute() prepares project root and returns blueprint") void creates_project_root_and_returns_blueprint() { var mapper = new ProjectBlueprintMapper(); - var fs = new FakeFs(); - var service = new CreateProjectService(mapper, fs); + var fakeRootPort = new FakeRootPort(); + var fakeArtifacts = new FakeArtifactsPort(); + var fakeWriter = new FakeWriterPort(); + + var service = new CreateProjectService(mapper, fakeRootPort, fakeArtifacts, fakeWriter); var cmd = new CreateProjectCommand( @@ -40,7 +51,7 @@ void creates_project_root_and_returns_blueprint() { JavaVersion.JAVA_21, SpringBootVersion.V3_5_6, List.of(), - Path.of(System.getProperty("java.io.tmpdir"))); + tempDir); var result = service.execute(cmd); @@ -49,30 +60,45 @@ void creates_project_root_and_returns_blueprint() { assertThat(bp.getIdentity().artifactId().value()).isEqualTo("demo-app"); assertThat(result.projectRoot().getFileName()).hasToString("demo-app"); - assertThat(fs.lastPreparedRoot).isEqualTo(result.projectRoot()); - assertThat(fs.lastPolicy).isEqualTo(FAIL_IF_EXISTS); + assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(result.projectRoot()); + assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); + + assertThat(fakeWriter.writtenTextCount).isEqualTo(1); } - static class FakeFs implements ProjectFileSystemPort { + static class FakeRootPort implements ProjectRootPort { Path lastPreparedRoot; - OnExistsPolicy lastPolicy; + ProjectRootExistencePolicy lastPolicy; @Override - public Path prepareProjectRoot(Path targetDirectory, String artifactId, OnExistsPolicy policy) { + public Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistencePolicy policy) { this.lastPolicy = policy; - this.lastPreparedRoot = targetDirectory.resolve(artifactId); + this.lastPreparedRoot = targetDir.resolve(artifactId); return lastPreparedRoot; } + } + + static class FakeArtifactsPort implements ProjectArtifactsPort { + @Override + public Iterable generate(ProjectBlueprint blueprint) { + return List.of( + new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8)); + } + } + + static class FakeWriterPort implements ProjectWriterPort { + int writtenTextCount = 0; @Override - public void writeBytes(Path root, Path relative, byte[] content) { - throw new UnsupportedOperationException("FakeFs.writeBytes not expected in this test"); + public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { + throw new UnsupportedOperationException("bytes not expected"); } @Override public void writeText( - Path root, Path relative, CharSequence content, java.nio.charset.Charset cs) { - throw new UnsupportedOperationException("FakeFs.writeText not expected in this test"); + Path projectRoot, Path relativePath, String content, java.nio.charset.Charset charset) { + writtenTextCount++; + assertThat(relativePath).hasToString("pom.xml"); } } } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java index 174f209..07d0fe9 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java @@ -24,75 +24,60 @@ @DisplayName("ProjectBlueprintMapper") class ProjectBlueprintMapperTest { - private static ProjectBlueprint getProjectBlueprint() { - var mapper = new ProjectBlueprintMapper(); - - var inputs = List.of( - new DependencyInput("org.acme", "alpha", "", ""), - new DependencyInput("org.acme", "beta", "1.2.3", "runtime"), - new DependencyInput("org.acme", "gamma", " ", " "), - new DependencyInput("org.acme", "delta", "2.0.0-RC1", "TeSt") - ); - - var cmd = new CreateProjectCommand( - "com.acme", "demo-app", - "Demo App", "desc", - "com.acme.demo", - new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), - JavaVersion.JAVA_21, SpringBootVersion.V3_5_6, - inputs, Path.of(".") - ); - - return mapper.toDomain(cmd); - } - - @Test - @DisplayName("maps dependencies; handles blank version/scope and case-insensitive scope") - void maps_dependencies_and_handles_blank_version_and_scope() { - ProjectBlueprint bp = getProjectBlueprint(); - - assertThat(bp.getDependencies().asList()).hasSize(4); - - Map byArtifact = bp.getDependencies().asList().stream() - .collect(Collectors.toMap(d -> d.coordinates().artifactId().value(), d -> d)); - - var alpha = byArtifact.get("alpha"); - assertThat(alpha.coordinates().groupId().value()).isEqualTo("org.acme"); - assertThat(alpha.version()).isNull(); - assertThat(alpha.scope()).isNull(); - assertThat(alpha.isDefaultScope()).isTrue(); - - var beta = byArtifact.get("beta"); - assertThat(beta.version().value()).isEqualTo("1.2.3"); - assertThat(beta.scope()).isEqualTo(DependencyScope.RUNTIME); - - var gamma = byArtifact.get("gamma"); - assertThat(gamma.version()).isNull(); - assertThat(gamma.scope()).isNull(); - assertThat(gamma.isDefaultScope()).isTrue(); - - var delta = byArtifact.get("delta"); - assertThat(delta.version().value()).isEqualTo("2.0.0-RC1"); - assertThat(delta.scope()).isEqualTo(DependencyScope.TEST); - } - - @Test - @DisplayName("selects default platform target when preferences are null") - void selects_default_platform_target_when_preferences_are_null() { - var mapper = new ProjectBlueprintMapper(); - - var cmd = new CreateProjectCommand( - "com.acme", "demo-app", - "Demo App", "desc", - "com.acme.demo", - new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), - null, null, - List.of(), Path.of(".") - ); - - ProjectBlueprint bp = mapper.toDomain(cmd); - - assertThat(bp.getPlatformTarget().springBoot()).isEqualTo(SpringBootVersion.V3_5_6); - assertThat(bp.getPlatformTarget().java()).isEqualTo(JavaVersion.JAVA_21); - } -} \ No newline at end of file + private static ProjectBlueprint getProjectBlueprint() { + var mapper = new ProjectBlueprintMapper(); + + var inputs = + List.of( + new DependencyInput("org.acme", "alpha", "", ""), + new DependencyInput("org.acme", "beta", "1.2.3", "runtime"), + new DependencyInput("org.acme", "gamma", " ", " "), + new DependencyInput("org.acme", "delta", "2.0.0-RC1", "TeSt")); + + var cmd = + new CreateProjectCommand( + "com.acme", + "demo-app", + "Demo App", + "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + JavaVersion.JAVA_21, + SpringBootVersion.V3_5_6, + inputs, + Path.of(".")); + + return mapper.toDomain(cmd); + } + + @Test + @DisplayName("maps dependencies; handles blank version/scope and case-insensitive scope") + void maps_dependencies_and_handles_blank_version_and_scope() { + ProjectBlueprint bp = getProjectBlueprint(); + + assertThat(bp.getDependencies().asList()).hasSize(4); + + Map byArtifact = + bp.getDependencies().asList().stream() + .collect(Collectors.toMap(d -> d.coordinates().artifactId().value(), d -> d)); + + var alpha = byArtifact.get("alpha"); + assertThat(alpha.coordinates().groupId().value()).isEqualTo("org.acme"); + assertThat(alpha.version()).isNull(); + assertThat(alpha.scope()).isNull(); + assertThat(alpha.isDefaultScope()).isTrue(); + + var beta = byArtifact.get("beta"); + assertThat(beta.version().value()).isEqualTo("1.2.3"); + assertThat(beta.scope()).isEqualTo(DependencyScope.RUNTIME); + + var gamma = byArtifact.get("gamma"); + assertThat(gamma.version()).isNull(); + assertThat(gamma.scope()).isNull(); + assertThat(gamma.isDefaultScope()).isTrue(); + + var delta = byArtifact.get("delta"); + assertThat(delta.version().value()).isEqualTo("2.0.0-RC1"); + assertThat(delta.scope()).isEqualTo(DependencyScope.TEST); + } +} From ca273a02900607ac94113a8c86e7d3a209a06b07 Mon Sep 17 00:00:00 2001 From: bsayli Date: Wed, 24 Sep 2025 17:28:21 -0600 Subject: [PATCH 07/74] chore(config): remove unused MavenPomPort bean from SpringBeansConfig --- .gitignore | 1 + .../codegen/initializr/bootstrap/SpringBeansConfig.java | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 2107c55..8c428bd 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ buildNumber.properties generated-sources/ generated-classes/ /HELP.md +!/codegen-springboot-initialzr.iml diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java index ae7b165..0e1aabc 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java @@ -1,15 +1,8 @@ package io.github.bsayli.codegen.initializr.bootstrap; -import io.github.bsayli.codegen.initializr.adapter.out.StandardArtifactsAdapter; -import io.github.bsayli.codegen.initializr.domain.port.out.*; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SpringBeansConfig { - - @Bean - ProjectArtifactsPort projectArtifactsPort(MavenPomPort mavenPomPort) { - return new StandardArtifactsAdapter(mavenPomPort); - } + } From a41740a0c36a6bb640a521e769a05559b72b557d Mon Sep 17 00:00:00 2001 From: bsayli Date: Wed, 24 Sep 2025 17:59:20 -0600 Subject: [PATCH 08/74] feat: add GitIgnorePort and extend artifacts adapter to generate .gitignore - Introduced GitIgnorePort abstraction for producing .gitignore files - Updated StandardArtifactsAdapter to delegate both Maven POM and .gitignore generation - Extended CreateProjectServiceTest with fake artifacts to assert pom.xml and .gitignore are written --- .../adapter/out/StandardArtifactsAdapter.java | 15 +++++-- .../domain/port/out/GitIgnorePort.java | 8 ++++ .../CreateProjectServiceTest.java | 40 +++++++++---------- 3 files changed, 40 insertions(+), 23 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java index d7bdc82..224152e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java @@ -1,6 +1,8 @@ package io.github.bsayli.codegen.initializr.adapter.out; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.GitIgnorePort; import io.github.bsayli.codegen.initializr.domain.port.out.MavenPomPort; import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; @@ -9,13 +11,20 @@ public class StandardArtifactsAdapter implements ProjectArtifactsPort { private final MavenPomPort mavenPomPort; + private final GitIgnorePort gitIgnorePort; - public StandardArtifactsAdapter(MavenPomPort mavenPomPort) { + public StandardArtifactsAdapter(MavenPomPort mavenPomPort, GitIgnorePort gitIgnorePort) { this.mavenPomPort = mavenPomPort; + this.gitIgnorePort = gitIgnorePort; } @Override public Iterable generate(ProjectBlueprint blueprint) { - return List.of(mavenPomPort.generate(blueprint)); + BuildOptions options = blueprint.getBuildOptions(); + + return List.of( + mavenPomPort.generate(blueprint), + gitIgnorePort.generate(options) + ); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java new file mode 100644 index 0000000..c430de4 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.domain.port.out; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface GitIgnorePort { + GeneratedFile generate(BuildOptions buildOptions); +} \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index 2bbec84..e176c0d 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -31,8 +31,8 @@ class CreateProjectServiceTest { @TempDir Path tempDir; @Test - @DisplayName("execute() prepares project root and returns blueprint") - void creates_project_root_and_returns_blueprint() { + @DisplayName("execute() prepares project root and writes pom.xml and .gitignore") + void creates_project_root_and_writes_artifacts() { var mapper = new ProjectBlueprintMapper(); var fakeRootPort = new FakeRootPort(); var fakeArtifacts = new FakeArtifactsPort(); @@ -41,17 +41,17 @@ void creates_project_root_and_returns_blueprint() { var service = new CreateProjectService(mapper, fakeRootPort, fakeArtifacts, fakeWriter); var cmd = - new CreateProjectCommand( - "com.acme", - "demo-app", - "Demo App", - "desc", - "com.acme.demo", - new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), - JavaVersion.JAVA_21, - SpringBootVersion.V3_5_6, - List.of(), - tempDir); + new CreateProjectCommand( + "com.acme", + "demo-app", + "Demo App", + "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + JavaVersion.JAVA_21, + SpringBootVersion.V3_5_6, + List.of(), + tempDir); var result = service.execute(cmd); @@ -63,7 +63,7 @@ void creates_project_root_and_returns_blueprint() { assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(result.projectRoot()); assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); - assertThat(fakeWriter.writtenTextCount).isEqualTo(1); + assertThat(fakeWriter.writtenFiles).containsExactlyInAnyOrder("pom.xml", ".gitignore"); } static class FakeRootPort implements ProjectRootPort { @@ -82,12 +82,13 @@ static class FakeArtifactsPort implements ProjectArtifactsPort { @Override public Iterable generate(ProjectBlueprint blueprint) { return List.of( - new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8)); + new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8), + new GeneratedFile.Text(Path.of(".gitignore"), "*.class", StandardCharsets.UTF_8)); } } static class FakeWriterPort implements ProjectWriterPort { - int writtenTextCount = 0; + final List writtenFiles = new java.util.ArrayList<>(); @Override public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { @@ -96,9 +97,8 @@ public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { @Override public void writeText( - Path projectRoot, Path relativePath, String content, java.nio.charset.Charset charset) { - writtenTextCount++; - assertThat(relativePath).hasToString("pom.xml"); + Path projectRoot, Path relativePath, String content, java.nio.charset.Charset charset) { + writtenFiles.add(relativePath.toString()); } } -} +} \ No newline at end of file From dda33900b8b6012f790cbf0e2eead6cbec228760 Mon Sep 17 00:00:00 2001 From: bsayli Date: Thu, 25 Sep 2025 13:12:15 -0600 Subject: [PATCH 09/74] refactor(app): restructure artifact generation ports and adapters - Introduced ArtifactGenerator interface in adapter.out.generator - Updated StandardArtifactsAdapter to orchestrate multiple ArtifactGenerators - Moved MavenPomPort, GitIgnorePort, SourceScaffolderPort, TestScaffolderPort, ConfigFilesPort into application.port.out.artifacts - Standardized generateFiles(...) contract for artifact generation --- .../adapter/out/StandardArtifactsAdapter.java | 29 +++++++------- .../out/generator/ArtifactGenerator.java | 14 +++++++ .../out/generator/build/MavenPomAdapter.java | 34 ++++++++++++++++ .../port/out/ProjectArtifactsPort.java | 3 +- .../port/out/artifacts/ConfigFilesPort.java | 8 ++++ .../port/out/artifacts}/GitIgnorePort.java | 6 +-- .../port/out/artifacts}/MavenPomPort.java | 2 +- .../out/artifacts/SourceScaffolderPort.java | 9 +++++ .../out/artifacts/TestScaffolderPort.java | 8 ++++ .../createproject/CreateProjectService.java | 8 ++-- .../bootstrap/SpringBeansConfig.java | 4 +- .../ProjectRootExistencePolicy.java | 2 +- .../out/{ => filesystem}/ProjectRootPort.java | 2 +- .../{ => filesystem}/ProjectWriterPort.java | 11 +++++- .../CreateProjectServiceTest.java | 39 ++++++++++++++----- 15 files changed, 139 insertions(+), 40 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java rename src/main/java/io/github/bsayli/codegen/initializr/{domain => application}/port/out/ProjectArtifactsPort.java (80%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java rename src/main/java/io/github/bsayli/codegen/initializr/{domain/port/out => application/port/out/artifacts}/GitIgnorePort.java (61%) rename src/main/java/io/github/bsayli/codegen/initializr/{domain/port/out => application/port/out/artifacts}/MavenPomPort.java (76%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java rename src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/{ => filesystem}/ProjectRootExistencePolicy.java (50%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/{ => filesystem}/ProjectRootPort.java (68%) rename src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/{ => filesystem}/ProjectWriterPort.java (72%) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java index 224152e..a727fe6 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java @@ -1,30 +1,29 @@ package io.github.bsayli.codegen.initializr.adapter.out; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.GitIgnorePort; -import io.github.bsayli.codegen.initializr.domain.port.out.MavenPomPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; public class StandardArtifactsAdapter implements ProjectArtifactsPort { - private final MavenPomPort mavenPomPort; - private final GitIgnorePort gitIgnorePort; + private final List artifactGenerators; - public StandardArtifactsAdapter(MavenPomPort mavenPomPort, GitIgnorePort gitIgnorePort) { - this.mavenPomPort = mavenPomPort; - this.gitIgnorePort = gitIgnorePort; + public StandardArtifactsAdapter(List artifactGenerators) { + this.artifactGenerators = artifactGenerators; } @Override public Iterable generate(ProjectBlueprint blueprint) { - BuildOptions options = blueprint.getBuildOptions(); - - return List.of( - mavenPomPort.generate(blueprint), - gitIgnorePort.generate(options) - ); + List generatedFiles = new ArrayList<>(); + artifactGenerators.stream() + .filter(g -> g.supports(blueprint)) + .sorted(Comparator.comparingInt(ArtifactGenerator::order)) + .forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); + return generatedFiles; } } \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java new file mode 100644 index 0000000..7ed0438 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java @@ -0,0 +1,14 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface ArtifactGenerator { + default boolean supports(ProjectBlueprint blueprint) { return true; } + + default int order() { return 100; } + + Iterable generateFiles(ProjectBlueprint blueprint); + + default String name() { return getClass().getSimpleName(); } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java new file mode 100644 index 0000000..58657e7 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java @@ -0,0 +1,34 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.build; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +import java.util.List; + +public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { + + private static final int ORDER = 10; + private static final String GENERATOR_NAME = "maven-pom"; + + @Override + public GeneratedFile generate(ProjectBlueprint blueprint) { + throw new UnsupportedOperationException("MavenPomAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return List.of(generate(blueprint)); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return GENERATOR_NAME; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsPort.java similarity index 80% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsPort.java index 6bac028..b00fa4a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectArtifactsPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsPort.java @@ -1,8 +1,9 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.application.port.out; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; public interface ProjectArtifactsPort { + Iterable generate(ProjectBlueprint blueprint); } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java new file mode 100644 index 0000000..63531d1 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface ConfigFilesPort { + Iterable generate(BuildOptions options); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java similarity index 61% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java index c430de4..e618c39 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/GitIgnorePort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java @@ -1,8 +1,8 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; public interface GitIgnorePort { - GeneratedFile generate(BuildOptions buildOptions); -} \ No newline at end of file + GeneratedFile generate(BuildOptions buildOptions); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java similarity index 76% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java index 31355f8..8ec4f27 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/MavenPomPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java new file mode 100644 index 0000000..6eab8ec --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface SourceScaffolderPort { + + Iterable generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java new file mode 100644 index 0000000..ed8845e --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface TestScaffolderPort { + Iterable generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java index 5c7e9c5..4cbcfee 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -1,11 +1,11 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; -import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy.FAIL_IF_EXISTS; +import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectWriterPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; import java.nio.file.Path; public class CreateProjectService implements CreateProjectUseCase { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java index 0e1aabc..877e242 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java @@ -3,6 +3,4 @@ import org.springframework.context.annotation.Configuration; @Configuration -public class SpringBeansConfig { - -} +public class SpringBeansConfig {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootExistencePolicy.java similarity index 50% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootExistencePolicy.java index 20a1178..9629469 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootExistencePolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootExistencePolicy.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.domain.port.out.filesystem; public enum ProjectRootExistencePolicy { FAIL_IF_EXISTS, diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootPort.java similarity index 68% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootPort.java index 7309b1d..8f4b421 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectRootPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectRootPort.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.domain.port.out.filesystem; import java.nio.file.Path; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java similarity index 72% rename from src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java index b8ab038..5d95941 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/ProjectWriterPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.domain.port.out; +package io.github.bsayli.codegen.initializr.domain.port.out.filesystem; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.nio.charset.Charset; @@ -23,4 +23,13 @@ default void write(Path projectRoot, GeneratedFile file) { default void write(Path projectRoot, Iterable files) { for (GeneratedFile f : files) write(projectRoot, f); } + + default void write(Path projectRoot, GeneratedFile... files) { + for (GeneratedFile f : files) write(projectRoot, f); + } + + default void write(Path projectRoot, java.util.stream.Stream files) { + files.forEach(f -> write(projectRoot, f)); + } + } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index e176c0d..1435ba3 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; -import static io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy.FAIL_IF_EXISTS; +import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; @@ -10,13 +10,15 @@ import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootExistencePolicy; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectRootPort; -import io.github.bsayli.codegen.initializr.domain.port.out.ProjectWriterPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -31,7 +33,7 @@ class CreateProjectServiceTest { @TempDir Path tempDir; @Test - @DisplayName("execute() prepares project root and writes pom.xml and .gitignore") + @DisplayName("execute() prepares project root and writes pom.xml, .gitignore and source skeleton") void creates_project_root_and_writes_artifacts() { var mapper = new ProjectBlueprintMapper(); var fakeRootPort = new FakeRootPort(); @@ -63,7 +65,14 @@ void creates_project_root_and_writes_artifacts() { assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(result.projectRoot()); assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); - assertThat(fakeWriter.writtenFiles).containsExactlyInAnyOrder("pom.xml", ".gitignore"); + assertThat(fakeWriter.writtenFiles) + .containsExactlyInAnyOrder( + "pom.xml", + ".gitignore", + "src/main/java/com/acme/demo/DemoApplication.java", + "src/test/java/com/acme/demo/DemoApplicationTests.java", + "src/main/resources/application.yml" + ); } static class FakeRootPort implements ProjectRootPort { @@ -81,14 +90,24 @@ public Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistenceP static class FakeArtifactsPort implements ProjectArtifactsPort { @Override public Iterable generate(ProjectBlueprint blueprint) { + String pkgPath = blueprint.getPackageName().value().replace('.', '/'); + return List.of( new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8), - new GeneratedFile.Text(Path.of(".gitignore"), "*.class", StandardCharsets.UTF_8)); + new GeneratedFile.Text(Path.of(".gitignore"), "*.class", StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("src/main/java", pkgPath, "DemoApplication.java"), + "class DemoApplication {}", StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("src/test/java", pkgPath, "DemoApplicationTests.java"), + "class DemoApplicationTests {}", StandardCharsets.UTF_8), + new GeneratedFile.Text(Path.of("src/main/resources/application.yml"), + "spring:\n application:\n name: demo-app", StandardCharsets.UTF_8)); } } static class FakeWriterPort implements ProjectWriterPort { - final List writtenFiles = new java.util.ArrayList<>(); + final List writtenFiles = new ArrayList<>(); @Override public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { @@ -97,7 +116,7 @@ public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { @Override public void writeText( - Path projectRoot, Path relativePath, String content, java.nio.charset.Charset charset) { + Path projectRoot, Path relativePath, String content, Charset charset) { writtenFiles.add(relativePath.toString()); } } From b522dc3cd40a7dd4dcc3b00a1e8526b3a7f64dab Mon Sep 17 00:00:00 2001 From: bsayli Date: Thu, 25 Sep 2025 13:13:55 -0600 Subject: [PATCH 10/74] git commit -m "chore(gitignore): exclude IntelliJ .iml files from version control" --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c428bd..e5719b5 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,4 @@ buildNumber.properties generated-sources/ generated-classes/ /HELP.md -!/codegen-springboot-initialzr.iml +*.iml From 7f8504d5f0c58dcddef5eb7a5da003e658d3ffb5 Mon Sep 17 00:00:00 2001 From: bsayli Date: Thu, 25 Sep 2025 17:48:22 -0600 Subject: [PATCH 11/74] feat(generator): add GitIgnoreAdapter and ReadmeAdapter, introduce templating config --- pom.xml | 5 + .../adapter/error/AdapterException.java | 38 +++++ .../adapter/out/StandardArtifactsAdapter.java | 11 +- .../out/generator/ArtifactGenerator.java | 14 +- .../out/generator/build/MavenPomAdapter.java | 47 ++++--- .../generator/config/ConfigFilesAdapter.java | 33 +++++ .../out/generator/docs/ReadmeAdapter.java | 95 +++++++++++++ .../source/SourceScaffolderAdapter.java | 32 +++++ .../generator/test/TestScaffolderAdapter.java | 32 +++++ .../out/generator/vcs/GitIgnoreAdapter.java | 62 +++++++++ .../wrapper/MavenWrapperAdapter.java | 32 +++++ .../FreeMarkerTemplateRenderer.java | 29 ++++ .../out/templating/TemplateRenderer.java | 9 ++ .../TemplateRenderingException.java | 22 +++ .../port/out/archive/ProjectArchiverPort.java | 7 + .../port/out/artifacts/MavenWrapperPort.java | 8 ++ .../port/out/artifacts/ReadmePort.java | 8 ++ .../createproject/CreateProjectResult.java | 3 +- .../createproject/CreateProjectService.java | 13 +- .../bootstrap/config/ArtifactProperties.java | 6 + .../config/CodegenArtifactsProperties.java | 14 ++ .../FreeMarkerTemplateConfiguration.java | 50 +++++++ .../FreeMarkerTemplateProperties.java | 22 +++ .../out/filesystem/ProjectWriterPort.java | 1 - src/main/resources/application.yml | 24 ++++ src/main/resources/messages.properties | 6 +- src/main/resources/templates/readme.ftl | 129 +++++++++++++++++ .../CreateProjectServiceTest.java | 130 +++++++++++------- 28 files changed, 795 insertions(+), 87 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderer.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/archive/ProjectArchiverPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java create mode 100644 src/main/resources/templates/readme.ftl diff --git a/pom.xml b/pom.xml index 447c2dc..71c246a 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,11 @@ spring-boot-starter + + org.springframework.boot + spring-boot-starter-validation + + org.apache.maven maven-model diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java new file mode 100644 index 0000000..e1095e9 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java @@ -0,0 +1,38 @@ +package io.github.bsayli.codegen.initializr.adapter.error; + +import java.io.Serial; + +public abstract class AdapterException extends RuntimeException { + @Serial private static final long serialVersionUID = 1L; + + private final String messageKey; + private final transient Object[] args; + + protected AdapterException(String messageKey, Object... args) { + super(messageKey); + this.messageKey = messageKey; + this.args = args; + } + + protected AdapterException(String messageKey, Throwable cause, Object... args) { + super(messageKey, cause); + this.messageKey = messageKey; + this.args = args; + } + + protected static Object[] prepend(Object first, Object... rest) { + int extra = (rest == null) ? 0 : rest.length; + Object[] merged = new Object[1 + extra]; + merged[0] = first; + if (extra > 0) System.arraycopy(rest, 0, merged, 1, extra); + return merged; + } + + public String getMessageKey() { + return messageKey; + } + + public Object[] getArgs() { + return args; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java index a727fe6..ca351b4 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java @@ -1,10 +1,9 @@ package io.github.bsayli.codegen.initializr.adapter.out; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -21,9 +20,9 @@ public StandardArtifactsAdapter(List artifactGenerators) { public Iterable generate(ProjectBlueprint blueprint) { List generatedFiles = new ArrayList<>(); artifactGenerators.stream() - .filter(g -> g.supports(blueprint)) - .sorted(Comparator.comparingInt(ArtifactGenerator::order)) - .forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); + .filter(g -> g.supports(blueprint)) + .sorted(Comparator.comparingInt(ArtifactGenerator::order)) + .forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); return generatedFiles; } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java index 7ed0438..c161ab8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java @@ -4,11 +4,17 @@ import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; public interface ArtifactGenerator { - default boolean supports(ProjectBlueprint blueprint) { return true; } + default boolean supports(ProjectBlueprint blueprint) { + return true; + } - default int order() { return 100; } + default int order() { + return 100; + } Iterable generateFiles(ProjectBlueprint blueprint); - default String name() { return getClass().getSimpleName(); } -} \ No newline at end of file + default String name() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java index 58657e7..c9e58f3 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java @@ -4,31 +4,30 @@ import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - import java.util.List; public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { - private static final int ORDER = 10; - private static final String GENERATOR_NAME = "maven-pom"; - - @Override - public GeneratedFile generate(ProjectBlueprint blueprint) { - throw new UnsupportedOperationException("MavenPomAdapter.generate not implemented yet"); - } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return List.of(generate(blueprint)); - } - - @Override - public int order() { - return ORDER; - } - - @Override - public String name() { - return GENERATOR_NAME; - } -} \ No newline at end of file + private static final int ORDER = 10; + private static final String GENERATOR_NAME = "maven-pom"; + + @Override + public GeneratedFile generate(ProjectBlueprint blueprint) { + throw new UnsupportedOperationException("MavenPomAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return List.of(generate(blueprint)); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return GENERATOR_NAME; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java new file mode 100644 index 0000000..a1e3401 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java @@ -0,0 +1,33 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.config; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public final class ConfigFilesAdapter implements ConfigFilesPort, ArtifactGenerator { + + private static final int ORDER = 50; + private static final String NAME = "config-files"; + + @Override + public Iterable generate(BuildOptions options) { + throw new UnsupportedOperationException("ConfigFilesAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint.getBuildOptions()); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java new file mode 100644 index 0000000..df68b98 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java @@ -0,0 +1,95 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.docs; + +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenArtifactsProperties; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { + + private static final int ORDER = 90; + private static final String NAME = "readme"; + + // Model keys + private static final String KEY_PROJECT_NAME = "projectName"; + private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; + private static final String KEY_GROUP_ID = "groupId"; + private static final String KEY_ARTIFACT_ID = "artifactId"; + private static final String KEY_PACKAGE_NAME = "packageName"; + private static final String KEY_BUILD_TOOL = "buildTool"; + private static final String KEY_LANGUAGE = "language"; + private static final String KEY_FRAMEWORK = "framework"; + private static final String KEY_JAVA_VERSION = "javaVersion"; + private static final String KEY_SPRING_BOOT_VERSION = "springBootVersion"; + private static final String KEY_DEPENDENCIES = "dependencies"; + + private final TemplateRenderer renderer; + private final ArtifactProperties cfg; + + public ReadmeAdapter(TemplateRenderer renderer, CodegenArtifactsProperties props) { + this.renderer = renderer; + this.cfg = props.readme(); + } + + @Override + public boolean supports(ProjectBlueprint bp) { + return cfg.enabled(); + } + + @Override + public GeneratedFile generate(ProjectBlueprint blueprint) { + Path outPath = Path.of(cfg.outputPath()); + String template = cfg.template(); + Map model = buildModel(blueprint); + return renderer.renderUtf8(outPath, template, model); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return List.of(generate(blueprint)); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } + + private Map buildModel(ProjectBlueprint bp) { + ProjectIdentity id = bp.getIdentity(); + BuildOptions bo = bp.getBuildOptions(); + PlatformTarget pt = bp.getPlatformTarget(); + PackageName pn = bp.getPackageName(); + Dependencies deps = bp.getDependencies(); + + return Map.ofEntries( + entry(KEY_PROJECT_NAME, bp.getName().value()), + entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), + entry(KEY_GROUP_ID, id.groupId().value()), + entry(KEY_ARTIFACT_ID, id.artifactId().value()), + entry(KEY_PACKAGE_NAME, pn.value()), + entry(KEY_BUILD_TOOL, bo.buildTool().name()), + entry(KEY_LANGUAGE, bo.language().name()), + entry(KEY_FRAMEWORK, bo.framework().name()), + entry(KEY_JAVA_VERSION, pt.java().asString()), + entry(KEY_SPRING_BOOT_VERSION, pt.springBoot().value()), + entry(KEY_DEPENDENCIES, deps.asList())); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java new file mode 100644 index 0000000..2e1615c --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java @@ -0,0 +1,32 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.source; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public final class SourceScaffolderAdapter implements SourceScaffolderPort, ArtifactGenerator { + + private static final int ORDER = 30; + private static final String NAME = "source-scaffold"; + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + throw new UnsupportedOperationException("SourceScaffolderAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java new file mode 100644 index 0000000..178d788 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java @@ -0,0 +1,32 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.test; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public final class TestScaffolderAdapter implements TestScaffolderPort, ArtifactGenerator { + + private static final int ORDER = 40; + private static final String NAME = "test-scaffold"; + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + throw new UnsupportedOperationException("TestScaffolderAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java new file mode 100644 index 0000000..2bdd3e7 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java @@ -0,0 +1,62 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.vcs; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenArtifactsProperties; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public final class GitIgnoreAdapter implements GitIgnorePort, ArtifactGenerator { + + private static final int ORDER = 20; + private static final String NAME = "gitignore"; + + private static final String KEY_IGNORE_LIST = "ignoreList"; + + private final TemplateRenderer renderer; + private final ArtifactProperties cfg; + + public GitIgnoreAdapter(TemplateRenderer renderer, CodegenArtifactsProperties props) { + this.renderer = renderer; + this.cfg = props.gitignore(); + } + + @Override + public boolean supports(ProjectBlueprint bp) { + return cfg.enabled(); + } + + @Override + public GeneratedFile generate(BuildOptions buildOptions) { + Path outPath = Path.of(cfg.outputPath()); + String template = cfg.template(); + Map model = buildModel(buildOptions); + return renderer.renderUtf8(outPath, template, model); + } + + @Override + public Iterable generateFiles(ProjectBlueprint bp) { + return List.of(generate(bp.getBuildOptions())); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } + + @SuppressWarnings("unused") + private Map buildModel(BuildOptions buildOptions) { + return Map.of(KEY_IGNORE_LIST, List.of()); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java new file mode 100644 index 0000000..0a8ca93 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java @@ -0,0 +1,32 @@ +package io.github.bsayli.codegen.initializr.adapter.out.generator.wrapper; + +import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public final class MavenWrapperAdapter implements MavenWrapperPort, ArtifactGenerator { + + private static final int ORDER = 60; + private static final String NAME = "maven-wrapper"; + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + throw new UnsupportedOperationException("MavenWrapperAdapter.generate not implemented yet"); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java new file mode 100644 index 0000000..96ff848 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java @@ -0,0 +1,29 @@ +package io.github.bsayli.codegen.initializr.adapter.out.templating; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Map; + +public class FreeMarkerTemplateRenderer implements TemplateRenderer { + + private final Configuration cfg; + + public FreeMarkerTemplateRenderer(Configuration cfg) { + this.cfg = cfg; + } + + @Override + public GeneratedFile renderUtf8(Path outPath, String templateName, Map model) { + try (StringWriter sw = new StringWriter()) { + Template tpl = cfg.getTemplate(templateName); + tpl.process(model, sw); + return new GeneratedFile.Text(outPath, sw.toString(), StandardCharsets.UTF_8); + } catch (Exception e) { + throw new TemplateRenderingException(templateName, e); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderer.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderer.java new file mode 100644 index 0000000..4edb23b --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderer.java @@ -0,0 +1,9 @@ +package io.github.bsayli.codegen.initializr.adapter.out.templating; + +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.Map; + +public interface TemplateRenderer { + GeneratedFile renderUtf8(Path outPath, String templateName, Map model); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java new file mode 100644 index 0000000..93ffedb --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.adapter.out.templating; + +import io.github.bsayli.codegen.initializr.adapter.error.AdapterException; + +public final class TemplateRenderingException extends AdapterException { + public static final String KEY = "adapter.template.render.failed"; + private final String templateName; + + public TemplateRenderingException(String templateName, Object... args) { + super(KEY, prepend(templateName, args)); + this.templateName = templateName; + } + + public TemplateRenderingException(String templateName, Throwable cause, Object... args) { + super(KEY, cause, prepend(templateName, args)); + this.templateName = templateName; + } + + public String getTemplateName() { + return templateName; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/archive/ProjectArchiverPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/archive/ProjectArchiverPort.java new file mode 100644 index 0000000..77369d4 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/archive/ProjectArchiverPort.java @@ -0,0 +1,7 @@ +package io.github.bsayli.codegen.initializr.application.port.out.archive; + +import java.nio.file.Path; + +public interface ProjectArchiverPort { + Path archive(Path projectRoot, String artifactId); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java new file mode 100644 index 0000000..8b7f751 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface MavenWrapperPort { + Iterable generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java new file mode 100644 index 0000000..8daed75 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java @@ -0,0 +1,8 @@ +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; + +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; + +public interface ReadmePort { + GeneratedFile generate(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java index 23d141a..ff2d834 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java @@ -1,6 +1,5 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import java.nio.file.Path; -public record CreateProjectResult(ProjectBlueprint blueprint, Path projectRoot) {} +public record CreateProjectResult(Path archivePath) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java index 4cbcfee..6e1e559 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -2,8 +2,9 @@ import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; import java.nio.file.Path; @@ -14,16 +15,19 @@ public class CreateProjectService implements CreateProjectUseCase { private final ProjectRootPort rootPort; private final ProjectArtifactsPort artifactsPort; private final ProjectWriterPort writerPort; + private final ProjectArchiverPort archiverPort; public CreateProjectService( ProjectBlueprintMapper mapper, ProjectRootPort rootPort, ProjectArtifactsPort artifactsPort, - ProjectWriterPort writerPort) { + ProjectWriterPort writerPort, + ProjectArchiverPort archiverPort) { this.mapper = mapper; this.rootPort = rootPort; this.artifactsPort = artifactsPort; this.writerPort = writerPort; + this.archiverPort = archiverPort; } @Override @@ -37,6 +41,9 @@ public CreateProjectResult execute(CreateProjectCommand command) { var files = artifactsPort.generate(bp); writerPort.write(projectRoot, files); - return new CreateProjectResult(bp, projectRoot); + String baseName = bp.getIdentity().artifactId().value(); + Path archive = archiverPort.archive(projectRoot, baseName); + + return new CreateProjectResult(archive); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java new file mode 100644 index 0000000..c2fd065 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java @@ -0,0 +1,6 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import jakarta.validation.constraints.NotBlank; + +public record ArtifactProperties( + boolean enabled, @NotBlank String template, @NotBlank String outputPath) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java new file mode 100644 index 0000000..182474c --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java @@ -0,0 +1,14 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.validation.annotation.Validated; + +@Validated +@ConfigurationProperties(prefix = "codegen.artifacts") +public record CodegenArtifactsProperties( + @Valid @NotNull @NestedConfigurationProperty ArtifactProperties gitignore, + @Valid @NotNull @NestedConfigurationProperty ArtifactProperties mavenPom, + @Valid @NotNull @NestedConfigurationProperty ArtifactProperties readme) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java new file mode 100644 index 0000000..c225961 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java @@ -0,0 +1,50 @@ +package io.github.bsayli.codegen.initializr.bootstrap.freemarker; + +import static freemarker.template.Configuration.VERSION_2_3_34; + +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; +import freemarker.template.Version; +import org.springframework.context.annotation.Bean; + +// @org.springframework.context.annotation.Configuration +// @EnableConfigurationProperties(FreeMarkerTemplateProperties.class) +public class FreeMarkerTemplateConfiguration { + + public static final String NUMBER_FORMAT_COMPUTER = "computer"; + + private static final Version FM_VER = VERSION_2_3_34; + + private final FreeMarkerTemplateProperties props; + + public FreeMarkerTemplateConfiguration(FreeMarkerTemplateProperties props) { + this.props = props; + } + + @Bean + Configuration freemarkerConfiguration() { + Configuration cfg = new Configuration(FM_VER); + cfg.setDefaultEncoding(props.encoding()); + cfg.setOutputEncoding(props.encoding()); + cfg.setClassForTemplateLoading(getClass(), props.templatePath()); + cfg.setTemplateExceptionHandler(toHandler(props.handler())); + cfg.setLogTemplateExceptions(false); + cfg.setWrapUncheckedExceptions(true); + cfg.setLocalizedLookup(false); + cfg.setNumberFormat(NUMBER_FORMAT_COMPUTER); + cfg.setFallbackOnNullLoopVariable(false); + + cfg.setTemplateUpdateDelayMilliseconds(props.cacheEnabled() ? props.cacheUpdateDelayMs() : 0L); + + return cfg; + } + + private TemplateExceptionHandler toHandler(FreeMarkerTemplateProperties.Handler h) { + return switch (h) { + case RETHROW -> TemplateExceptionHandler.RETHROW_HANDLER; + case DEBUG -> TemplateExceptionHandler.DEBUG_HANDLER; + case HTML_DEBUG -> TemplateExceptionHandler.HTML_DEBUG_HANDLER; + case IGNORE -> TemplateExceptionHandler.IGNORE_HANDLER; + }; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java new file mode 100644 index 0000000..3dff461 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.bootstrap.freemarker; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Validated +@ConfigurationProperties(prefix = "templating") +public record FreeMarkerTemplateProperties( + @NotBlank String encoding, + @NotNull Handler handler, + @NotBlank String templatePath, + boolean cacheEnabled, + long cacheUpdateDelayMs) { + public enum Handler { + RETHROW, + DEBUG, + HTML_DEBUG, + IGNORE + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java index 5d95941..f0dd71c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/filesystem/ProjectWriterPort.java @@ -31,5 +31,4 @@ default void write(Path projectRoot, GeneratedFile... files) { default void write(Path projectRoot, java.util.stream.Stream files) { files.forEach(f -> write(projectRoot, f)); } - } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0171193..c8f2f53 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,30 @@ spring: application: name: codegen-springboot-initializr + +codegen: + artifacts: + gitignore: + enabled: true + template: gitignore.ftl + output-path: .gitignore + maven-pom: + enabled: true + template: pom.ftl + output-path: pom.xml + readme: + enabled: true + template: readme.ftl + output-path: README.md + +templating: + encoding: UTF-8 + handler: RETHROW + template-path: /templates + cache-enabled: true + cache-update-delay-ms: 60000 + + freemarker: encoding: UTF-8 template-exception-handler: RETHROW_HANDLER diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 3271e8c..9c41e6a 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -66,4 +66,8 @@ file.path.not.blank=File path is required file.path.absolute.not.allowed=Absolute file paths are not allowed file.path.traversal.not.allowed=Path traversal segments ('.' or '..') are not allowed file.content.not.blank=File content must not be null -file.charset.not.blank=Charset must not be null \ No newline at end of file +file.charset.not.blank=Charset must not be null +# ================================ +# --- ADAPTER (TEMPLATING) --- +# ================================ +adapter.template.render.failed=Failed to render template ''{0}''. diff --git a/src/main/resources/templates/readme.ftl b/src/main/resources/templates/readme.ftl new file mode 100644 index 0000000..1049914 --- /dev/null +++ b/src/main/resources/templates/readme.ftl @@ -0,0 +1,129 @@ +# Codegen Spring Boot Initializr + +A modern Spring Boot project initializer with **Hexagonal Architecture** baked in. +This tool generates a clean project scaffold you can extend — not just a blank template, but a foundation aligned with real-world best practices from companies like Netflix, Uber, Amazon, and Google. + +--- + +## Features + +* **Maven Wrapper & pom.xml** generated out of the box. +* **Hexagonal Architecture project layout** with `domain`, `application`, `adapter`, and `bootstrap` layers. +* **Artifacts** like `.gitignore`, `application.yml`, and optional `README.md` included. +* **Minimal Hexagonal Example**: a `Customer` aggregate with a single `CreateCustomer` use case, REST controller, and in-memory repository. +* **Extensible artifact generators**: each artifact is generated by its own adapter implementing the `ArtifactGenerator` contract. + +--- + +## Generated Project Structure + +```text +com.example.customer +├─ domain // Pure business rules, core domain logic +│ ├─ model // Aggregate root & value objects +│ │ ├─ Customer.java +│ │ └─ value +│ │ ├─ CustomerId.java +│ │ └─ Email.java +│ └─ port +│ └─ CustomerRepository.java // Outbound port for persistence +│ +├─ application // Use case orchestration +│ ├─ usecase +│ │ └─ customer +│ │ └─ create +│ │ ├─ CreateCustomerCommand.java +│ │ ├─ CreateCustomerResult.java +│ │ └─ CreateCustomerService.java +│ └─ mapper (optional) +│ +├─ adapter // Technical implementations +│ ├─ inbound +│ │ └─ rest +│ │ └─ customer +│ │ ├─ CustomerController.java +│ │ └─ dto +│ │ ├─ CreateCustomerRequest.java +│ │ └─ CustomerResponse.java +│ └─ outbound +│ └─ persistence +│ └─ memory +│ └─ InMemoryCustomerRepository.java +│ +└─ bootstrap // Spring configs, bean wiring +└─ SpringBeansConfig.java +``` + +--- + +## Why Only **Create**? + +Instead of generating a full CRUD garden, the initializer provides a **minimal Create use case**: + +* Keeps the scaffold **lean and comprehensible**. +* Shows the full flow across **all hexagonal layers**. +* Leaves room for you to extend with Update/Delete/Search as exercises. + +--- + +## Artifact Generation Flow + +Artifacts are produced by ordered generators implementing the `ArtifactGenerator` contract: + +1. **MavenPomAdapter** → `pom.xml` +2. **GitIgnoreAdapter** → `.gitignore` +3. **ConfigFilesAdapter** → `application.yml` +4. **HexSourceScaffolderAdapter** → Java source files for domain/application/adapters +5. **HexTestScaffolderAdapter** → Minimal test scaffold +6. **ReadmeAdapter** → `README.md` +7. **MavenWrapperAdapter** → `.mvn/wrapper/maven-wrapper.properties` + +Each adapter declares: + +* `supports(ProjectBlueprint)` → whether to apply for a given template kind. +* `order()` → ensures proper generation sequence. +* `generateFiles(ProjectBlueprint)` → returns one or more `GeneratedFile` objects. + +--- + +## Running the Generated Project + +1. Extract the archive: `unzip myapp.zip` +2. Navigate: `cd myapp` +3. Build with Maven (if installed): `mvn package` +4. Or use the wrapper (no Maven required): + +* Linux/macOS: `./mvnw package` +* Windows: `mvnw.cmd package` + +--- + +## Example Use Case Flow + +1. **POST /customers** with JSON body → handled by `CustomerController`. +2. Controller maps request → `CreateCustomerCommand`. +3. `CreateCustomerService` executes business logic → calls `CustomerRepository` port. +4. `InMemoryCustomerRepository` adapter persists entity → returns result. +5. Response mapped back to `CustomerResponse` and returned with `201 Created`. + +--- + +## Philosophy + +* **Domain first** → business rules and entities stay free of frameworks. +* **Ports define needs, not implementations** → adapters can change without touching domain/application. +* **Minimal, extendable** → enough to show the pattern, small enough to adapt. + +--- + +## Next Steps + +* Add your own use cases: Update, Delete, Search. +* Swap `InMemoryCustomerRepository` with a JPA or MongoDB adapter. +* Extend inbound side with CLI or messaging adapters. + +--- + +## License + +MIT License — see [LICENSE](LICENSE). diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index 1435ba3..d539b75 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -3,6 +3,8 @@ import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import static org.assertj.core.api.Assertions.assertThat; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; @@ -10,11 +12,10 @@ import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -30,49 +31,72 @@ @DisplayName("CreateProjectService") class CreateProjectServiceTest { + private static final Path POM = Path.of("pom.xml"); + private static final Path GITIGNORE = Path.of(".gitignore"); + private static final Path APP_YML = Path.of("src/main/resources/application.yml"); + private static final Path WRAPPER_PROPS = Path.of(".mvn/wrapper/maven-wrapper.properties"); + private static final Path README = Path.of("README.md"); + @TempDir Path tempDir; + private static Path appClass(ProjectBlueprint bp) { + String pkg = bp.getPackageName().value().replace('.', '/'); + return Path.of("src/main/java", pkg, "DemoApplication.java"); + } + + private static Path testClass(ProjectBlueprint bp) { + String pkg = bp.getPackageName().value().replace('.', '/'); + return Path.of("src/test/java", pkg, "DemoApplicationTests.java"); + } + @Test - @DisplayName("execute() prepares project root and writes pom.xml, .gitignore and source skeleton") - void creates_project_root_and_writes_artifacts() { + @DisplayName("execute() prepares project root, writes artifacts, and returns archive path") + void creates_project_root_writes_artifacts_and_archives() { var mapper = new ProjectBlueprintMapper(); var fakeRootPort = new FakeRootPort(); var fakeArtifacts = new FakeArtifactsPort(); var fakeWriter = new FakeWriterPort(); + var fakeArchiver = new FakeArchiverPort(); - var service = new CreateProjectService(mapper, fakeRootPort, fakeArtifacts, fakeWriter); + var service = + new CreateProjectService(mapper, fakeRootPort, fakeArtifacts, fakeWriter, fakeArchiver); var cmd = - new CreateProjectCommand( - "com.acme", - "demo-app", - "Demo App", - "desc", - "com.acme.demo", - new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), - JavaVersion.JAVA_21, - SpringBootVersion.V3_5_6, - List.of(), - tempDir); + new CreateProjectCommand( + "com.acme", + "demo-app", + "Demo App", + "desc", + "com.acme.demo", + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA), + JavaVersion.JAVA_21, + SpringBootVersion.V3_5_6, + List.of(), + tempDir); var result = service.execute(cmd); - ProjectBlueprint bp = result.blueprint(); - assertThat(bp.getIdentity().groupId().value()).isEqualTo("com.acme"); - assertThat(bp.getIdentity().artifactId().value()).isEqualTo("demo-app"); - assertThat(result.projectRoot().getFileName()).hasToString("demo-app"); + assertThat(result.archivePath()).hasFileName("demo-app.zip"); + assertThat(fakeArchiver.lastProjectRoot).isEqualTo(fakeRootPort.lastPreparedRoot); + assertThat(fakeArchiver.lastArtifactId).isEqualTo("demo-app"); - assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(result.projectRoot()); + assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(tempDir.resolve("demo-app")); assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); + var bpForPaths = mapper.toDomain(cmd); + var expected = + List.of( + POM, + GITIGNORE, + appClass(bpForPaths), + testClass(bpForPaths), + APP_YML, + WRAPPER_PROPS, + README); + assertThat(fakeWriter.writtenFiles) - .containsExactlyInAnyOrder( - "pom.xml", - ".gitignore", - "src/main/java/com/acme/demo/DemoApplication.java", - "src/test/java/com/acme/demo/DemoApplicationTests.java", - "src/main/resources/application.yml" - ); + .containsExactlyInAnyOrderElementsOf(expected) + .hasSize(expected.size()); } static class FakeRootPort implements ProjectRootPort { @@ -89,25 +113,28 @@ public Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistenceP static class FakeArtifactsPort implements ProjectArtifactsPort { @Override - public Iterable generate(ProjectBlueprint blueprint) { - String pkgPath = blueprint.getPackageName().value().replace('.', '/'); - + public Iterable generate(ProjectBlueprint bp) { return List.of( - new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8), - new GeneratedFile.Text(Path.of(".gitignore"), "*.class", StandardCharsets.UTF_8), - new GeneratedFile.Text( - Path.of("src/main/java", pkgPath, "DemoApplication.java"), - "class DemoApplication {}", StandardCharsets.UTF_8), - new GeneratedFile.Text( - Path.of("src/test/java", pkgPath, "DemoApplicationTests.java"), - "class DemoApplicationTests {}", StandardCharsets.UTF_8), - new GeneratedFile.Text(Path.of("src/main/resources/application.yml"), - "spring:\n application:\n name: demo-app", StandardCharsets.UTF_8)); + new GeneratedFile.Text(POM, "", StandardCharsets.UTF_8), + new GeneratedFile.Text(GITIGNORE, "*.class", StandardCharsets.UTF_8), + new GeneratedFile.Text(appClass(bp), "class DemoApplication {}", StandardCharsets.UTF_8), + new GeneratedFile.Text( + testClass(bp), "class DemoApplicationTests {}", StandardCharsets.UTF_8), + new GeneratedFile.Text( + APP_YML, "spring:\n application:\n name: demo-app", StandardCharsets.UTF_8), + new GeneratedFile.Text( + WRAPPER_PROPS, + "distributionUrl=https://repo.maven.apache.org/maven2/...", + StandardCharsets.UTF_8), + new GeneratedFile.Text( + README, + "# Demo App\n\nThis project was generated by Codegen Initializr.", + StandardCharsets.UTF_8)); } } static class FakeWriterPort implements ProjectWriterPort { - final List writtenFiles = new ArrayList<>(); + final List writtenFiles = new ArrayList<>(); @Override public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { @@ -115,9 +142,20 @@ public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { } @Override - public void writeText( - Path projectRoot, Path relativePath, String content, Charset charset) { - writtenFiles.add(relativePath.toString()); + public void writeText(Path projectRoot, Path relativePath, String content, Charset charset) { + writtenFiles.add(relativePath); + } + } + + static class FakeArchiverPort implements ProjectArchiverPort { + Path lastProjectRoot; + String lastArtifactId; + + @Override + public Path archive(Path projectRoot, String artifactId) { + this.lastProjectRoot = projectRoot; + this.lastArtifactId = artifactId; + return projectRoot.getParent().resolve(artifactId + ".zip"); } } -} \ No newline at end of file +} From 19863fb818baaefe0d039743e0a90b6ca0346a1f Mon Sep 17 00:00:00 2001 From: bsayli Date: Thu, 2 Oct 2025 19:40:51 -0600 Subject: [PATCH 12/74] refactor: introduce profiles-based config and restructure adapters - Switched to `codegen.profiles` with template-base-path support - Unified ArtifactProperties to resolve full template paths - Moved ArtifactGenerator to adapter.out.spi (as SPI contract) - Refactored GitIgnore, MavenPom, Readme, SpringBootApplicationYaml adapters to new profiles - Introduced InfrastructureException and ProfileConfigurationException for bootstrap layer - Reorganized package structure under adapter.out.profile.springboot.maven.java --- .../adapter/error/AdapterException.java | 33 +---- ... SpringBootMavenJavaArtifactsAdapter.java} | 6 +- .../out/generator/build/MavenPomAdapter.java | 33 ----- .../generator/config/ConfigFilesAdapter.java | 33 ----- .../maven/java/build/MavenPomAdapter.java | 136 ++++++++++++++++++ .../java/config/ApplicationYamlAdapter.java | 80 +++++++++++ .../maven/java}/docs/ReadmeAdapter.java | 35 +++-- .../java}/source/SourceScaffolderAdapter.java | 4 +- .../java}/test/TestScaffolderAdapter.java | 4 +- .../maven/java}/vcs/GitIgnoreAdapter.java | 36 +++-- .../java}/wrapper/MavenWrapperAdapter.java | 4 +- .../{generator => spi}/ArtifactGenerator.java | 2 +- .../createproject/ProjectBlueprintMapper.java | 74 ++++++---- .../config/CodegenArtifactsProperties.java | 14 -- .../config/CodegenProfilesProperties.java | 44 ++++++ .../error/InfrastructureException.java | 38 +++++ .../error/ProfileConfigurationException.java | 15 ++ .../factory/ProjectBlueprintFactory.java | 26 ---- src/main/resources/application.yml | 37 +++-- src/main/resources/messages.properties | 3 + .../maven/java/README.md.ftl} | 0 .../springboot/maven/java/application.yml.ftl | 8 ++ .../springboot/maven/java/gitignore.ftl | 76 ++++++++++ .../springboot/maven/java/pom.xml.ftl | 50 +++++++ 24 files changed, 583 insertions(+), 208 deletions(-) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{StandardArtifactsAdapter.java => SpringBootMavenJavaArtifactsAdapter.java} (78%) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => profile/springboot/maven/java}/docs/ReadmeAdapter.java (81%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => profile/springboot/maven/java}/source/SourceScaffolderAdapter.java (83%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => profile/springboot/maven/java}/test/TestScaffolderAdapter.java (83%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => profile/springboot/maven/java}/vcs/GitIgnoreAdapter.java (67%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => profile/springboot/maven/java}/wrapper/MavenWrapperAdapter.java (83%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{generator => spi}/ArtifactGenerator.java (87%) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java rename src/main/resources/templates/{readme.ftl => springboot/maven/java/README.md.ftl} (100%) create mode 100644 src/main/resources/templates/springboot/maven/java/application.yml.ftl create mode 100644 src/main/resources/templates/springboot/maven/java/gitignore.ftl create mode 100644 src/main/resources/templates/springboot/maven/java/pom.xml.ftl diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java index e1095e9..ac3ca59 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java @@ -1,38 +1,13 @@ package io.github.bsayli.codegen.initializr.adapter.error; -import java.io.Serial; - -public abstract class AdapterException extends RuntimeException { - @Serial private static final long serialVersionUID = 1L; - - private final String messageKey; - private final transient Object[] args; +import io.github.bsayli.codegen.initializr.bootstrap.error.InfrastructureException; +public abstract class AdapterException extends InfrastructureException { protected AdapterException(String messageKey, Object... args) { - super(messageKey); - this.messageKey = messageKey; - this.args = args; + super(messageKey, args); } protected AdapterException(String messageKey, Throwable cause, Object... args) { - super(messageKey, cause); - this.messageKey = messageKey; - this.args = args; - } - - protected static Object[] prepend(Object first, Object... rest) { - int extra = (rest == null) ? 0 : rest.length; - Object[] merged = new Object[1 + extra]; - merged[0] = first; - if (extra > 0) System.arraycopy(rest, 0, merged, 1, extra); - return merged; - } - - public String getMessageKey() { - return messageKey; - } - - public Object[] getArgs() { - return args; + super(messageKey, cause, args); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java similarity index 78% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java index ca351b4..1291379 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/StandardArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; @@ -8,11 +8,11 @@ import java.util.Comparator; import java.util.List; -public class StandardArtifactsAdapter implements ProjectArtifactsPort { +public class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { private final List artifactGenerators; - public StandardArtifactsAdapter(List artifactGenerators) { + public SpringBootMavenJavaArtifactsAdapter(List artifactGenerators) { this.artifactGenerators = artifactGenerators; } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java deleted file mode 100644 index c9e58f3..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/build/MavenPomAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.build; - -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.util.List; - -public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { - - private static final int ORDER = 10; - private static final String GENERATOR_NAME = "maven-pom"; - - @Override - public GeneratedFile generate(ProjectBlueprint blueprint) { - throw new UnsupportedOperationException("MavenPomAdapter.generate not implemented yet"); - } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return List.of(generate(blueprint)); - } - - @Override - public int order() { - return ORDER; - } - - @Override - public String name() { - return GENERATOR_NAME; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java deleted file mode 100644 index a1e3401..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/config/ConfigFilesAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.config; - -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public final class ConfigFilesAdapter implements ConfigFilesPort, ArtifactGenerator { - - private static final int ORDER = 50; - private static final String NAME = "config-files"; - - @Override - public Iterable generate(BuildOptions options) { - throw new UnsupportedOperationException("ConfigFilesAdapter.generate not implemented yet"); - } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint.getBuildOptions()); - } - - @Override - public int order() { - return ORDER; - } - - @Override - public String name() { - return NAME; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java new file mode 100644 index 0000000..bb05229 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -0,0 +1,136 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build; + +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { + + public static final String PROFILE_KEY = "springboot-maven-java"; + private static final int ORDER = 10; + private static final String NAME = "maven-pom"; + private static final String KEY_GROUP_ID = "groupId"; + private static final String KEY_ARTIFACT_ID = "artifactId"; + private static final String KEY_JAVA_VERSION = "javaVersion"; + private static final String KEY_SPRING_BOOT_VER = "springBootVersion"; + private static final String KEY_DEPENDENCIES = "dependencies"; + private static final String KEY_PROJECT_NAME = "projectName"; + private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; + + private static final Map CORE_STARTER = + Map.of(KEY_GROUP_ID, "org.springframework.boot", KEY_ARTIFACT_ID, "spring-boot-starter"); + + private static final Map TEST_STARTER = + Map.of( + KEY_GROUP_ID, + "org.springframework.boot", + KEY_ARTIFACT_ID, + "spring-boot-starter-test", + "scope", + "test"); + + private final TemplateRenderer renderer; + private final CodegenProfilesProperties profiles; + private final String profileKey; + + public MavenPomAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + this(renderer, profiles, PROFILE_KEY); + } + + public MavenPomAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { + this.renderer = renderer; + this.profiles = profiles; + this.profileKey = profileKey; + } + + @Override + public GeneratedFile generate(ProjectBlueprint blueprint) { + ArtifactProperties cfg = cfg(); + Path outPath = Path.of(cfg.outputPath()); + String template = cfg.template(); + Map model = buildModel(blueprint); + return renderer.renderUtf8(outPath, template, model); + } + + @Override + public boolean supports(ProjectBlueprint bp) { + ArtifactProperties cfg = cfg(); + return cfg.enabled(); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return List.of(generate(blueprint)); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } + + private ArtifactProperties cfg() { + return profiles.artifact(profileKey, NAME); + } + + private Map buildModel(ProjectBlueprint bp) { + ProjectIdentity id = bp.getIdentity(); + PlatformTarget pt = bp.getPlatformTarget(); + + List> dependencies = new ArrayList<>(); + dependencies.add(CORE_STARTER); + dependencies.addAll(mapUserDependencies(bp.getDependencies())); + dependencies.add(TEST_STARTER); + + return Map.ofEntries( + entry(KEY_GROUP_ID, id.groupId().value()), + entry(KEY_ARTIFACT_ID, id.artifactId().value()), + entry(KEY_JAVA_VERSION, pt.java().asString()), + entry(KEY_SPRING_BOOT_VER, pt.springBoot().value()), + entry(KEY_PROJECT_NAME, bp.getName().value()), + entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), + entry(KEY_DEPENDENCIES, dependencies)); + } + + private List> mapUserDependencies(Dependencies userDependencies) { + if (userDependencies == null || userDependencies.isEmpty()) return List.of(); + List> list = new ArrayList<>(userDependencies.asList().size()); + for (Dependency d : userDependencies.asList()) { + list.add(toMap(d)); + } + return list; + } + + private Map toMap(Dependency d) { + Map m = new LinkedHashMap<>(); + m.put(KEY_GROUP_ID, d.coordinates().groupId().value()); + m.put(KEY_ARTIFACT_ID, d.coordinates().artifactId().value()); + if (d.version() != null && !d.version().value().isBlank()) { + m.put("version", d.version().value()); + } + if (d.scope() != null && !d.scope().value().isBlank()) { + m.put("scope", d.scope().value()); + } + return m; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java new file mode 100644 index 0000000..b0b5d65 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -0,0 +1,80 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config; + +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public final class ApplicationYamlAdapter implements ConfigFilesPort, ArtifactGenerator { + + public static final String PROFILE_KEY = "springboot-maven-java"; + private static final int ORDER = 50; + private static final String NAME = "application-yaml"; + private static final String KEY_FRAMEWORK = "framework"; + private static final String KEY_BUILD_TOOL = "buildTool"; + private static final String KEY_LANGUAGE = "language"; + + private final TemplateRenderer renderer; + private final CodegenProfilesProperties profiles; + private final String profileKey; + + public ApplicationYamlAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + this(renderer, profiles, PROFILE_KEY); + } + + public ApplicationYamlAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { + this.renderer = renderer; + this.profiles = profiles; + this.profileKey = profileKey; + } + + @Override + public Iterable generate(BuildOptions options) { + ArtifactProperties cfg = cfg(); + Path outPath = Path.of(cfg.outputPath()); + String template = cfg.template(); + Map model = buildModel(options); + return List.of(renderer.renderUtf8(outPath, template, model)); + } + + @Override + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint.getBuildOptions()); + } + + @Override + public boolean supports(ProjectBlueprint blueprint) { + return cfg().enabled(); + } + + @Override + public int order() { + return ORDER; + } + + @Override + public String name() { + return NAME; + } + + private ArtifactProperties cfg() { + return profiles.artifact(profileKey, NAME); + } + + private Map buildModel(BuildOptions options) { + return Map.ofEntries( + entry(KEY_FRAMEWORK, options.framework().name()), + entry(KEY_BUILD_TOOL, options.buildTool().name()), + entry(KEY_LANGUAGE, options.language().name())); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java similarity index 81% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index df68b98..304933b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -1,12 +1,12 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.docs; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs; import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenArtifactsProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; @@ -20,9 +20,9 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { + public static final String PROFILE_KEY = "springboot-maven-java"; private static final int ORDER = 90; private static final String NAME = "readme"; - // Model keys private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; @@ -37,20 +37,23 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { private static final String KEY_DEPENDENCIES = "dependencies"; private final TemplateRenderer renderer; - private final ArtifactProperties cfg; + private final CodegenProfilesProperties profiles; + private final String profileKey; - public ReadmeAdapter(TemplateRenderer renderer, CodegenArtifactsProperties props) { - this.renderer = renderer; - this.cfg = props.readme(); + public ReadmeAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + this(renderer, profiles, PROFILE_KEY); } - @Override - public boolean supports(ProjectBlueprint bp) { - return cfg.enabled(); + public ReadmeAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { + this.renderer = renderer; + this.profiles = profiles; + this.profileKey = profileKey; } @Override public GeneratedFile generate(ProjectBlueprint blueprint) { + ArtifactProperties cfg = cfg(); Path outPath = Path.of(cfg.outputPath()); String template = cfg.template(); Map model = buildModel(blueprint); @@ -62,6 +65,12 @@ public Iterable generateFiles(ProjectBlueprint blueprin return List.of(generate(blueprint)); } + @Override + public boolean supports(ProjectBlueprint bp) { + ArtifactProperties cfg = cfg(); + return cfg.enabled(); + } + @Override public int order() { return ORDER; @@ -72,6 +81,10 @@ public String name() { return NAME; } + private ArtifactProperties cfg() { + return profiles.artifact(profileKey, NAME); + } + private Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); BuildOptions bo = bp.getBuildOptions(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java similarity index 83% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index 2e1615c..d1394b8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -1,6 +1,6 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.source; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java similarity index 83% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 178d788..1cdb764 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,6 +1,6 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.test; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java similarity index 67% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index 2bdd3e7..34dd56c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,10 +1,10 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.vcs; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenArtifactsProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; @@ -14,26 +14,28 @@ public final class GitIgnoreAdapter implements GitIgnorePort, ArtifactGenerator { + public static final String PROFILE_KEY = "springboot-maven-java"; private static final int ORDER = 20; private static final String NAME = "gitignore"; - private static final String KEY_IGNORE_LIST = "ignoreList"; - private final TemplateRenderer renderer; - private final ArtifactProperties cfg; + private final CodegenProfilesProperties profiles; + private final String profileKey; - public GitIgnoreAdapter(TemplateRenderer renderer, CodegenArtifactsProperties props) { - this.renderer = renderer; - this.cfg = props.gitignore(); + public GitIgnoreAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + this(renderer, profiles, PROFILE_KEY); } - @Override - public boolean supports(ProjectBlueprint bp) { - return cfg.enabled(); + public GitIgnoreAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { + this.renderer = renderer; + this.profiles = profiles; + this.profileKey = profileKey; } @Override public GeneratedFile generate(BuildOptions buildOptions) { + ArtifactProperties cfg = cfg(); Path outPath = Path.of(cfg.outputPath()); String template = cfg.template(); Map model = buildModel(buildOptions); @@ -45,6 +47,12 @@ public Iterable generateFiles(ProjectBlueprint bp) { return List.of(generate(bp.getBuildOptions())); } + @Override + public boolean supports(ProjectBlueprint bp) { + ArtifactProperties cfg = cfg(); + return cfg.enabled(); + } + @Override public int order() { return ORDER; @@ -55,6 +63,10 @@ public String name() { return NAME; } + private ArtifactProperties cfg() { + return profiles.artifact(profileKey, NAME); + } + @SuppressWarnings("unused") private Map buildModel(BuildOptions buildOptions) { return Map.of(KEY_IGNORE_LIST, List.of()); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java similarity index 83% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index 0a8ca93..bb15247 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -1,6 +1,6 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator.wrapper; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; -import io.github.bsayli.codegen.initializr.adapter.out.generator.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java similarity index 87% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java index c161ab8..1463b36 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/generator/ArtifactGenerator.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.out.generator; +package io.github.bsayli.codegen.initializr.adapter.out.spi; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java index a5aa545..32401d7 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java @@ -9,37 +9,57 @@ import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyVersion; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; +import java.util.ArrayList; import java.util.List; public class ProjectBlueprintMapper { public ProjectBlueprint toDomain(CreateProjectCommand c) { - List deps = - c.dependencies().stream() - .map( - d -> - new Dependency( - new DependencyCoordinates( - new GroupId(d.groupId()), new ArtifactId(d.artifactId())), - (d.version() == null || d.version().isBlank()) - ? null - : new DependencyVersion(d.version()), - (d.scope() == null || d.scope().isBlank()) - ? null - : DependencyScope.valueOf(d.scope().trim().toUpperCase()))) - .toList(); - - var depsWrapper = Dependencies.of(deps); - - return ProjectBlueprintFactory.fromPrimitives( - c.groupId(), - c.artifactId(), - c.projectName(), - c.projectDescription(), - c.packageName(), - c.buildOptions(), - c.preferredJava(), - c.preferredBoot(), - depsWrapper); + ProjectIdentity identity = + new ProjectIdentity(new GroupId(c.groupId()), new ArtifactId(c.artifactId())); + + ProjectName name = new ProjectName(c.projectName()); + ProjectDescription description = new ProjectDescription(c.projectDescription()); + PackageName pkg = new PackageName(c.packageName()); + + PlatformTarget target = + PlatformTargetSelector.select(c.buildOptions(), c.preferredJava(), c.preferredBoot()); + + Dependencies deps = mapDependencies(c.dependencies()); + + return ProjectBlueprintFactory.of( + identity, name, description, pkg, c.buildOptions(), target, deps); + } + + private Dependencies mapDependencies(List raw) { + if (raw == null || raw.isEmpty()) { + return Dependencies.of(List.of()); + } + + List items = new ArrayList<>(raw.size()); + for (DependencyInput d : raw) { + DependencyVersion version = + (d.version() == null || d.version().isBlank()) + ? null + : new DependencyVersion(d.version()); + + DependencyScope scope = + (d.scope() == null || d.scope().isBlank()) + ? null + : DependencyScope.valueOf(d.scope().trim().toUpperCase()); + + items.add( + new Dependency( + new DependencyCoordinates(new GroupId(d.groupId()), new ArtifactId(d.artifactId())), + version, + scope)); + } + return Dependencies.of(items); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java deleted file mode 100644 index 182474c..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenArtifactsProperties.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.bsayli.codegen.initializr.bootstrap.config; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; -import org.springframework.validation.annotation.Validated; - -@Validated -@ConfigurationProperties(prefix = "codegen.artifacts") -public record CodegenArtifactsProperties( - @Valid @NotNull @NestedConfigurationProperty ArtifactProperties gitignore, - @Valid @NotNull @NestedConfigurationProperty ArtifactProperties mavenPom, - @Valid @NotNull @NestedConfigurationProperty ArtifactProperties readme) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java new file mode 100644 index 0000000..945df5b --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -0,0 +1,44 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Validated +@ConfigurationProperties(prefix = "codegen") +public record CodegenProfilesProperties(@Valid @NotNull Map profiles) { + + public ArtifactProperties artifact(String profileKey, String artifactKey) { + var profile = requireProfile(profileKey); + var raw = requireArtifact(profileKey, profile, artifactKey); + String fullTemplate = profile.templateBasePath() + "/" + raw.template(); + return new ArtifactProperties(raw.enabled(), fullTemplate, raw.outputPath()); + } + + private ArtifactProperties requireArtifact( + String profileKey, ProfileProperties profile, String artifactKey) { + var artifact = profile.artifacts().get(artifactKey); + if (artifact == null) { + throw new ProfileConfigurationException( + ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey, profileKey); + } + return artifact; + } + + private ProfileProperties requireProfile(String profileKey) { + var profile = profiles.get(profileKey); + if (profile == null) { + throw new ProfileConfigurationException( + ProfileConfigurationException.KEY_PROFILE_NOT_FOUND, profileKey); + } + return profile; + } + + public record ProfileProperties( + @NotBlank String templateBasePath, + @Valid @NotNull Map artifacts) {} +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java new file mode 100644 index 0000000..5df6cfc --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java @@ -0,0 +1,38 @@ +package io.github.bsayli.codegen.initializr.bootstrap.error; + +import java.io.Serial; + +public abstract class InfrastructureException extends RuntimeException { + @Serial private static final long serialVersionUID = 1L; + + private final String messageKey; + private final transient Object[] args; + + protected InfrastructureException(String messageKey, Object... args) { + super(messageKey); + this.messageKey = messageKey; + this.args = args; + } + + protected InfrastructureException(String messageKey, Throwable cause, Object... args) { + super(messageKey, cause); + this.messageKey = messageKey; + this.args = args; + } + + protected static Object[] prepend(Object first, Object... rest) { + int extra = (rest == null) ? 0 : rest.length; + Object[] merged = new Object[1 + extra]; + merged[0] = first; + if (extra > 0) System.arraycopy(rest, 0, merged, 1, extra); + return merged; + } + + public String getMessageKey() { + return messageKey; + } + + public Object[] getArgs() { + return args; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java new file mode 100644 index 0000000..165955f --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java @@ -0,0 +1,15 @@ +package io.github.bsayli.codegen.initializr.bootstrap.error; + +public final class ProfileConfigurationException extends InfrastructureException { + public static final String KEY_PROFILE_NOT_FOUND = "bootstrap.profile.not.found"; + public static final String KEY_ARTIFACT_NOT_FOUND = "bootstrap.artifact.not.found"; + public static final String KEY_TEMPLATE_BASE_MISSING = "bootstrap.template.base.missing"; + + public ProfileConfigurationException(String key, Object... args) { + super(key, args); + } + + public ProfileConfigurationException(String key, Throwable cause, Object... args) { + super(key, cause, args); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java index c945a77..2d8f69d 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactory.java @@ -10,18 +10,13 @@ import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; -import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; -import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.policy.tech.CompatibilityPolicy; -import io.github.bsayli.codegen.initializr.domain.policy.tech.PlatformTargetSelector; import java.util.Arrays; import java.util.List; @@ -92,27 +87,6 @@ public static ProjectBlueprint of( Dependencies.of(Arrays.asList(deps))); } - public static ProjectBlueprint fromPrimitives( - String groupId, - String artifactId, - String projectName, - String projectDescription, - String packageName, - BuildOptions buildOptions, - JavaVersion preferredJava, - SpringBootVersion preferredBoot, - Dependencies dependencies) { - - var identity = new ProjectIdentity(new GroupId(groupId), new ArtifactId(artifactId)); - var name = new ProjectName(projectName); - var description = new ProjectDescription(projectDescription); - var pkg = new PackageName(packageName); - - var target = PlatformTargetSelector.select(buildOptions, preferredJava, preferredBoot); - - return of(identity, name, description, pkg, buildOptions, target, dependencies); - } - private static void ensureNotNull(Object value, ErrorCode code) { if (value == null) throw new DomainViolationException(code); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c8f2f53..7757c53 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,19 +4,30 @@ spring: codegen: - artifacts: - gitignore: - enabled: true - template: gitignore.ftl - output-path: .gitignore - maven-pom: - enabled: true - template: pom.ftl - output-path: pom.xml - readme: - enabled: true - template: readme.ftl - output-path: README.md + profiles: + springboot-maven-java: + template-base-path: springboot/maven/java + artifacts: + gitignore: + enabled: true + template: .gitignore.ftl + output-path: .gitignore + + pom: + enabled: true + template: pom.xml.ftl + output-path: pom.xml + + application-yaml: + enabled: true + template: application.yml.ftl + output-path: src/main/resources/application.yml + + readme: + enabled: true + template: README.md.ftl + output-path: README.md + templating: encoding: UTF-8 diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 9c41e6a..ed29c7b 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -71,3 +71,6 @@ file.charset.not.blank=Charset must not be null # --- ADAPTER (TEMPLATING) --- # ================================ adapter.template.render.failed=Failed to render template ''{0}''. +bootstrap.profile.not.found=Unknown profile: {0} +bootstrap.artifact.not.found=Unknown artifact ''{0}'' for profile: {1} +bootstrap.template.base.missing=template-base-path must be set for profile: {0} \ No newline at end of file diff --git a/src/main/resources/templates/readme.ftl b/src/main/resources/templates/springboot/maven/java/README.md.ftl similarity index 100% rename from src/main/resources/templates/readme.ftl rename to src/main/resources/templates/springboot/maven/java/README.md.ftl diff --git a/src/main/resources/templates/springboot/maven/java/application.yml.ftl b/src/main/resources/templates/springboot/maven/java/application.yml.ftl new file mode 100644 index 0000000..3525bb9 --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/application.yml.ftl @@ -0,0 +1,8 @@ +spring: +application: +name: ${projectName} +# server: +# port: 8080 +# logging: +# level: +# root: INFO \ No newline at end of file diff --git a/src/main/resources/templates/springboot/maven/java/gitignore.ftl b/src/main/resources/templates/springboot/maven/java/gitignore.ftl new file mode 100644 index 0000000..5d94f6b --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/gitignore.ftl @@ -0,0 +1,76 @@ +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Eclipse / IntelliJ / VSCode IDE # +################################## +.classpath +.project +.settings/ +.idea/ +*.iws +*.iml +*.ipr + +# Build directories # +##################### +target/ +build/ +bin/ + +# Maven specific # +################## +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +pom.xml.bak +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/ + +# Generated source folders # +############################ +generated-sources/ +generated-classes/ + +# Add placeholders for project-specific ignores (optional) +<#if ignoreList?has_content && ignoreList?size gt 0> + <#list ignoreList as pattern> + ${pattern} + + \ No newline at end of file diff --git a/src/main/resources/templates/springboot/maven/java/pom.xml.ftl b/src/main/resources/templates/springboot/maven/java/pom.xml.ftl new file mode 100644 index 0000000..50ad8eb --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/pom.xml.ftl @@ -0,0 +1,50 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + ${springBootVersion} + + + + ${groupId} + ${artifactId} + 0.0.1-SNAPSHOT + + ${projectName} + ${projectDescription} + + + ${javaVersion} + + + + <#list dependencies as d> + + ${d.groupId} + ${d.artifactId} + <#if d.version?? && d.version?has_content> + ${d.version} + + <#if d.scope?? && d.scope?has_content> + ${d.scope} + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file From 1478c50a7ebbff664ab5cfa4cfcb64632a4592dc Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 5 Oct 2025 20:19:08 -0600 Subject: [PATCH 13/74] refactor: introduce profile-based artifact selection with ProfileType - Added ProfileBasedArtifactsSelector to centralize artifact port selection - Integrated type-safe ProfileType enum instead of string-based profile keys - Added UnsupportedProfileTypeException and ArtifactsPortNotFoundException - Updated CreateProjectService to use ProjectArtifactsSelector for orchestration - Updated adapters (MavenPomAdapter, ApplicationYamlAdapter, ReadmeAdapter, GitIgnoreAdapter) to reference fixed ProfileType and remove external profileKey passing - Improved cohesion between bootstrap config and adapter layer - Ensured all adapter exceptions extend AdapterException for consistent localization Result: Profile-based routing and adapter modularity are now fully type-safe, eliminating magic strings and strengthening adapter isolation. --- .../{ => exception}/AdapterException.java | 2 +- .../ArtifactsPortNotFoundException.java | 19 +++++++++ .../TemplateRenderingException.java | 7 ++-- .../UnsupportedProfileTypeException.java | 12 ++++++ .../out/ProfileBasedArtifactsSelector.java | 33 +++++++++++++++ .../maven/java/build/MavenPomAdapter.java | 27 ++++-------- .../java/config/ApplicationYamlAdapter.java | 12 ++---- .../maven/java/docs/ReadmeAdapter.java | 13 ++---- .../maven/java/vcs/GitIgnoreAdapter.java | 12 ++---- .../FreeMarkerTemplateRenderer.java | 1 + .../adapter/profile/ProfileType.java | 35 ++++++++++++++++ .../port/out/ProjectArtifactsSelector.java | 7 ++++ .../createproject/CreateProjectService.java | 11 +++-- .../config/CodegenProfilesProperties.java | 31 +++++++------- .../bootstrap/wiring/ArtifactsWiring.java | 41 +++++++++++++++++++ src/main/resources/messages.properties | 4 +- .../CreateProjectServiceTest.java | 19 ++++++++- 17 files changed, 213 insertions(+), 73 deletions(-) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/error/{ => exception}/AdapterException.java (84%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java rename src/main/java/io/github/bsayli/codegen/initializr/adapter/{out/templating => error/exception}/TemplateRenderingException.java (71%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnsupportedProfileTypeException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsSelector.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java similarity index 84% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java index ac3ca59..fbb56e8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/AdapterException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.error; +package io.github.bsayli.codegen.initializr.adapter.error.exception; import io.github.bsayli.codegen.initializr.bootstrap.error.InfrastructureException; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java new file mode 100644 index 0000000..74cccaa --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java @@ -0,0 +1,19 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; + +@SuppressWarnings("java:S110") +public final class ArtifactsPortNotFoundException extends AdapterException { + + private static final String KEY = "adapter.artifacts.port.not.found"; + private final ProfileType profileType; + + public ArtifactsPortNotFoundException(ProfileType profileType) { + super(KEY, profileType.name()); + this.profileType = profileType; + } + + public ProfileType getProfileType() { + return profileType; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/TemplateRenderingException.java similarity index 71% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/TemplateRenderingException.java index 93ffedb..15f977d 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/TemplateRenderingException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/TemplateRenderingException.java @@ -1,9 +1,8 @@ -package io.github.bsayli.codegen.initializr.adapter.out.templating; - -import io.github.bsayli.codegen.initializr.adapter.error.AdapterException; +package io.github.bsayli.codegen.initializr.adapter.error.exception; +@SuppressWarnings("java:S110") public final class TemplateRenderingException extends AdapterException { - public static final String KEY = "adapter.template.render.failed"; + private static final String KEY = "adapter.template.render.failed"; private final String templateName; public TemplateRenderingException(String templateName, Object... args) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnsupportedProfileTypeException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnsupportedProfileTypeException.java new file mode 100644 index 0000000..34502ed --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnsupportedProfileTypeException.java @@ -0,0 +1,12 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; + +@SuppressWarnings("java:S110") +public final class UnsupportedProfileTypeException extends AdapterException { + private static final String KEY = "adapter.profile.unsupported"; + + public UnsupportedProfileTypeException(BuildOptions options) { + super(KEY, options.framework(), options.buildTool(), options.language()); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java new file mode 100644 index 0000000..3de72c9 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java @@ -0,0 +1,33 @@ +package io.github.bsayli.codegen.initializr.adapter.out; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactsPortNotFoundException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.UnsupportedProfileTypeException; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import java.util.Map; + +public final class ProfileBasedArtifactsSelector implements ProjectArtifactsSelector { + + private final Map registry; + + public ProfileBasedArtifactsSelector(Map registry) { + this.registry = registry; + } + + @Override + public ProjectArtifactsPort select(BuildOptions options) { + ProfileType type = ProfileType.from(options); + if (type == null) { + throw new UnsupportedProfileTypeException(options); + } + + ProjectArtifactsPort port = registry.get(type); + if (port == null) { + throw new ArtifactsPortNotFoundException(type); + } + + return port; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index bb05229..556d33e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -4,6 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; @@ -21,9 +22,10 @@ public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { - public static final String PROFILE_KEY = "springboot-maven-java"; + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; private static final int ORDER = 10; private static final String NAME = "maven-pom"; + private static final String KEY_GROUP_ID = "groupId"; private static final String KEY_ARTIFACT_ID = "artifactId"; private static final String KEY_JAVA_VERSION = "javaVersion"; @@ -46,17 +48,10 @@ public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { private final TemplateRenderer renderer; private final CodegenProfilesProperties profiles; - private final String profileKey; public MavenPomAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { - this(renderer, profiles, PROFILE_KEY); - } - - public MavenPomAdapter( - TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { this.renderer = renderer; this.profiles = profiles; - this.profileKey = profileKey; } @Override @@ -70,8 +65,7 @@ public GeneratedFile generate(ProjectBlueprint blueprint) { @Override public boolean supports(ProjectBlueprint bp) { - ArtifactProperties cfg = cfg(); - return cfg.enabled(); + return cfg().enabled(); } @Override @@ -90,7 +84,7 @@ public String name() { } private ArtifactProperties cfg() { - return profiles.artifact(profileKey, NAME); + return profiles.artifact(PROFILE, NAME); } private Map buildModel(ProjectBlueprint bp) { @@ -115,9 +109,7 @@ private Map buildModel(ProjectBlueprint bp) { private List> mapUserDependencies(Dependencies userDependencies) { if (userDependencies == null || userDependencies.isEmpty()) return List.of(); List> list = new ArrayList<>(userDependencies.asList().size()); - for (Dependency d : userDependencies.asList()) { - list.add(toMap(d)); - } + for (Dependency d : userDependencies.asList()) list.add(toMap(d)); return list; } @@ -125,12 +117,9 @@ private Map toMap(Dependency d) { Map m = new LinkedHashMap<>(); m.put(KEY_GROUP_ID, d.coordinates().groupId().value()); m.put(KEY_ARTIFACT_ID, d.coordinates().artifactId().value()); - if (d.version() != null && !d.version().value().isBlank()) { + if (d.version() != null && !d.version().value().isBlank()) m.put("version", d.version().value()); - } - if (d.scope() != null && !d.scope().value().isBlank()) { - m.put("scope", d.scope().value()); - } + if (d.scope() != null && !d.scope().value().isBlank()) m.put("scope", d.scope().value()); return m; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index b0b5d65..4ee0898 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -4,6 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; @@ -16,7 +17,7 @@ public final class ApplicationYamlAdapter implements ConfigFilesPort, ArtifactGenerator { - public static final String PROFILE_KEY = "springboot-maven-java"; + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; private static final int ORDER = 50; private static final String NAME = "application-yaml"; private static final String KEY_FRAMEWORK = "framework"; @@ -25,17 +26,10 @@ public final class ApplicationYamlAdapter implements ConfigFilesPort, ArtifactGe private final TemplateRenderer renderer; private final CodegenProfilesProperties profiles; - private final String profileKey; public ApplicationYamlAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { - this(renderer, profiles, PROFILE_KEY); - } - - public ApplicationYamlAdapter( - TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { this.renderer = renderer; this.profiles = profiles; - this.profileKey = profileKey; } @Override @@ -68,7 +62,7 @@ public String name() { } private ArtifactProperties cfg() { - return profiles.artifact(profileKey, NAME); + return profiles.artifact(PROFILE, NAME); } private Map buildModel(BuildOptions options) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 304933b..9871a04 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -4,6 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; @@ -20,10 +21,9 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { - public static final String PROFILE_KEY = "springboot-maven-java"; + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; private static final int ORDER = 90; private static final String NAME = "readme"; - // Model keys private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; private static final String KEY_GROUP_ID = "groupId"; @@ -38,17 +38,10 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { private final TemplateRenderer renderer; private final CodegenProfilesProperties profiles; - private final String profileKey; public ReadmeAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { - this(renderer, profiles, PROFILE_KEY); - } - - public ReadmeAdapter( - TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { this.renderer = renderer; this.profiles = profiles; - this.profileKey = profileKey; } @Override @@ -82,7 +75,7 @@ public String name() { } private ArtifactProperties cfg() { - return profiles.artifact(profileKey, NAME); + return profiles.artifact(PROFILE, NAME); } private Map buildModel(ProjectBlueprint bp) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index 34dd56c..ef27c0e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -2,6 +2,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; @@ -14,23 +15,16 @@ public final class GitIgnoreAdapter implements GitIgnorePort, ArtifactGenerator { - public static final String PROFILE_KEY = "springboot-maven-java"; + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; private static final int ORDER = 20; private static final String NAME = "gitignore"; private static final String KEY_IGNORE_LIST = "ignoreList"; private final TemplateRenderer renderer; private final CodegenProfilesProperties profiles; - private final String profileKey; public GitIgnoreAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { - this(renderer, profiles, PROFILE_KEY); - } - - public GitIgnoreAdapter( - TemplateRenderer renderer, CodegenProfilesProperties profiles, String profileKey) { this.renderer = renderer; this.profiles = profiles; - this.profileKey = profileKey; } @Override @@ -64,7 +58,7 @@ public String name() { } private ArtifactProperties cfg() { - return profiles.artifact(profileKey, NAME); + return profiles.artifact(PROFILE, NAME); } @SuppressWarnings("unused") diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java index 96ff848..0571497 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/templating/FreeMarkerTemplateRenderer.java @@ -2,6 +2,7 @@ import freemarker.template.Configuration; import freemarker.template.Template; +import io.github.bsayli.codegen.initializr.adapter.error.exception.TemplateRenderingException; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.io.StringWriter; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java new file mode 100644 index 0000000..7dc60df --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java @@ -0,0 +1,35 @@ +package io.github.bsayli.codegen.initializr.adapter.profile; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; + +public enum ProfileType { + SPRINGBOOT_MAVEN_JAVA(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + private final Framework framework; + private final BuildTool buildTool; + private final Language language; + + ProfileType(Framework framework, BuildTool buildTool, Language language) { + this.framework = framework; + this.buildTool = buildTool; + this.language = language; + } + + public static ProfileType from(BuildOptions o) { + for (ProfileType p : values()) { + if (p.framework == o.framework() + && p.buildTool == o.buildTool() + && p.language == o.language()) { + return p; + } + } + return null; + } + + public String key() { + return (framework.name() + "-" + buildTool.name() + "-" + language.name()).toLowerCase(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsSelector.java new file mode 100644 index 0000000..4aa85de --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/ProjectArtifactsSelector.java @@ -0,0 +1,7 @@ +package io.github.bsayli.codegen.initializr.application.port.out; + +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; + +public interface ProjectArtifactsSelector { + ProjectArtifactsPort select(BuildOptions options); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java index 6e1e559..245a498 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -3,6 +3,7 @@ import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; @@ -13,19 +14,19 @@ public class CreateProjectService implements CreateProjectUseCase { private final ProjectBlueprintMapper mapper; private final ProjectRootPort rootPort; - private final ProjectArtifactsPort artifactsPort; + private final ProjectArtifactsSelector artifactsSelector; private final ProjectWriterPort writerPort; private final ProjectArchiverPort archiverPort; public CreateProjectService( ProjectBlueprintMapper mapper, ProjectRootPort rootPort, - ProjectArtifactsPort artifactsPort, + ProjectArtifactsSelector artifactsSelector, ProjectWriterPort writerPort, ProjectArchiverPort archiverPort) { this.mapper = mapper; this.rootPort = rootPort; - this.artifactsPort = artifactsPort; + this.artifactsSelector = artifactsSelector; this.writerPort = writerPort; this.archiverPort = archiverPort; } @@ -38,7 +39,9 @@ public CreateProjectResult execute(CreateProjectCommand command) { rootPort.prepareRoot( command.targetDirectory(), bp.getIdentity().artifactId().value(), FAIL_IF_EXISTS); - var files = artifactsPort.generate(bp); + ProjectArtifactsPort port = artifactsSelector.select(bp.getBuildOptions()); + var files = port.generate(bp); + writerPort.write(projectRoot, files); String baseName = bp.getIdentity().artifactId().value(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 945df5b..74a1016 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -1,5 +1,6 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -12,30 +13,30 @@ @ConfigurationProperties(prefix = "codegen") public record CodegenProfilesProperties(@Valid @NotNull Map profiles) { - public ArtifactProperties artifact(String profileKey, String artifactKey) { - var profile = requireProfile(profileKey); - var raw = requireArtifact(profileKey, profile, artifactKey); - String fullTemplate = profile.templateBasePath() + "/" + raw.template(); + public ArtifactProperties artifact(ProfileType profile, String artifactKey) { + var p = requireProfile(profile); + var raw = requireArtifact(profile, p, artifactKey); + String fullTemplate = p.templateBasePath() + "/" + raw.template(); return new ArtifactProperties(raw.enabled(), fullTemplate, raw.outputPath()); } - private ArtifactProperties requireArtifact( - String profileKey, ProfileProperties profile, String artifactKey) { - var artifact = profile.artifacts().get(artifactKey); - if (artifact == null) { + ProfileProperties requireProfile(ProfileType profile) { + var key = profile.key(); + var p = profiles.get(key); + if (p == null) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey, profileKey); + ProfileConfigurationException.KEY_PROFILE_NOT_FOUND, key); } - return artifact; + return p; } - private ProfileProperties requireProfile(String profileKey) { - var profile = profiles.get(profileKey); - if (profile == null) { + ArtifactProperties requireArtifact(ProfileType profile, ProfileProperties p, String artifactKey) { + var a = p.artifacts().get(artifactKey); + if (a == null) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_PROFILE_NOT_FOUND, profileKey); + ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey, profile.key()); } - return profile; + return a; } public record ProfileProperties( diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java new file mode 100644 index 0000000..108aa7f --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java @@ -0,0 +1,41 @@ +package io.github.bsayli.codegen.initializr.bootstrap.wiring; + +import io.github.bsayli.codegen.initializr.adapter.out.ProfileBasedArtifactsSelector; +import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; +import java.util.List; +import java.util.Map; +import org.springframework.context.annotation.Bean; + +// @Configuration +class ArtifactsWiring { + + @Bean + ProjectArtifactsPort springbootMavenJavaArtifactsPort( + TemplateRenderer renderer, CodegenProfilesProperties profiles) { + List gens = + List.of( + new MavenPomAdapter(renderer, profiles), + new GitIgnoreAdapter(renderer, profiles), + new ApplicationYamlAdapter(renderer, profiles), + new ReadmeAdapter(renderer, profiles)); + return new SpringBootMavenJavaArtifactsAdapter(gens); + } + + @Bean + ProjectArtifactsSelector artifactsSelector( + ProjectArtifactsPort springbootMavenJavaArtifactsPort) { + Map reg = + Map.of(ProfileType.SPRINGBOOT_MAVEN_JAVA, springbootMavenJavaArtifactsPort); + return new ProfileBasedArtifactsSelector(reg); + } +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ed29c7b..88860e5 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -73,4 +73,6 @@ file.charset.not.blank=Charset must not be null adapter.template.render.failed=Failed to render template ''{0}''. bootstrap.profile.not.found=Unknown profile: {0} bootstrap.artifact.not.found=Unknown artifact ''{0}'' for profile: {1} -bootstrap.template.base.missing=template-base-path must be set for profile: {0} \ No newline at end of file +bootstrap.template.base.missing=template-base-path must be set for profile: {0} +adapter.artifacts.port.not.found=No artifact generator adapter registered for profile ''{0}''. +adapter.profile.unsupported=Unsupported profile combination: framework={0}, buildTool={1}, language={2} \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index d539b75..75b85a1 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; @@ -55,11 +56,12 @@ void creates_project_root_writes_artifacts_and_archives() { var mapper = new ProjectBlueprintMapper(); var fakeRootPort = new FakeRootPort(); var fakeArtifacts = new FakeArtifactsPort(); + var fakeSelector = new FakeSelector(fakeArtifacts); var fakeWriter = new FakeWriterPort(); var fakeArchiver = new FakeArchiverPort(); var service = - new CreateProjectService(mapper, fakeRootPort, fakeArtifacts, fakeWriter, fakeArchiver); + new CreateProjectService(mapper, fakeRootPort, fakeSelector, fakeWriter, fakeArchiver); var cmd = new CreateProjectCommand( @@ -99,6 +101,8 @@ void creates_project_root_writes_artifacts_and_archives() { .hasSize(expected.size()); } + // ---- fakes ---- + static class FakeRootPort implements ProjectRootPort { Path lastPreparedRoot; ProjectRootExistencePolicy lastPolicy; @@ -111,6 +115,19 @@ public Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistenceP } } + static class FakeSelector implements ProjectArtifactsSelector { + private final ProjectArtifactsPort delegate; + + FakeSelector(ProjectArtifactsPort delegate) { + this.delegate = delegate; + } + + @Override + public ProjectArtifactsPort select(BuildOptions options) { + return delegate; + } + } + static class FakeArtifactsPort implements ProjectArtifactsPort { @Override public Iterable generate(ProjectBlueprint bp) { From a7c0ca3e76058c245e142dab34edaf0b60027331 Mon Sep 17 00:00:00 2001 From: bsayli Date: Sat, 11 Oct 2025 16:54:58 -0600 Subject: [PATCH 14/74] refactor(core): decouple artifact adapters from configuration layer and add factory validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactored ArtifactWiring to use functional factory map with BiFunction - Removed direct dependency on CodegenProfilesProperties from artifact adapters - Injected only ArtifactProperties into adapters for cleaner separation of concerns - Added GeneratorFactoryNotFoundException and ArtifactKeyMismatchException with i18n support - Introduced artifactKey() validation to ensure correct factory–adapter matching - Simplified MavenPomAdapter constructor and isolated configuration access - Improved error localization and consistency across adapter exceptions --- .../adapter/artifact/ArtifactKey.java | 33 ++++++++++++ .../ArtifactKeyMismatchException.java | 12 +++++ .../GeneratorFactoryNotFoundException.java | 12 +++++ .../UnknownArtifactKeyException.java | 12 +++++ .../SpringBootMavenJavaArtifactsAdapter.java | 6 +-- .../maven/java/build/MavenPomAdapter.java | 36 +++---------- .../java/config/ApplicationYamlAdapter.java | 39 ++++---------- .../maven/java/docs/ReadmeAdapter.java | 40 ++++----------- .../java/source/SourceScaffolderAdapter.java | 17 ++----- .../java/test/TestScaffolderAdapter.java | 18 ++----- .../maven/java/vcs/GitIgnoreAdapter.java | 40 ++++----------- .../java/wrapper/MavenWrapperAdapter.java | 17 ++----- .../adapter/out/spi/ArtifactGenerator.java | 8 +-- .../config/ArtifactKeyConverter.java | 16 ++++++ .../bootstrap/config/ArtifactProperties.java | 3 +- .../config/CodegenProfilesProperties.java | 19 +++---- .../bootstrap/config/ProfileProperties.java | 13 +++++ .../bootstrap/wiring/ArtifactsWiring.java | 51 +++++++++++++++---- src/main/resources/application.yml | 15 ++---- src/main/resources/messages.properties | 5 +- 20 files changed, 213 insertions(+), 199 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java new file mode 100644 index 0000000..d2159b2 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java @@ -0,0 +1,33 @@ +package io.github.bsayli.codegen.initializr.adapter.artifact; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.UnknownArtifactKeyException; +import java.util.Arrays; + +public enum ArtifactKey { + POM("pom"), + GITIGNORE("gitignore"), + APPLICATION_YAML("application-yaml"), + README("readme"); + + private final String key; + + ArtifactKey(String key) { + this.key = key; + } + + public static ArtifactKey fromKey(String key) { + return Arrays.stream(values()) + .filter(a -> a.key.equals(key)) + .findFirst() + .orElseThrow(() -> new UnknownArtifactKeyException(key)); + } + + public String key() { + return key; + } + + @Override + public String toString() { + return key; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java new file mode 100644 index 0000000..63b36ee --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java @@ -0,0 +1,12 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; + +@SuppressWarnings("java:S110") +public final class ArtifactKeyMismatchException extends AdapterException { + private static final String KEY = "adapter.generator.key.mismatch"; + + public ArtifactKeyMismatchException(ArtifactKey expected, ArtifactKey actual) { + super(KEY, expected.key(), actual.key()); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java new file mode 100644 index 0000000..0a8941a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java @@ -0,0 +1,12 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; + +@SuppressWarnings("java:S110") +public final class GeneratorFactoryNotFoundException extends AdapterException { + private static final String KEY = "adapter.generator.factory.not.found"; + + public GeneratorFactoryNotFoundException(ArtifactKey artifactKey) { + super(KEY, artifactKey.key()); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java new file mode 100644 index 0000000..4383aec --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java @@ -0,0 +1,12 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import io.github.bsayli.codegen.initializr.bootstrap.error.InfrastructureException; + +@SuppressWarnings("java:S110") +public final class UnknownArtifactKeyException extends InfrastructureException { + private static final String KEY = "adapter.artifact.key.unknown"; + + public UnknownArtifactKeyException(String key) { + super(KEY, key); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java index 1291379..44d1a19 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java @@ -5,7 +5,6 @@ import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; public class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { @@ -19,10 +18,7 @@ public SpringBootMavenJavaArtifactsAdapter(List artifactGener @Override public Iterable generate(ProjectBlueprint blueprint) { List generatedFiles = new ArrayList<>(); - artifactGenerators.stream() - .filter(g -> g.supports(blueprint)) - .sorted(Comparator.comparingInt(ArtifactGenerator::order)) - .forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); + artifactGenerators.forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); return generatedFiles; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index 556d33e..c92b823 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -2,12 +2,11 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; @@ -22,10 +21,6 @@ public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { - private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; - private static final int ORDER = 10; - private static final String NAME = "maven-pom"; - private static final String KEY_GROUP_ID = "groupId"; private static final String KEY_ARTIFACT_ID = "artifactId"; private static final String KEY_JAVA_VERSION = "javaVersion"; @@ -47,25 +42,24 @@ public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { "test"); private final TemplateRenderer renderer; - private final CodegenProfilesProperties profiles; + private final ArtifactProperties artifactProperties; - public MavenPomAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + public MavenPomAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { this.renderer = renderer; - this.profiles = profiles; + this.artifactProperties = artifactProperties; } @Override public GeneratedFile generate(ProjectBlueprint blueprint) { - ArtifactProperties cfg = cfg(); - Path outPath = Path.of(cfg.outputPath()); - String template = cfg.template(); + Path outPath = Path.of(artifactProperties.outputPath()); + String template = artifactProperties.template(); Map model = buildModel(blueprint); return renderer.renderUtf8(outPath, template, model); } @Override - public boolean supports(ProjectBlueprint bp) { - return cfg().enabled(); + public ArtifactKey artifactKey() { + return ArtifactKey.POM; } @Override @@ -73,20 +67,6 @@ public Iterable generateFiles(ProjectBlueprint blueprin return List.of(generate(blueprint)); } - @Override - public int order() { - return ORDER; - } - - @Override - public String name() { - return NAME; - } - - private ArtifactProperties cfg() { - return profiles.artifact(PROFILE, NAME); - } - private Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); PlatformTarget pt = bp.getPlatformTarget(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index 4ee0898..b36d2f4 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -2,12 +2,11 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; @@ -17,52 +16,34 @@ public final class ApplicationYamlAdapter implements ConfigFilesPort, ArtifactGenerator { - private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; - private static final int ORDER = 50; - private static final String NAME = "application-yaml"; private static final String KEY_FRAMEWORK = "framework"; private static final String KEY_BUILD_TOOL = "buildTool"; private static final String KEY_LANGUAGE = "language"; private final TemplateRenderer renderer; - private final CodegenProfilesProperties profiles; + private final ArtifactProperties artifactProperties; - public ApplicationYamlAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + public ApplicationYamlAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { this.renderer = renderer; - this.profiles = profiles; + this.artifactProperties = artifactProperties; } @Override public Iterable generate(BuildOptions options) { - ArtifactProperties cfg = cfg(); - Path outPath = Path.of(cfg.outputPath()); - String template = cfg.template(); + Path outPath = Path.of(artifactProperties.outputPath()); + String template = artifactProperties.template(); Map model = buildModel(options); return List.of(renderer.renderUtf8(outPath, template, model)); } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint.getBuildOptions()); - } - - @Override - public boolean supports(ProjectBlueprint blueprint) { - return cfg().enabled(); - } - - @Override - public int order() { - return ORDER; + public ArtifactKey artifactKey() { + return ArtifactKey.APPLICATION_YAML; } @Override - public String name() { - return NAME; - } - - private ArtifactProperties cfg() { - return profiles.artifact(PROFILE, NAME); + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint.getBuildOptions()); } private Map buildModel(BuildOptions options) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 9871a04..3294402 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -2,12 +2,11 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; @@ -21,9 +20,6 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { - private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; - private static final int ORDER = 90; - private static final String NAME = "readme"; private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; private static final String KEY_GROUP_ID = "groupId"; @@ -37,45 +33,29 @@ public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { private static final String KEY_DEPENDENCIES = "dependencies"; private final TemplateRenderer renderer; - private final CodegenProfilesProperties profiles; + private final ArtifactProperties artifactProperties; - public ReadmeAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + public ReadmeAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { this.renderer = renderer; - this.profiles = profiles; + this.artifactProperties = artifactProperties; } @Override public GeneratedFile generate(ProjectBlueprint blueprint) { - ArtifactProperties cfg = cfg(); - Path outPath = Path.of(cfg.outputPath()); - String template = cfg.template(); + Path outPath = Path.of(artifactProperties.outputPath()); + String template = artifactProperties.template(); Map model = buildModel(blueprint); return renderer.renderUtf8(outPath, template, model); } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return List.of(generate(blueprint)); - } - - @Override - public boolean supports(ProjectBlueprint bp) { - ArtifactProperties cfg = cfg(); - return cfg.enabled(); - } - - @Override - public int order() { - return ORDER; + public ArtifactKey artifactKey() { + return ArtifactKey.README; } @Override - public String name() { - return NAME; - } - - private ArtifactProperties cfg() { - return profiles.artifact(PROFILE, NAME); + public Iterable generateFiles(ProjectBlueprint blueprint) { + return List.of(generate(blueprint)); } private Map buildModel(ProjectBlueprint bp) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index d1394b8..23de0a4 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -1,5 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; @@ -7,26 +8,18 @@ public final class SourceScaffolderAdapter implements SourceScaffolderPort, ArtifactGenerator { - private static final int ORDER = 30; - private static final String NAME = "source-scaffold"; - @Override public Iterable generate(ProjectBlueprint blueprint) { throw new UnsupportedOperationException("SourceScaffolderAdapter.generate not implemented yet"); } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); - } - - @Override - public int order() { - return ORDER; + public ArtifactKey artifactKey() { + return null; } @Override - public String name() { - return NAME; + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 1cdb764..9835402 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,32 +1,24 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; public final class TestScaffolderAdapter implements TestScaffolderPort, ArtifactGenerator { - - private static final int ORDER = 40; - private static final String NAME = "test-scaffold"; - @Override public Iterable generate(ProjectBlueprint blueprint) { throw new UnsupportedOperationException("TestScaffolderAdapter.generate not implemented yet"); } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); + public ArtifactKey artifactKey() { + return null; } @Override - public int order() { - return ORDER; - } - - @Override - public String name() { - return NAME; + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index ef27c0e..3c6707c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,11 +1,10 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; @@ -15,50 +14,31 @@ public final class GitIgnoreAdapter implements GitIgnorePort, ArtifactGenerator { - private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; - private static final int ORDER = 20; - private static final String NAME = "gitignore"; private static final String KEY_IGNORE_LIST = "ignoreList"; private final TemplateRenderer renderer; - private final CodegenProfilesProperties profiles; + private final ArtifactProperties artifactProperties; - public GitIgnoreAdapter(TemplateRenderer renderer, CodegenProfilesProperties profiles) { + public GitIgnoreAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { this.renderer = renderer; - this.profiles = profiles; + this.artifactProperties = artifactProperties; } @Override public GeneratedFile generate(BuildOptions buildOptions) { - ArtifactProperties cfg = cfg(); - Path outPath = Path.of(cfg.outputPath()); - String template = cfg.template(); + Path outPath = Path.of(artifactProperties.outputPath()); + String template = artifactProperties.template(); Map model = buildModel(buildOptions); return renderer.renderUtf8(outPath, template, model); } @Override - public Iterable generateFiles(ProjectBlueprint bp) { - return List.of(generate(bp.getBuildOptions())); - } - - @Override - public boolean supports(ProjectBlueprint bp) { - ArtifactProperties cfg = cfg(); - return cfg.enabled(); - } - - @Override - public int order() { - return ORDER; + public ArtifactKey artifactKey() { + return ArtifactKey.GITIGNORE; } @Override - public String name() { - return NAME; - } - - private ArtifactProperties cfg() { - return profiles.artifact(PROFILE, NAME); + public Iterable generateFiles(ProjectBlueprint bp) { + return List.of(generate(bp.getBuildOptions())); } @SuppressWarnings("unused") diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index bb15247..f21fd2f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -1,5 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; @@ -7,26 +8,18 @@ public final class MavenWrapperAdapter implements MavenWrapperPort, ArtifactGenerator { - private static final int ORDER = 60; - private static final String NAME = "maven-wrapper"; - @Override public Iterable generate(ProjectBlueprint blueprint) { throw new UnsupportedOperationException("MavenWrapperAdapter.generate not implemented yet"); } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); - } - - @Override - public int order() { - return ORDER; + public ArtifactKey artifactKey() { + return null; } @Override - public String name() { - return NAME; + public Iterable generateFiles(ProjectBlueprint blueprint) { + return generate(blueprint); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java index 1463b36..fb27788 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java @@ -1,16 +1,12 @@ package io.github.bsayli.codegen.initializr.adapter.out.spi; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; public interface ArtifactGenerator { - default boolean supports(ProjectBlueprint blueprint) { - return true; - } - default int order() { - return 100; - } + ArtifactKey artifactKey(); Iterable generateFiles(ProjectBlueprint blueprint); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java new file mode 100644 index 0000000..0821040 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java @@ -0,0 +1,16 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationPropertiesBinding +public class ArtifactKeyConverter implements Converter { + @Override + public ArtifactKey convert(@NonNull String source) { + return ArtifactKey.fromKey(source); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java index c2fd065..4071c48 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java @@ -2,5 +2,4 @@ import jakarta.validation.constraints.NotBlank; -public record ArtifactProperties( - boolean enabled, @NotBlank String template, @NotBlank String outputPath) {} +public record ArtifactProperties(@NotBlank String template, @NotBlank String outputPath) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 74a1016..45388fc 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -1,9 +1,9 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -13,14 +13,14 @@ @ConfigurationProperties(prefix = "codegen") public record CodegenProfilesProperties(@Valid @NotNull Map profiles) { - public ArtifactProperties artifact(ProfileType profile, String artifactKey) { + public ArtifactProperties artifact(ProfileType profile, ArtifactKey artifactKey) { var p = requireProfile(profile); var raw = requireArtifact(profile, p, artifactKey); String fullTemplate = p.templateBasePath() + "/" + raw.template(); - return new ArtifactProperties(raw.enabled(), fullTemplate, raw.outputPath()); + return new ArtifactProperties(fullTemplate, raw.outputPath()); } - ProfileProperties requireProfile(ProfileType profile) { + public ProfileProperties requireProfile(ProfileType profile) { var key = profile.key(); var p = profiles.get(key); if (p == null) { @@ -30,16 +30,13 @@ ProfileProperties requireProfile(ProfileType profile) { return p; } - ArtifactProperties requireArtifact(ProfileType profile, ProfileProperties p, String artifactKey) { - var a = p.artifacts().get(artifactKey); + ArtifactProperties requireArtifact( + ProfileType profile, ProfileProperties p, ArtifactKey artifactKey) { + var a = p.artifacts().get(artifactKey.key()); if (a == null) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey, profile.key()); + ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); } return a; } - - public record ProfileProperties( - @NotBlank String templateBasePath, - @Valid @NotNull Map artifacts) {} } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java new file mode 100644 index 0000000..ec2c51f --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +public record ProfileProperties( + @NotBlank String templateBasePath, + @Valid @NotNull List run, + @Valid @NotNull Map artifacts) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java index 108aa7f..a970644 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java @@ -1,5 +1,8 @@ package io.github.bsayli.codegen.initializr.bootstrap.wiring; +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.GeneratorFactoryNotFoundException; import io.github.bsayli.codegen.initializr.adapter.out.ProfileBasedArtifactsSelector; import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; @@ -11,31 +14,61 @@ import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ProfileProperties; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; import org.springframework.context.annotation.Bean; // @Configuration class ArtifactsWiring { + private static final Map GENERATOR_FACTORIES = + Map.of( + ArtifactKey.POM, MavenPomAdapter::new, + ArtifactKey.GITIGNORE, GitIgnoreAdapter::new, + ArtifactKey.APPLICATION_YAML, ApplicationYamlAdapter::new, + ArtifactKey.README, ReadmeAdapter::new); + @Bean ProjectArtifactsPort springbootMavenJavaArtifactsPort( TemplateRenderer renderer, CodegenProfilesProperties profiles) { - List gens = - List.of( - new MavenPomAdapter(renderer, profiles), - new GitIgnoreAdapter(renderer, profiles), - new ApplicationYamlAdapter(renderer, profiles), - new ReadmeAdapter(renderer, profiles)); - return new SpringBootMavenJavaArtifactsAdapter(gens); + + var type = ProfileType.SPRINGBOOT_MAVEN_JAVA; + ProfileProperties profile = profiles.requireProfile(type); + + List generators = + profile.run().stream() + .map( + expectedKey -> { + var factory = GENERATOR_FACTORIES.get(expectedKey); + if (factory == null) { + throw new GeneratorFactoryNotFoundException(expectedKey); + } + var artifactProperties = profiles.artifact(type, expectedKey); + var generator = factory.apply(renderer, artifactProperties); + var actualKey = generator.artifactKey(); + if (!actualKey.equals(expectedKey)) { + throw new ArtifactKeyMismatchException(expectedKey, actualKey); + } + return generator; + }) + .toList(); + + return new SpringBootMavenJavaArtifactsAdapter(generators); } @Bean ProjectArtifactsSelector artifactsSelector( ProjectArtifactsPort springbootMavenJavaArtifactsPort) { - Map reg = + Map registry = Map.of(ProfileType.SPRINGBOOT_MAVEN_JAVA, springbootMavenJavaArtifactsPort); - return new ProfileBasedArtifactsSelector(reg); + return new ProfileBasedArtifactsSelector(registry); } + + @FunctionalInterface + interface ArtifactGeneratorFactory + extends BiFunction {} } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7757c53..b63bbec 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,28 +7,21 @@ codegen: profiles: springboot-maven-java: template-base-path: springboot/maven/java + run: [ pom, gitignore, application-yaml, readme ] # execution order artifacts: - gitignore: - enabled: true - template: .gitignore.ftl - output-path: .gitignore - pom: - enabled: true template: pom.xml.ftl output-path: pom.xml - + gitignore: + template: .gitignore.ftl + output-path: .gitignore application-yaml: - enabled: true template: application.yml.ftl output-path: src/main/resources/application.yml - readme: - enabled: true template: README.md.ftl output-path: README.md - templating: encoding: UTF-8 handler: RETHROW diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 88860e5..ffd8bde 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -75,4 +75,7 @@ bootstrap.profile.not.found=Unknown profile: {0} bootstrap.artifact.not.found=Unknown artifact ''{0}'' for profile: {1} bootstrap.template.base.missing=template-base-path must be set for profile: {0} adapter.artifacts.port.not.found=No artifact generator adapter registered for profile ''{0}''. -adapter.profile.unsupported=Unsupported profile combination: framework={0}, buildTool={1}, language={2} \ No newline at end of file +adapter.profile.unsupported=Unsupported profile combination: framework={0}, buildTool={1}, language={2} +adapter.artifact.key.unknown=Unknown artifact key ''{0}''. +adapter.generator.factory.not.found=No generator factory registered for artifact ''{0}''. +adapter.generator.key.mismatch=Generator artifact key mismatch (expected ''{0}'', actual ''{1}''). \ No newline at end of file From d16ec6805cb663df400e48c51f79561571a2ae24 Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 2 Nov 2025 15:28:48 +0300 Subject: [PATCH 15/74] refactor: unify artifact generation under ArtifactPort abstraction - Removed obsolete ArtifactGenerator SPI - Introduced base ArtifactPort interface with consistent generate(ProjectBlueprint) contract - Updated all adapters (MavenPom, ApplicationYaml, GitIgnore, Readme) to implement ArtifactPort - Simplified SpringBootMavenJavaArtifactsAdapter using StreamSupport for Iterable flattening - Cleaned up ArtifactsWiring naming for clarity --- .../SpringBootMavenJavaArtifactsAdapter.java | 18 ++--- .../maven/java/build/MavenPomAdapter.java | 50 +++++------- .../java/config/ApplicationYamlAdapter.java | 24 ++---- .../maven/java/docs/ReadmeAdapter.java | 13 +-- .../java/source/SourceScaffolderAdapter.java | 8 +- .../java/test/TestScaffolderAdapter.java | 8 +- .../maven/java/vcs/GitIgnoreAdapter.java | 15 ++-- .../java/wrapper/MavenWrapperAdapter.java | 8 +- .../port/out/artifacts/ArtifactPort.java} | 11 +-- .../port/out/artifacts/ConfigFilesPort.java | 7 +- .../port/out/artifacts/GitIgnorePort.java | 7 +- .../port/out/artifacts/MavenPomPort.java | 7 +- .../port/out/artifacts/MavenWrapperPort.java | 7 +- .../port/out/artifacts/ReadmePort.java | 7 +- .../out/artifacts/SourceScaffolderPort.java | 8 +- .../out/artifacts/TestScaffolderPort.java | 7 +- .../bootstrap/wiring/ArtifactsWiring.java | 80 +++++++------------ .../model/value/dependency/Dependencies.java | 2 +- 18 files changed, 89 insertions(+), 198 deletions(-) rename src/main/java/io/github/bsayli/codegen/initializr/{adapter/out/spi/ArtifactGenerator.java => application/port/out/artifacts/ArtifactPort.java} (51%) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java index 44d1a19..fe825cb 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java @@ -1,24 +1,24 @@ package io.github.bsayli.codegen.initializr.adapter.out; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.util.ArrayList; import java.util.List; +import java.util.stream.StreamSupport; -public class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { +public final class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { - private final List artifactGenerators; + private final List artifacts; - public SpringBootMavenJavaArtifactsAdapter(List artifactGenerators) { - this.artifactGenerators = artifactGenerators; + public SpringBootMavenJavaArtifactsAdapter(List artifacts) { + this.artifacts = artifacts; } @Override public Iterable generate(ProjectBlueprint blueprint) { - List generatedFiles = new ArrayList<>(); - artifactGenerators.forEach(g -> g.generateFiles(blueprint).forEach(generatedFiles::add)); - return generatedFiles; + return artifacts.stream() + .flatMap(p -> StreamSupport.stream(p.generate(blueprint).spliterator(), false)) + .toList(); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index c92b823..e787887 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -3,7 +3,6 @@ import static java.util.Map.entry; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; @@ -19,7 +18,7 @@ import java.util.List; import java.util.Map; -public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { +public final class MavenPomAdapter implements MavenPomPort { private static final String KEY_GROUP_ID = "groupId"; private static final String KEY_ARTIFACT_ID = "artifactId"; @@ -30,16 +29,12 @@ public final class MavenPomAdapter implements MavenPomPort, ArtifactGenerator { private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; private static final Map CORE_STARTER = - Map.of(KEY_GROUP_ID, "org.springframework.boot", KEY_ARTIFACT_ID, "spring-boot-starter"); + Map.of(KEY_GROUP_ID, "org.springframework.boot", KEY_ARTIFACT_ID, "spring-boot-starter"); private static final Map TEST_STARTER = - Map.of( - KEY_GROUP_ID, - "org.springframework.boot", - KEY_ARTIFACT_ID, - "spring-boot-starter-test", - "scope", - "test"); + Map.of(KEY_GROUP_ID, "org.springframework.boot", + KEY_ARTIFACT_ID, "spring-boot-starter-test", + "scope", "test"); private final TemplateRenderer renderer; private final ArtifactProperties artifactProperties; @@ -49,22 +44,18 @@ public MavenPomAdapter(TemplateRenderer renderer, ArtifactProperties artifactPro this.artifactProperties = artifactProperties; } - @Override - public GeneratedFile generate(ProjectBlueprint blueprint) { - Path outPath = Path.of(artifactProperties.outputPath()); - String template = artifactProperties.template(); - Map model = buildModel(blueprint); - return renderer.renderUtf8(outPath, template, model); - } - @Override public ArtifactKey artifactKey() { return ArtifactKey.POM; } @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return List.of(generate(blueprint)); + public Iterable generate(ProjectBlueprint blueprint) { + Path outPath = Path.of(artifactProperties.outputPath()); + String template = artifactProperties.template(); + Map model = buildModel(blueprint); + GeneratedFile file = renderer.renderUtf8(outPath, template, model); + return List.of(file); } private Map buildModel(ProjectBlueprint bp) { @@ -77,13 +68,13 @@ private Map buildModel(ProjectBlueprint bp) { dependencies.add(TEST_STARTER); return Map.ofEntries( - entry(KEY_GROUP_ID, id.groupId().value()), - entry(KEY_ARTIFACT_ID, id.artifactId().value()), - entry(KEY_JAVA_VERSION, pt.java().asString()), - entry(KEY_SPRING_BOOT_VER, pt.springBoot().value()), - entry(KEY_PROJECT_NAME, bp.getName().value()), - entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), - entry(KEY_DEPENDENCIES, dependencies)); + entry(KEY_GROUP_ID, id.groupId().value()), + entry(KEY_ARTIFACT_ID, id.artifactId().value()), + entry(KEY_JAVA_VERSION, pt.java().asString()), + entry(KEY_SPRING_BOOT_VER, pt.springBoot().value()), + entry(KEY_PROJECT_NAME, bp.getName().value()), + entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), + entry(KEY_DEPENDENCIES, dependencies)); } private List> mapUserDependencies(Dependencies userDependencies) { @@ -97,9 +88,8 @@ private Map toMap(Dependency d) { Map m = new LinkedHashMap<>(); m.put(KEY_GROUP_ID, d.coordinates().groupId().value()); m.put(KEY_ARTIFACT_ID, d.coordinates().artifactId().value()); - if (d.version() != null && !d.version().value().isBlank()) - m.put("version", d.version().value()); + if (d.version() != null && !d.version().value().isBlank()) m.put("version", d.version().value()); if (d.scope() != null && !d.scope().value().isBlank()) m.put("scope", d.scope().value()); return m; } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index b36d2f4..21ac940 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -3,22 +3,18 @@ import static java.util.Map.entry; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.nio.file.Path; import java.util.List; import java.util.Map; -public final class ApplicationYamlAdapter implements ConfigFilesPort, ArtifactGenerator { +public final class ApplicationYamlAdapter implements ConfigFilesPort { - private static final String KEY_FRAMEWORK = "framework"; - private static final String KEY_BUILD_TOOL = "buildTool"; - private static final String KEY_LANGUAGE = "language"; + private static final String KEY_PROJECT_NAME = "projectName"; private final TemplateRenderer renderer; private final ArtifactProperties artifactProperties; @@ -29,10 +25,10 @@ public ApplicationYamlAdapter(TemplateRenderer renderer, ArtifactProperties arti } @Override - public Iterable generate(BuildOptions options) { + public Iterable generate(ProjectBlueprint blueprint) { Path outPath = Path.of(artifactProperties.outputPath()); String template = artifactProperties.template(); - Map model = buildModel(options); + Map model = buildModel(blueprint); return List.of(renderer.renderUtf8(outPath, template, model)); } @@ -41,15 +37,7 @@ public ArtifactKey artifactKey() { return ArtifactKey.APPLICATION_YAML; } - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint.getBuildOptions()); - } - - private Map buildModel(BuildOptions options) { - return Map.ofEntries( - entry(KEY_FRAMEWORK, options.framework().name()), - entry(KEY_BUILD_TOOL, options.buildTool().name()), - entry(KEY_LANGUAGE, options.language().name())); + private Map buildModel(ProjectBlueprint blueprint) { + return Map.ofEntries(entry(KEY_PROJECT_NAME, blueprint.getName().value())); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 3294402..1515148 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -3,7 +3,6 @@ import static java.util.Map.entry; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; @@ -18,7 +17,7 @@ import java.util.List; import java.util.Map; -public final class ReadmeAdapter implements ReadmePort, ArtifactGenerator { +public final class ReadmeAdapter implements ReadmePort { private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; @@ -41,11 +40,12 @@ public ReadmeAdapter(TemplateRenderer renderer, ArtifactProperties artifactPrope } @Override - public GeneratedFile generate(ProjectBlueprint blueprint) { + public Iterable generate(ProjectBlueprint blueprint) { Path outPath = Path.of(artifactProperties.outputPath()); String template = artifactProperties.template(); Map model = buildModel(blueprint); - return renderer.renderUtf8(outPath, template, model); + GeneratedFile generatedFile = renderer.renderUtf8(outPath, template, model); + return List.of(generatedFile); } @Override @@ -53,11 +53,6 @@ public ArtifactKey artifactKey() { return ArtifactKey.README; } - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return List.of(generate(blueprint)); - } - private Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); BuildOptions bo = bp.getBuildOptions(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index 23de0a4..f5ac2dd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -1,12 +1,11 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -public final class SourceScaffolderAdapter implements SourceScaffolderPort, ArtifactGenerator { +public final class SourceScaffolderAdapter implements SourceScaffolderPort { @Override public Iterable generate(ProjectBlueprint blueprint) { @@ -17,9 +16,4 @@ public Iterable generate(ProjectBlueprint blueprint) { public ArtifactKey artifactKey() { return null; } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); - } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 9835402..704e820 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,12 +1,11 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -public final class TestScaffolderAdapter implements TestScaffolderPort, ArtifactGenerator { +public final class TestScaffolderAdapter implements TestScaffolderPort { @Override public Iterable generate(ProjectBlueprint blueprint) { throw new UnsupportedOperationException("TestScaffolderAdapter.generate not implemented yet"); @@ -16,9 +15,4 @@ public Iterable generate(ProjectBlueprint blueprint) { public ArtifactKey artifactKey() { return null; } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); - } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index 3c6707c..773a9af 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,7 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; @@ -12,7 +11,7 @@ import java.util.List; import java.util.Map; -public final class GitIgnoreAdapter implements GitIgnorePort, ArtifactGenerator { +public final class GitIgnoreAdapter implements GitIgnorePort { private static final String KEY_IGNORE_LIST = "ignoreList"; private final TemplateRenderer renderer; @@ -24,11 +23,12 @@ public GitIgnoreAdapter(TemplateRenderer renderer, ArtifactProperties artifactPr } @Override - public GeneratedFile generate(BuildOptions buildOptions) { + public Iterable generate(ProjectBlueprint projectBlueprint) { Path outPath = Path.of(artifactProperties.outputPath()); String template = artifactProperties.template(); - Map model = buildModel(buildOptions); - return renderer.renderUtf8(outPath, template, model); + Map model = buildModel(projectBlueprint.getBuildOptions()); + GeneratedFile generatedFile = renderer.renderUtf8(outPath, template, model); + return List.of(generatedFile); } @Override @@ -36,11 +36,6 @@ public ArtifactKey artifactKey() { return ArtifactKey.GITIGNORE; } - @Override - public Iterable generateFiles(ProjectBlueprint bp) { - return List.of(generate(bp.getBuildOptions())); - } - @SuppressWarnings("unused") private Map buildModel(BuildOptions buildOptions) { return Map.of(KEY_IGNORE_LIST, List.of()); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index f21fd2f..c035ecc 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -1,12 +1,11 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -public final class MavenWrapperAdapter implements MavenWrapperPort, ArtifactGenerator { +public final class MavenWrapperAdapter implements MavenWrapperPort { @Override public Iterable generate(ProjectBlueprint blueprint) { @@ -17,9 +16,4 @@ public Iterable generate(ProjectBlueprint blueprint) { public ArtifactKey artifactKey() { return null; } - - @Override - public Iterable generateFiles(ProjectBlueprint blueprint) { - return generate(blueprint); - } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java similarity index 51% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java index fb27788..fa7050a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/spi/ArtifactGenerator.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java @@ -1,16 +1,11 @@ -package io.github.bsayli.codegen.initializr.adapter.out.spi; +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -public interface ArtifactGenerator { - +public interface ArtifactPort { ArtifactKey artifactKey(); - Iterable generateFiles(ProjectBlueprint blueprint); - - default String name() { - return getClass().getSimpleName(); - } + Iterable generate(ProjectBlueprint blueprint); } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java index 63531d1..bd04385 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface ConfigFilesPort { - Iterable generate(BuildOptions options); -} +public interface ConfigFilesPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java index e618c39..1c5d47d 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface GitIgnorePort { - GeneratedFile generate(BuildOptions buildOptions); -} +public interface GitIgnorePort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java index 8ec4f27..b7163b7 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface MavenPomPort { - GeneratedFile generate(ProjectBlueprint blueprint); -} +public interface MavenPomPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java index 8b7f751..4066d1e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface MavenWrapperPort { - Iterable generate(ProjectBlueprint blueprint); -} +public interface MavenWrapperPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java index 8daed75..6b74273 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface ReadmePort { - GeneratedFile generate(ProjectBlueprint blueprint); -} +public interface ReadmePort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java index 6eab8ec..01cf93e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java @@ -1,9 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface SourceScaffolderPort { - - Iterable generate(ProjectBlueprint blueprint); -} +public interface SourceScaffolderPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java index ed8845e..95403bb 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java @@ -1,8 +1,3 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; - -public interface TestScaffolderPort { - Iterable generate(ProjectBlueprint blueprint); -} +public interface TestScaffolderPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java index a970644..72b4439 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java @@ -3,21 +3,17 @@ import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; import io.github.bsayli.codegen.initializr.adapter.error.exception.GeneratorFactoryNotFoundException; -import io.github.bsayli.codegen.initializr.adapter.out.ProfileBasedArtifactsSelector; import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.spi.ArtifactGenerator; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.ProfileProperties; -import java.util.List; import java.util.Map; import java.util.function.BiFunction; import org.springframework.context.annotation.Bean; @@ -25,50 +21,36 @@ // @Configuration class ArtifactsWiring { - private static final Map GENERATOR_FACTORIES = - Map.of( - ArtifactKey.POM, MavenPomAdapter::new, - ArtifactKey.GITIGNORE, GitIgnoreAdapter::new, - ArtifactKey.APPLICATION_YAML, ApplicationYamlAdapter::new, - ArtifactKey.README, ReadmeAdapter::new); - - @Bean - ProjectArtifactsPort springbootMavenJavaArtifactsPort( - TemplateRenderer renderer, CodegenProfilesProperties profiles) { - - var type = ProfileType.SPRINGBOOT_MAVEN_JAVA; - ProfileProperties profile = profiles.requireProfile(type); - - List generators = - profile.run().stream() - .map( - expectedKey -> { - var factory = GENERATOR_FACTORIES.get(expectedKey); - if (factory == null) { - throw new GeneratorFactoryNotFoundException(expectedKey); - } - var artifactProperties = profiles.artifact(type, expectedKey); - var generator = factory.apply(renderer, artifactProperties); - var actualKey = generator.artifactKey(); - if (!actualKey.equals(expectedKey)) { - throw new ArtifactKeyMismatchException(expectedKey, actualKey); - } - return generator; + private static final Map FACTORIES = Map.of( + ArtifactKey.POM, MavenPomAdapter::new, + ArtifactKey.GITIGNORE, GitIgnoreAdapter::new, + ArtifactKey.APPLICATION_YAML, ApplicationYamlAdapter::new, + ArtifactKey.README, ReadmeAdapter::new + ); + + @Bean + ProjectArtifactsPort springBootMavenJavaArtifactsAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles) { + + var type = ProfileType.SPRINGBOOT_MAVEN_JAVA; + var profile = profiles.requireProfile(type); + + var artifacts = profile.run().stream() + .map(expectedKey -> { + var f = FACTORIES.get(expectedKey); + if (f == null) throw new GeneratorFactoryNotFoundException(expectedKey); + var props = profiles.artifact(type, expectedKey); + var port = f.apply(renderer, props); + if (!port.artifactKey().equals(expectedKey)) + throw new ArtifactKeyMismatchException(expectedKey, port.artifactKey()); + return port; }) - .toList(); - - return new SpringBootMavenJavaArtifactsAdapter(generators); - } + .toList(); - @Bean - ProjectArtifactsSelector artifactsSelector( - ProjectArtifactsPort springbootMavenJavaArtifactsPort) { - Map registry = - Map.of(ProfileType.SPRINGBOOT_MAVEN_JAVA, springbootMavenJavaArtifactsPort); - return new ProfileBasedArtifactsSelector(registry); - } + return new SpringBootMavenJavaArtifactsAdapter(artifacts); + } - @FunctionalInterface - interface ArtifactGeneratorFactory - extends BiFunction {} -} + @FunctionalInterface + interface ArtifactFactory + extends BiFunction {} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java index 35c2323..71d6829 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java @@ -7,7 +7,7 @@ public final class Dependencies { private final List items; private Dependencies(List items) { - this.items = items; + this.items = List.copyOf(items); } public static Dependencies of(List raw) { From f08bc69b03baa0061fbf4593cd3bfef99b96b82e Mon Sep 17 00:00:00 2001 From: bsayli Date: Mon, 3 Nov 2025 16:49:31 +0300 Subject: [PATCH 16/74] refactor: consolidate Spring Boot Maven Java wiring and improve profile naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Unified all Spring Boot Maven Java beans under `SpringBootMavenJavaConfig` - Registered artifact adapters and dependency mapper as Spring beans - Introduced ordered artifact registry with lifecycle managed by Spring context - Fixed `ProfileType.key()` slug generation (SPRING_BOOT → springboot) - Renamed `run` keyword in application.yml profiles definition → `ordered-artifact-keys` for clarity and accuracy - Prepared `FreeMarkerTemplatingProperties` for future multi-engine support --- .../maven/java/build/MavenPomAdapter.java | 58 +++---- .../maven/java/docs/ReadmeAdapter.java | 15 +- .../maven/java/shared/PomDependency.java | 39 +++++ .../java/shared/PomDependencyMapper.java | 23 +++ .../adapter/profile/ProfileType.java | 6 +- .../createproject/CreateProjectService.java | 2 +- .../createproject/ProjectBlueprintMapper.java | 2 +- .../bootstrap/config/ProfileProperties.java | 2 +- ...=> FreeMarkerTemplatingConfiguration.java} | 20 ++- ...va => FreeMarkerTemplatingProperties.java} | 2 +- .../bootstrap/wiring/ArtifactsWiring.java | 56 ------- .../wiring/SpringBootMavenJavaConfig.java | 112 +++++++++++++ src/main/resources/application.yml | 2 +- .../springboot/maven/java/README.md.ftl | 148 ++++++------------ .../CreateProjectServiceTest.java | 2 +- .../ProjectBlueprintMapperTest.java | 2 +- 16 files changed, 285 insertions(+), 206 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/{FreeMarkerTemplateConfiguration.java => FreeMarkerTemplatingConfiguration.java} (63%) rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/{FreeMarkerTemplateProperties.java => FreeMarkerTemplatingProperties.java} (92%) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index e787887..47be3cf 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -3,18 +3,17 @@ import static java.util.Map.entry; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; -import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.nio.file.Path; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -28,20 +27,23 @@ public final class MavenPomAdapter implements MavenPomPort { private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; - private static final Map CORE_STARTER = - Map.of(KEY_GROUP_ID, "org.springframework.boot", KEY_ARTIFACT_ID, "spring-boot-starter"); + private static final PomDependency CORE_STARTER = + PomDependency.of("org.springframework.boot", "spring-boot-starter"); - private static final Map TEST_STARTER = - Map.of(KEY_GROUP_ID, "org.springframework.boot", - KEY_ARTIFACT_ID, "spring-boot-starter-test", - "scope", "test"); + private static final PomDependency TEST_STARTER = + PomDependency.of("org.springframework.boot", "spring-boot-starter-test", null, "test"); private final TemplateRenderer renderer; private final ArtifactProperties artifactProperties; + private final PomDependencyMapper pomDependencyMapper; - public MavenPomAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { + public MavenPomAdapter( + TemplateRenderer renderer, + ArtifactProperties artifactProperties, + PomDependencyMapper pomDependencyMapper) { this.renderer = renderer; this.artifactProperties = artifactProperties; + this.pomDependencyMapper = pomDependencyMapper; } @Override @@ -62,34 +64,18 @@ private Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); PlatformTarget pt = bp.getPlatformTarget(); - List> dependencies = new ArrayList<>(); + List dependencies = new ArrayList<>(); dependencies.add(CORE_STARTER); - dependencies.addAll(mapUserDependencies(bp.getDependencies())); + dependencies.addAll(pomDependencyMapper.from(bp.getDependencies())); dependencies.add(TEST_STARTER); return Map.ofEntries( - entry(KEY_GROUP_ID, id.groupId().value()), - entry(KEY_ARTIFACT_ID, id.artifactId().value()), - entry(KEY_JAVA_VERSION, pt.java().asString()), - entry(KEY_SPRING_BOOT_VER, pt.springBoot().value()), - entry(KEY_PROJECT_NAME, bp.getName().value()), - entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), - entry(KEY_DEPENDENCIES, dependencies)); + entry(KEY_GROUP_ID, id.groupId().value()), + entry(KEY_ARTIFACT_ID, id.artifactId().value()), + entry(KEY_JAVA_VERSION, pt.java().asString()), + entry(KEY_SPRING_BOOT_VER, pt.springBoot().value()), + entry(KEY_PROJECT_NAME, bp.getName().value()), + entry(KEY_PROJECT_DESCRIPTION, bp.getDescription().value()), + entry(KEY_DEPENDENCIES, dependencies)); } - - private List> mapUserDependencies(Dependencies userDependencies) { - if (userDependencies == null || userDependencies.isEmpty()) return List.of(); - List> list = new ArrayList<>(userDependencies.asList().size()); - for (Dependency d : userDependencies.asList()) list.add(toMap(d)); - return list; - } - - private Map toMap(Dependency d) { - Map m = new LinkedHashMap<>(); - m.put(KEY_GROUP_ID, d.coordinates().groupId().value()); - m.put(KEY_ARTIFACT_ID, d.coordinates().artifactId().value()); - if (d.version() != null && !d.version().value().isBlank()) m.put("version", d.version().value()); - if (d.scope() != null && !d.scope().value().isBlank()) m.put("scope", d.scope().value()); - return m; - } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 1515148..15a005f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -3,6 +3,8 @@ import static java.util.Map.entry; import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; @@ -33,10 +35,15 @@ public final class ReadmeAdapter implements ReadmePort { private final TemplateRenderer renderer; private final ArtifactProperties artifactProperties; + private final PomDependencyMapper pomDependencyMapper; - public ReadmeAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { + public ReadmeAdapter( + TemplateRenderer renderer, + ArtifactProperties artifactProperties, + PomDependencyMapper pomDependencyMapper) { this.renderer = renderer; this.artifactProperties = artifactProperties; + this.pomDependencyMapper = pomDependencyMapper; } @Override @@ -58,7 +65,9 @@ private Map buildModel(ProjectBlueprint bp) { BuildOptions bo = bp.getBuildOptions(); PlatformTarget pt = bp.getPlatformTarget(); PackageName pn = bp.getPackageName(); - Dependencies deps = bp.getDependencies(); + Dependencies selectedDependencies = bp.getDependencies(); + + List dependencies = pomDependencyMapper.from(selectedDependencies); return Map.ofEntries( entry(KEY_PROJECT_NAME, bp.getName().value()), @@ -71,6 +80,6 @@ private Map buildModel(ProjectBlueprint bp) { entry(KEY_FRAMEWORK, bo.framework().name()), entry(KEY_JAVA_VERSION, pt.java().asString()), entry(KEY_SPRING_BOOT_VERSION, pt.springBoot().value()), - entry(KEY_DEPENDENCIES, deps.asList())); + entry(KEY_DEPENDENCIES, dependencies)); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java new file mode 100644 index 0000000..7043ff3 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java @@ -0,0 +1,39 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; + +public final class PomDependency { + private final String groupId; + private final String artifactId; + private final String version; // nullable + private final String scope; // nullable + + private PomDependency(String groupId, String artifactId, String version, String scope) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; + this.scope = scope; + } + + public static PomDependency of(String groupId, String artifactId) { + return new PomDependency(groupId, artifactId, null, null); + } + + public static PomDependency of(String groupId, String artifactId, String version, String scope) { + return new PomDependency(groupId, artifactId, version, scope); + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getVersion() { + return version; + } + + public String getScope() { + return scope; + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java new file mode 100644 index 0000000..1cdbc1a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java @@ -0,0 +1,23 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; + +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import java.util.ArrayList; +import java.util.List; + +public class PomDependencyMapper { + + public List from(Dependencies dependencies) { + if (dependencies == null || dependencies.isEmpty()) return List.of(); + var list = new ArrayList(dependencies.asList().size()); + for (Dependency d : dependencies.asList()) list.add(from(d)); + return list; + } + + public PomDependency from(Dependency d) { + var v = (d.version() == null || d.version().value().isBlank()) ? null : d.version().value(); + var s = (d.scope() == null || d.scope().value().isBlank()) ? null : d.scope().value(); + return PomDependency.of( + d.coordinates().groupId().value(), d.coordinates().artifactId().value(), v, s); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java index 7dc60df..117b656 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java @@ -29,7 +29,11 @@ public static ProfileType from(BuildOptions o) { return null; } + private static String slug(Enum e) { + return e.name().toLowerCase().replace("_", ""); + } + public String key() { - return (framework.name() + "-" + buildTool.name() + "-" + language.name()).toLowerCase(); + return slug(framework) + "-" + slug(buildTool) + "-" + slug(language); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java index 245a498..cd82c4e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java @@ -33,7 +33,7 @@ public CreateProjectService( @Override public CreateProjectResult execute(CreateProjectCommand command) { - ProjectBlueprint bp = mapper.toDomain(command); + ProjectBlueprint bp = mapper.from(command); Path projectRoot = rootPort.prepareRoot( diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java index 32401d7..c9e7db8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java @@ -20,7 +20,7 @@ public class ProjectBlueprintMapper { - public ProjectBlueprint toDomain(CreateProjectCommand c) { + public ProjectBlueprint from(CreateProjectCommand c) { ProjectIdentity identity = new ProjectIdentity(new GroupId(c.groupId()), new ArtifactId(c.artifactId())); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java index ec2c51f..43ae936 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java @@ -9,5 +9,5 @@ public record ProfileProperties( @NotBlank String templateBasePath, - @Valid @NotNull List run, + @Valid @NotNull List orderedArtifactKeys, @Valid @NotNull Map artifacts) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java similarity index 63% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java index c225961..dab58c8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateConfiguration.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java @@ -5,19 +5,22 @@ import freemarker.template.Configuration; import freemarker.template.TemplateExceptionHandler; import freemarker.template.Version; +import io.github.bsayli.codegen.initializr.adapter.out.templating.FreeMarkerTemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -// @org.springframework.context.annotation.Configuration -// @EnableConfigurationProperties(FreeMarkerTemplateProperties.class) -public class FreeMarkerTemplateConfiguration { +@org.springframework.context.annotation.Configuration +@EnableConfigurationProperties(FreeMarkerTemplatingProperties.class) +public class FreeMarkerTemplatingConfiguration { public static final String NUMBER_FORMAT_COMPUTER = "computer"; private static final Version FM_VER = VERSION_2_3_34; - private final FreeMarkerTemplateProperties props; + private final FreeMarkerTemplatingProperties props; - public FreeMarkerTemplateConfiguration(FreeMarkerTemplateProperties props) { + public FreeMarkerTemplatingConfiguration(FreeMarkerTemplatingProperties props) { this.props = props; } @@ -39,7 +42,12 @@ Configuration freemarkerConfiguration() { return cfg; } - private TemplateExceptionHandler toHandler(FreeMarkerTemplateProperties.Handler h) { + @Bean + TemplateRenderer templateRenderer(Configuration freemarkerConfiguration) { + return new FreeMarkerTemplateRenderer(freemarkerConfiguration); + } + + private TemplateExceptionHandler toHandler(FreeMarkerTemplatingProperties.Handler h) { return switch (h) { case RETHROW -> TemplateExceptionHandler.RETHROW_HANDLER; case DEBUG -> TemplateExceptionHandler.DEBUG_HANDLER; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java similarity index 92% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java index 3dff461..f168629 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplateProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java @@ -7,7 +7,7 @@ @Validated @ConfigurationProperties(prefix = "templating") -public record FreeMarkerTemplateProperties( +public record FreeMarkerTemplatingProperties( @NotBlank String encoding, @NotNull Handler handler, @NotBlank String templatePath, diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java deleted file mode 100644 index 72b4439..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ArtifactsWiring.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.bsayli.codegen.initializr.bootstrap.wiring; - -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; -import io.github.bsayli.codegen.initializr.adapter.error.exception.GeneratorFactoryNotFoundException; -import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; -import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; -import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; -import java.util.Map; -import java.util.function.BiFunction; -import org.springframework.context.annotation.Bean; - -// @Configuration -class ArtifactsWiring { - - private static final Map FACTORIES = Map.of( - ArtifactKey.POM, MavenPomAdapter::new, - ArtifactKey.GITIGNORE, GitIgnoreAdapter::new, - ArtifactKey.APPLICATION_YAML, ApplicationYamlAdapter::new, - ArtifactKey.README, ReadmeAdapter::new - ); - - @Bean - ProjectArtifactsPort springBootMavenJavaArtifactsAdapter( - TemplateRenderer renderer, CodegenProfilesProperties profiles) { - - var type = ProfileType.SPRINGBOOT_MAVEN_JAVA; - var profile = profiles.requireProfile(type); - - var artifacts = profile.run().stream() - .map(expectedKey -> { - var f = FACTORIES.get(expectedKey); - if (f == null) throw new GeneratorFactoryNotFoundException(expectedKey); - var props = profiles.artifact(type, expectedKey); - var port = f.apply(renderer, props); - if (!port.artifactKey().equals(expectedKey)) - throw new ArtifactKeyMismatchException(expectedKey, port.artifactKey()); - return port; - }) - .toList(); - - return new SpringBootMavenJavaArtifactsAdapter(artifacts); - } - - @FunctionalInterface - interface ArtifactFactory - extends BiFunction {} -} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java new file mode 100644 index 0000000..c12cf13 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -0,0 +1,112 @@ +package io.github.bsayli.codegen.initializr.bootstrap.wiring; + +import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; +import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; +import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringBootMavenJavaConfig { + + @Bean + PomDependencyMapper pomDependencyMapper() { + return new PomDependencyMapper(); + } + + @Bean + MavenPomAdapter springBootMavenJavaPomAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + PomDependencyMapper pomMapper) { + ArtifactProperties props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.POM); + return new MavenPomAdapter(renderer, props, pomMapper); + } + + @Bean + GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles) { + ArtifactProperties props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.GITIGNORE); + return new GitIgnoreAdapter(renderer, props); + } + + @Bean + ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles) { + ArtifactProperties props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.APPLICATION_YAML); + return new ApplicationYamlAdapter(renderer, props); + } + + @Bean + ReadmeAdapter springBootMavenJavaReadmeAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + PomDependencyMapper pomDependencyMapper) { + ArtifactProperties props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.README); + return new ReadmeAdapter(renderer, props, pomDependencyMapper); + } + + @Bean(name = "springBootMavenJavaArtifactRegistry") + Map springBootMavenJavaArtifactRegistry( + MavenPomAdapter mavenPomAdapter, + GitIgnoreAdapter gitIgnoreAdapter, + ApplicationYamlAdapter applicationYamlAdapter, + ReadmeAdapter readmeAdapter) { + + Map registry = new EnumMap<>(ArtifactKey.class); + registry.put(ArtifactKey.POM, mavenPomAdapter); + registry.put(ArtifactKey.GITIGNORE, gitIgnoreAdapter); + registry.put(ArtifactKey.APPLICATION_YAML, applicationYamlAdapter); + registry.put(ArtifactKey.README, readmeAdapter); + return registry; + } + + @Bean + ProjectArtifactsPort springBootMavenJavaArtifactsAdapter( + CodegenProfilesProperties profiles, + @Qualifier("springBootMavenJavaArtifactRegistry") Map registry) { + + var profile = profiles.requireProfile(ProfileType.SPRINGBOOT_MAVEN_JAVA); + var orderedArtifactKeys = profile.orderedArtifactKeys(); + + List ordered = + orderedArtifactKeys.stream() + .map( + key -> { + ArtifactPort port = registry.get(key); + if (port == null) { + throw new ProfileConfigurationException( + "bootstrap.artifact.not.found", + key.key(), + ProfileType.SPRINGBOOT_MAVEN_JAVA.key()); + } + if (!port.artifactKey().equals(key)) { + throw new ArtifactKeyMismatchException(key, port.artifactKey()); + } + return port; + }) + .toList(); + + return new SpringBootMavenJavaArtifactsAdapter(ordered); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b63bbec..e363c36 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,7 +7,7 @@ codegen: profiles: springboot-maven-java: template-base-path: springboot/maven/java - run: [ pom, gitignore, application-yaml, readme ] # execution order + ordered-artifact-keys: [ pom, gitignore, application-yaml, readme ] # execution order artifacts: pom: template: pom.xml.ftl diff --git a/src/main/resources/templates/springboot/maven/java/README.md.ftl b/src/main/resources/templates/springboot/maven/java/README.md.ftl index 1049914..8f7eca3 100644 --- a/src/main/resources/templates/springboot/maven/java/README.md.ftl +++ b/src/main/resources/templates/springboot/maven/java/README.md.ftl @@ -1,129 +1,83 @@ -# Codegen Spring Boot Initializr +# ${projectName} -A modern Spring Boot project initializer with **Hexagonal Architecture** baked in. -This tool generates a clean project scaffold you can extend — not just a blank template, but a foundation aligned with real-world best practices from companies like Netflix, Uber, Amazon, and Google. +${projectDescription} --- -## Features +## Tech Stack -* **Maven Wrapper & pom.xml** generated out of the box. -* **Hexagonal Architecture project layout** with `domain`, `application`, `adapter`, and `bootstrap` layers. -* **Artifacts** like `.gitignore`, `application.yml`, and optional `README.md` included. -* **Minimal Hexagonal Example**: a `Customer` aggregate with a single `CreateCustomer` use case, REST controller, and in-memory repository. -* **Extensible artifact generators**: each artifact is generated by its own adapter implementing the `ArtifactGenerator` contract. +* **Framework:** ${framework} +* **Language:** ${language} +* **Build Tool:** ${buildTool} +* **Java:** ${javaVersion} +* **Spring Boot:** ${springBootVersion} ---- - -## Generated Project Structure - -```text -com.example.customer -├─ domain // Pure business rules, core domain logic -│ ├─ model // Aggregate root & value objects -│ │ ├─ Customer.java -│ │ └─ value -│ │ ├─ CustomerId.java -│ │ └─ Email.java -│ └─ port -│ └─ CustomerRepository.java // Outbound port for persistence -│ -├─ application // Use case orchestration -│ ├─ usecase -│ │ └─ customer -│ │ └─ create -│ │ ├─ CreateCustomerCommand.java -│ │ ├─ CreateCustomerResult.java -│ │ └─ CreateCustomerService.java -│ └─ mapper (optional) -│ -├─ adapter // Technical implementations -│ ├─ inbound -│ │ └─ rest -│ │ └─ customer -│ │ ├─ CustomerController.java -│ │ └─ dto -│ │ ├─ CreateCustomerRequest.java -│ │ └─ CustomerResponse.java -│ └─ outbound -│ └─ persistence -│ └─ memory -│ └─ InMemoryCustomerRepository.java -│ -└─ bootstrap // Spring configs, bean wiring -└─ SpringBeansConfig.java -``` - ---- - -## Why Only **Create**? +**Coordinates** -Instead of generating a full CRUD garden, the initializer provides a **minimal Create use case**: - -* Keeps the scaffold **lean and comprehensible**. -* Shows the full flow across **all hexagonal layers**. -* Leaves room for you to extend with Update/Delete/Search as exercises. +* `groupId`: `${groupId}` +* `artifactId`: `${artifactId}` +* `package`: `${packageName}` --- -## Artifact Generation Flow - -Artifacts are produced by ordered generators implementing the `ArtifactGenerator` contract: +## Quick Start -1. **MavenPomAdapter** → `pom.xml` -2. **GitIgnoreAdapter** → `.gitignore` -3. **ConfigFilesAdapter** → `application.yml` -4. **HexSourceScaffolderAdapter** → Java source files for domain/application/adapters -5. **HexTestScaffolderAdapter** → Minimal test scaffold -6. **ReadmeAdapter** → `README.md` -7. **MavenWrapperAdapter** → `.mvn/wrapper/maven-wrapper.properties` +```bash +# Build (using wrapper) +./mvnw clean package -Each adapter declares: +# Run +./mvnw spring-boot:run +``` -* `supports(ProjectBlueprint)` → whether to apply for a given template kind. -* `order()` → ensures proper generation sequence. -* `generateFiles(ProjectBlueprint)` → returns one or more `GeneratedFile` objects. +> If Maven is installed globally, you can also use `mvn` instead of `./mvnw`. --- -## Running the Generated Project - -1. Extract the archive: `unzip myapp.zip` -2. Navigate: `cd myapp` -3. Build with Maven (if installed): `mvn package` -4. Or use the wrapper (no Maven required): +## Configuration -* Linux/macOS: `./mvnw package` -* Windows: `mvnw.cmd package` +Default configuration file: `src/main/resources/application.yml` +You can override settings via environment variables or Spring profiles. --- -## Example Use Case Flow +## Project Layout (short) -1. **POST /customers** with JSON body → handled by `CustomerController`. -2. Controller maps request → `CreateCustomerCommand`. -3. `CreateCustomerService` executes business logic → calls `CustomerRepository` port. -4. `InMemoryCustomerRepository` adapter persists entity → returns result. -5. Response mapped back to `CustomerResponse` and returned with `201 Created`. +``` +src +├─ main +│ ├─ java/…/${packageName?replace('.', '/')} +│ └─ resources/ +└─ test +└─ java/…/${packageName?replace('.', '/')} +``` ---- +<#-- Optional hexagonal example section --> +<#if hasHexSample?? && hasHexSample> + ------------------------------------ -## Philosophy + ## Hexagonal Sample (optional) -* **Domain first** → business rules and entities stay free of frameworks. -* **Ports define needs, not implementations** → adapters can change without touching domain/application. -* **Minimal, extendable** → enough to show the pattern, small enough to adapt. + An example domain/application/adapter structure has been included to illustrate a minimal "Create" use case. + --- -## Next Steps +## Dependencies (selected) -* Add your own use cases: Update, Delete, Search. -* Swap `InMemoryCustomerRepository` with a JPA or MongoDB adapter. -* Extend inbound side with CLI or messaging adapters. +<#if dependencies?? && dependencies?size > 0> + <#list dependencies as d> + + * ${d.groupId}:${d.artifactId}<#if d.version?? && d.version?has_content>:${d.version}<#if d.scope?? && d.scope?has_content> (${d.scope}) + +<#else> + * No additional dependencies selected. + --- -## License +## Next Steps -MIT License — see [LICENSE](LICENSE). +* Update port/log levels in `application.yml`. +* Add additional dependencies if required. +* Extend the project with CI/CD or containerization steps. diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java index 75b85a1..76e770b 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java @@ -85,7 +85,7 @@ void creates_project_root_writes_artifacts_and_archives() { assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(tempDir.resolve("demo-app")); assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); - var bpForPaths = mapper.toDomain(cmd); + var bpForPaths = mapper.from(cmd); var expected = List.of( POM, diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java index 07d0fe9..1e7e72a 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java @@ -47,7 +47,7 @@ private static ProjectBlueprint getProjectBlueprint() { inputs, Path.of(".")); - return mapper.toDomain(cmd); + return mapper.from(cmd); } @Test From bf236a4f5a807a3efaa22ae67ff45c69e7e0d9f0 Mon Sep 17 00:00:00 2001 From: bsayli Date: Mon, 3 Nov 2025 16:58:22 +0300 Subject: [PATCH 17/74] refactor: align Spring Boot Maven Java bean naming and enforce immutability - Standardized bean names to match method identifiers for consistency - Removed redundant @Qualifier usage and avoided duplicate bean names - Returned registry as unmodifiable map to prevent external mutation - Ensured artifact resolution order follows profile configuration safely - Preserved constructor-based injection and consistent adapter naming convention --- .../wiring/SpringBootMavenJavaConfig.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java index c12cf13..d6e9414 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -15,10 +15,11 @@ import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; + +import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,10 +35,10 @@ PomDependencyMapper pomDependencyMapper() { MavenPomAdapter springBootMavenJavaPomAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles, - PomDependencyMapper pomMapper) { + PomDependencyMapper pomDependencyMapper) { ArtifactProperties props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.POM); - return new MavenPomAdapter(renderer, props, pomMapper); + return new MavenPomAdapter(renderer, props, pomDependencyMapper); } @Bean @@ -66,34 +67,34 @@ ReadmeAdapter springBootMavenJavaReadmeAdapter( return new ReadmeAdapter(renderer, props, pomDependencyMapper); } - @Bean(name = "springBootMavenJavaArtifactRegistry") + @Bean Map springBootMavenJavaArtifactRegistry( - MavenPomAdapter mavenPomAdapter, - GitIgnoreAdapter gitIgnoreAdapter, - ApplicationYamlAdapter applicationYamlAdapter, - ReadmeAdapter readmeAdapter) { + MavenPomAdapter springBootMavenJavaPomAdapter, + GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter, + ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter, + ReadmeAdapter springBootMavenJavaReadmeAdapter) { Map registry = new EnumMap<>(ArtifactKey.class); - registry.put(ArtifactKey.POM, mavenPomAdapter); - registry.put(ArtifactKey.GITIGNORE, gitIgnoreAdapter); - registry.put(ArtifactKey.APPLICATION_YAML, applicationYamlAdapter); - registry.put(ArtifactKey.README, readmeAdapter); - return registry; + registry.put(ArtifactKey.POM, springBootMavenJavaPomAdapter); + registry.put(ArtifactKey.GITIGNORE, springBootMavenJavaGitIgnoreAdapter); + registry.put(ArtifactKey.APPLICATION_YAML, springBootMavenJavaApplicationYamlAdapter); + registry.put(ArtifactKey.README, springBootMavenJavaReadmeAdapter); + return Collections.unmodifiableMap(registry); } @Bean ProjectArtifactsPort springBootMavenJavaArtifactsAdapter( - CodegenProfilesProperties profiles, - @Qualifier("springBootMavenJavaArtifactRegistry") Map registry) { + CodegenProfilesProperties codegenProfilesProperties, + Map springBootMavenJavaArtifactRegistry) { - var profile = profiles.requireProfile(ProfileType.SPRINGBOOT_MAVEN_JAVA); + var profile = codegenProfilesProperties.requireProfile(ProfileType.SPRINGBOOT_MAVEN_JAVA); var orderedArtifactKeys = profile.orderedArtifactKeys(); List ordered = orderedArtifactKeys.stream() .map( key -> { - ArtifactPort port = registry.get(key); + ArtifactPort port = springBootMavenJavaArtifactRegistry.get(key); if (port == null) { throw new ProfileConfigurationException( "bootstrap.artifact.not.found", From 5efbcd64c91636090e369c33130ae20486426991 Mon Sep 17 00:00:00 2001 From: bsayli Date: Fri, 7 Nov 2025 17:55:56 +0300 Subject: [PATCH 18/74] refactor: add SourceScaffolderAdapter and common bean wiring for shared components - Introduced SourceScaffolderAdapter to generate main application class from templates - Registered StringCaseFormatter and PomDependencyMapper via CodegenCommonConfig - Added source-scaffolder artifact configuration under Spring Boot Maven Java profile - Improved package organization for shared and build-related adapters - Fixed YAML indentation in application.yml.ftl --- .../ArtifactKeyMismatchException.java | 2 +- .../GeneratorFactoryNotFoundException.java | 2 +- .../maven}/shared/PomDependency.java | 2 +- .../maven}/shared/PomDependencyMapper.java | 2 +- .../maven/java/build/MavenPomAdapter.java | 6 +- .../java/config/ApplicationYamlAdapter.java | 2 +- .../maven/java/docs/ReadmeAdapter.java | 6 +- .../java/source/SourceScaffolderAdapter.java | 57 ++++++++++-- .../java/test/TestScaffolderAdapter.java | 2 +- .../maven/java/vcs/GitIgnoreAdapter.java | 2 +- .../java/wrapper/MavenWrapperAdapter.java | 2 +- .../shared/naming/StringCaseFormatter.java | 24 +++++ .../port/out/artifacts}/ArtifactKey.java | 5 +- .../port/out/artifacts/ArtifactPort.java | 1 - ...Service.java => CreateProjectHandler.java} | 6 +- .../createproject/CreateProjectUseCase.java | 2 +- .../config/ArtifactKeyConverter.java | 2 +- .../config/CodegenProfilesProperties.java | 2 +- .../bootstrap/config/ProfileProperties.java | 2 +- .../bootstrap/wiring/CodegenCommonConfig.java | 20 ++++ .../wiring/SpringBootMavenJavaConfig.java | 18 +++- src/main/resources/application.yml | 5 +- .../springboot/maven/java/MainClass.java.ftl | 12 +++ .../springboot/maven/java/application.yml.ftl | 14 +-- ...est.java => CreateProjectHandlerTest.java} | 91 ++++++++----------- 25 files changed, 192 insertions(+), 97 deletions(-) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{profile/springboot/maven/java => build/maven}/shared/PomDependency.java (90%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{profile/springboot/maven/java => build/maven}/shared/PomDependencyMapper.java (90%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatter.java rename src/main/java/io/github/bsayli/codegen/initializr/{adapter/artifact => application/port/out/artifacts}/ArtifactKey.java (82%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/{CreateProjectService.java => CreateProjectHandler.java} (92%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/CodegenCommonConfig.java create mode 100644 src/main/resources/templates/springboot/maven/java/MainClass.java.ftl rename src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/{CreateProjectServiceTest.java => CreateProjectHandlerTest.java} (66%) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java index 63b36ee..c61782f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; @SuppressWarnings("java:S110") public final class ArtifactKeyMismatchException extends AdapterException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java index 0a8941a..02a8d7b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; @SuppressWarnings("java:S110") public final class GeneratorFactoryNotFoundException extends AdapterException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java index 7043ff3..6d2f8ed 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependency.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; +package io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared; public final class PomDependency { private final String groupId; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapper.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapper.java index 1cdbc1a..0591d68 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/PomDependencyMapper.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapper.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; +package io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index 47be3cf..67c58fa 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -2,10 +2,10 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependency; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index 21ac940..bd7a89c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -2,8 +2,8 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 15a005f..648a907 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -2,10 +2,10 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependency; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index f5ac2dd..80eeb45 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -1,19 +1,64 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; public final class SourceScaffolderAdapter implements SourceScaffolderPort { - @Override - public Iterable generate(ProjectBlueprint blueprint) { - throw new UnsupportedOperationException("SourceScaffolderAdapter.generate not implemented yet"); + public static final String POSTFIX_APPLICATION = "Application"; + private static final String KEY_PROJECT_PACKAGE = "projectPackageName"; + private static final String KEY_CLASS_NAME = "className"; + private static final String JAVA_FILE_EXTENSION = ".java"; + private static final String PACKAGE_PATH_DELIMITER = "."; + private static final String FILE_PATH_DELIMITER = "/"; + private final TemplateRenderer renderer; + private final ArtifactProperties artifactProperties; + private final StringCaseFormatter stringCaseFormatter; + + public SourceScaffolderAdapter( + TemplateRenderer renderer, + ArtifactProperties artifactProperties, + StringCaseFormatter stringCaseFormatter) { + this.renderer = renderer; + this.artifactProperties = artifactProperties; + this.stringCaseFormatter = stringCaseFormatter; } @Override public ArtifactKey artifactKey() { - return null; + return ArtifactKey.SOURCE_SCAFFOLDER; + } + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + PackageName packageName = blueprint.getPackageName(); + ProjectIdentity id = blueprint.getIdentity(); + + String className = + stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION; + + Map model = + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), + entry(KEY_CLASS_NAME, className)); + + Path baseDir = Path.of(artifactProperties.outputPath()); + String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); + Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); + + GeneratedFile file = renderer.renderUtf8(outPath, artifactProperties.template(), model); + return List.of(file); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 704e820..cd3bc8d 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index 773a9af..9d5cacd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index c035ecc..69c2b83 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatter.java new file mode 100644 index 0000000..ede9e9e --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatter.java @@ -0,0 +1,24 @@ +package io.github.bsayli.codegen.initializr.adapter.shared.naming; + +import java.util.regex.Pattern; + +public class StringCaseFormatter { + + private static final String EMPTY = ""; + private static final Pattern NON_ALPHANUMERIC_DELIMITER = Pattern.compile("[^A-Za-z0-9]+"); + + public String toPascalCase(String raw) { + if (raw == null || raw.isBlank()) return EMPTY; + + String[] parts = NON_ALPHANUMERIC_DELIMITER.split(raw.trim()); + StringBuilder sb = new StringBuilder(parts.length * 8); + + for (String part : parts) { + if (part.isEmpty()) continue; + sb.append(Character.toUpperCase(part.charAt(0))); + if (part.length() > 1) sb.append(part.substring(1).toLowerCase()); + } + + return sb.toString(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java similarity index 82% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java index d2159b2..794432f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/artifact/ArtifactKey.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.artifact; +package io.github.bsayli.codegen.initializr.application.port.out.artifacts; import io.github.bsayli.codegen.initializr.adapter.error.exception.UnknownArtifactKeyException; import java.util.Arrays; @@ -7,7 +7,8 @@ public enum ArtifactKey { POM("pom"), GITIGNORE("gitignore"), APPLICATION_YAML("application-yaml"), - README("readme"); + README("readme"), + SOURCE_SCAFFOLDER("source-scaffolder"); private final String key; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java index fa7050a..7a78e35 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java @@ -1,6 +1,5 @@ package io.github.bsayli.codegen.initializr.application.port.out.artifacts; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java similarity index 92% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java index cd82c4e..8577aaa 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectService.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java @@ -10,7 +10,7 @@ import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; import java.nio.file.Path; -public class CreateProjectService implements CreateProjectUseCase { +public class CreateProjectHandler implements CreateProjectUseCase { private final ProjectBlueprintMapper mapper; private final ProjectRootPort rootPort; @@ -18,7 +18,7 @@ public class CreateProjectService implements CreateProjectUseCase { private final ProjectWriterPort writerPort; private final ProjectArchiverPort archiverPort; - public CreateProjectService( + public CreateProjectHandler( ProjectBlueprintMapper mapper, ProjectRootPort rootPort, ProjectArtifactsSelector artifactsSelector, @@ -32,7 +32,7 @@ public CreateProjectService( } @Override - public CreateProjectResult execute(CreateProjectCommand command) { + public CreateProjectResult handle(CreateProjectCommand command) { ProjectBlueprint bp = mapper.from(command); Path projectRoot = diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java index 1177542..39ea89c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java @@ -1,5 +1,5 @@ package io.github.bsayli.codegen.initializr.application.usecase.createproject; public interface CreateProjectUseCase { - CreateProjectResult execute(CreateProjectCommand command); + CreateProjectResult handle(CreateProjectCommand command); } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java index 0821040..a5723fc 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.NonNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 45388fc..777a93a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java index 43ae936..efcc0e0 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/CodegenCommonConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/CodegenCommonConfig.java new file mode 100644 index 0000000..062a45f --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/CodegenCommonConfig.java @@ -0,0 +1,20 @@ +package io.github.bsayli.codegen.initializr.bootstrap.wiring; + +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CodegenCommonConfig { + + @Bean + public StringCaseFormatter stringCaseFormatter() { + return new StringCaseFormatter(); + } + + @Bean + public PomDependencyMapper pomDependencyMapper() { + return new PomDependencyMapper(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java index d6e9414..b5bdb07 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -1,21 +1,22 @@ package io.github.bsayli.codegen.initializr.bootstrap.wiring; -import io.github.bsayli.codegen.initializr.adapter.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; -import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source.SourceScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; - import java.util.Collections; import java.util.EnumMap; import java.util.List; @@ -27,8 +28,13 @@ public class SpringBootMavenJavaConfig { @Bean - PomDependencyMapper pomDependencyMapper() { - return new PomDependencyMapper(); + SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + StringCaseFormatter stringCaseFormatter) { + ArtifactProperties props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.SOURCE_SCAFFOLDER); + return new SourceScaffolderAdapter(renderer, props, stringCaseFormatter); } @Bean @@ -72,12 +78,14 @@ Map springBootMavenJavaArtifactRegistry( MavenPomAdapter springBootMavenJavaPomAdapter, GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter, ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter, + SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter, ReadmeAdapter springBootMavenJavaReadmeAdapter) { Map registry = new EnumMap<>(ArtifactKey.class); registry.put(ArtifactKey.POM, springBootMavenJavaPomAdapter); registry.put(ArtifactKey.GITIGNORE, springBootMavenJavaGitIgnoreAdapter); registry.put(ArtifactKey.APPLICATION_YAML, springBootMavenJavaApplicationYamlAdapter); + registry.put(ArtifactKey.SOURCE_SCAFFOLDER, springBootMavenJavaSourceScaffolderAdapter); registry.put(ArtifactKey.README, springBootMavenJavaReadmeAdapter); return Collections.unmodifiableMap(registry); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e363c36..3a3e374 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,7 +7,7 @@ codegen: profiles: springboot-maven-java: template-base-path: springboot/maven/java - ordered-artifact-keys: [ pom, gitignore, application-yaml, readme ] # execution order + ordered-artifact-keys: [ pom, gitignore, application-yaml, source-scaffolder, readme ] # execution order artifacts: pom: template: pom.xml.ftl @@ -18,6 +18,9 @@ codegen: application-yaml: template: application.yml.ftl output-path: src/main/resources/application.yml + source-scaffolder: + template: MainClass.java.ftl + output-path: src/main/java readme: template: README.md.ftl output-path: README.md diff --git a/src/main/resources/templates/springboot/maven/java/MainClass.java.ftl b/src/main/resources/templates/springboot/maven/java/MainClass.java.ftl new file mode 100644 index 0000000..3016fe2 --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/MainClass.java.ftl @@ -0,0 +1,12 @@ +package ${projectPackageName}; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ${className} { + + public static void main(String[] args) { + SpringApplication.run(${className}.class, args); + } +} \ No newline at end of file diff --git a/src/main/resources/templates/springboot/maven/java/application.yml.ftl b/src/main/resources/templates/springboot/maven/java/application.yml.ftl index 3525bb9..6a1b7b6 100644 --- a/src/main/resources/templates/springboot/maven/java/application.yml.ftl +++ b/src/main/resources/templates/springboot/maven/java/application.yml.ftl @@ -1,8 +1,8 @@ spring: -application: -name: ${projectName} -# server: -# port: 8080 -# logging: -# level: -# root: INFO \ No newline at end of file + application: + name: ${projectName} + # server: + # port: 8080 + # logging: + # level: + # root: INFO \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java similarity index 66% rename from src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java index 76e770b..dda7d13 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectServiceTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java @@ -29,30 +29,14 @@ @Tag("unit") @Tag("application") -@DisplayName("CreateProjectService") -class CreateProjectServiceTest { - - private static final Path POM = Path.of("pom.xml"); - private static final Path GITIGNORE = Path.of(".gitignore"); - private static final Path APP_YML = Path.of("src/main/resources/application.yml"); - private static final Path WRAPPER_PROPS = Path.of(".mvn/wrapper/maven-wrapper.properties"); - private static final Path README = Path.of("README.md"); +@DisplayName("CreateProjectHandler") +class CreateProjectHandlerTest { @TempDir Path tempDir; - private static Path appClass(ProjectBlueprint bp) { - String pkg = bp.getPackageName().value().replace('.', '/'); - return Path.of("src/main/java", pkg, "DemoApplication.java"); - } - - private static Path testClass(ProjectBlueprint bp) { - String pkg = bp.getPackageName().value().replace('.', '/'); - return Path.of("src/test/java", pkg, "DemoApplicationTests.java"); - } - @Test - @DisplayName("execute() prepares project root, writes artifacts, and returns archive path") - void creates_project_root_writes_artifacts_and_archives() { + @DisplayName("handle() prepares project root, writes artifacts, and returns archive path") + void handle_prepares_root_writes_artifacts_and_archives() { var mapper = new ProjectBlueprintMapper(); var fakeRootPort = new FakeRootPort(); var fakeArtifacts = new FakeArtifactsPort(); @@ -60,8 +44,8 @@ void creates_project_root_writes_artifacts_and_archives() { var fakeWriter = new FakeWriterPort(); var fakeArchiver = new FakeArchiverPort(); - var service = - new CreateProjectService(mapper, fakeRootPort, fakeSelector, fakeWriter, fakeArchiver); + var handler = + new CreateProjectHandler(mapper, fakeRootPort, fakeSelector, fakeWriter, fakeArchiver); var cmd = new CreateProjectCommand( @@ -76,7 +60,7 @@ void creates_project_root_writes_artifacts_and_archives() { List.of(), tempDir); - var result = service.execute(cmd); + var result = handler.handle(cmd); assertThat(result.archivePath()).hasFileName("demo-app.zip"); assertThat(fakeArchiver.lastProjectRoot).isEqualTo(fakeRootPort.lastPreparedRoot); @@ -85,24 +69,11 @@ void creates_project_root_writes_artifacts_and_archives() { assertThat(fakeRootPort.lastPreparedRoot).isEqualTo(tempDir.resolve("demo-app")); assertThat(fakeRootPort.lastPolicy).isEqualTo(FAIL_IF_EXISTS); - var bpForPaths = mapper.from(cmd); - var expected = - List.of( - POM, - GITIGNORE, - appClass(bpForPaths), - testClass(bpForPaths), - APP_YML, - WRAPPER_PROPS, - README); - assertThat(fakeWriter.writtenFiles) - .containsExactlyInAnyOrderElementsOf(expected) - .hasSize(expected.size()); + .containsExactlyInAnyOrderElementsOf(fakeArtifacts.lastEmittedRelativePaths) + .hasSize(fakeArtifacts.lastEmittedRelativePaths.size()); } - // ---- fakes ---- - static class FakeRootPort implements ProjectRootPort { Path lastPreparedRoot; ProjectRootExistencePolicy lastPolicy; @@ -129,24 +100,36 @@ public ProjectArtifactsPort select(BuildOptions options) { } static class FakeArtifactsPort implements ProjectArtifactsPort { + final List lastEmittedRelativePaths = new ArrayList<>(); + @Override public Iterable generate(ProjectBlueprint bp) { - return List.of( - new GeneratedFile.Text(POM, "", StandardCharsets.UTF_8), - new GeneratedFile.Text(GITIGNORE, "*.class", StandardCharsets.UTF_8), - new GeneratedFile.Text(appClass(bp), "class DemoApplication {}", StandardCharsets.UTF_8), - new GeneratedFile.Text( - testClass(bp), "class DemoApplicationTests {}", StandardCharsets.UTF_8), - new GeneratedFile.Text( - APP_YML, "spring:\n application:\n name: demo-app", StandardCharsets.UTF_8), - new GeneratedFile.Text( - WRAPPER_PROPS, - "distributionUrl=https://repo.maven.apache.org/maven2/...", - StandardCharsets.UTF_8), - new GeneratedFile.Text( - README, - "# Demo App\n\nThis project was generated by Codegen Initializr.", - StandardCharsets.UTF_8)); + var files = + List.of( + new GeneratedFile.Text(Path.of("pom.xml"), "", StandardCharsets.UTF_8), + new GeneratedFile.Text(Path.of(".gitignore"), "*.class", StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("src/main/java/com/acme/demo/DemoApplication.java"), + "class DemoApplication {}", + StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("src/test/java/com/acme/demo/DemoApplicationTests.java"), + "class DemoApplicationTests {}", + StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("src/main/resources/application.yml"), + "spring:\n application:\n name: demo-app", + StandardCharsets.UTF_8), + new GeneratedFile.Text( + Path.of("README.md"), + "# Demo App\n\nThis project was generated by Codegen Initializr.", + StandardCharsets.UTF_8)); + + lastEmittedRelativePaths.clear(); + for (var gf : files) { + lastEmittedRelativePaths.add(gf.relativePath()); + } + return files; } } From b5a0eee2e91eee5d9aeb99fde0e3e6541c42cc74 Mon Sep 17 00:00:00 2001 From: bsayli Date: Thu, 13 Nov 2025 15:41:35 +0300 Subject: [PATCH 19/74] refactor: introduce multi-template artifact system and scaffold adapters - Replaced ArtifactProperties with new ArtifactDefinition/TemplateDefinition model - Migrated all adapters (pom, gitignore, application-yaml, source-scaffolder, readme, test-scaffolder) to the new multi-template structure - Added AbstractSingleTemplateArtifactAdapter (Template Method pattern) - Implemented TestScaffolderAdapter with dedicated test template - Updated SourceScaffolderAdapter to use ArtifactDefinition + StringCaseFormatter - Added ArtifactKey.TEST_SCAFFOLDER and updated profile configuration (YAML) - Introduced ProjectArtifactsSelectorConfig for clean registry/selector wiring - Enhanced SpringBootMavenJavaConfig wiring for new artifact adapters - General cleanup, consistency fixes, and preparation for multi-template expansion --- .../out/ProfileBasedArtifactsSelector.java | 2 +- .../SpringBootMavenJavaArtifactsAdapter.java | 2 +- ...AbstractSingleTemplateArtifactAdapter.java | 35 +++++++++++ .../maven/java/build/MavenPomAdapter.java | 25 +++----- .../java/config/ApplicationYamlAdapter.java | 28 +++------ .../maven/java/docs/ReadmeAdapter.java | 27 +++------ .../java/source/SourceScaffolderAdapter.java | 29 +++++---- .../java/test/TestScaffolderAdapter.java | 59 +++++++++++++++++-- .../maven/java/vcs/GitIgnoreAdapter.java | 29 +++------ .../port/out/artifacts/ArtifactKey.java | 5 +- .../bootstrap/config/ArtifactDefinition.java | 7 +++ .../config/CodegenProfilesProperties.java | 24 ++++---- .../bootstrap/config/ProfileProperties.java | 2 +- ...roperties.java => TemplateDefinition.java} | 2 +- .../ProjectArtifactsSelectorConfig.java | 31 ++++++++++ .../wiring/SpringBootMavenJavaConfig.java | 25 ++++++-- src/main/resources/application.yml | 46 +++++++++++---- .../maven/java/MainClassTests.java.ftl | 14 +++++ 18 files changed, 261 insertions(+), 131 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/{ArtifactProperties.java => TemplateDefinition.java} (68%) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java create mode 100644 src/main/resources/templates/springboot/maven/java/MainClassTests.java.ftl diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java index 3de72c9..148c679 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java @@ -8,7 +8,7 @@ import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import java.util.Map; -public final class ProfileBasedArtifactsSelector implements ProjectArtifactsSelector { +public class ProfileBasedArtifactsSelector implements ProjectArtifactsSelector { private final Map registry; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java index fe825cb..297a84a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.stream.StreamSupport; -public final class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { +public class SpringBootMavenJavaArtifactsAdapter implements ProjectArtifactsPort { private final List artifacts; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java new file mode 100644 index 0000000..3c4524c --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java @@ -0,0 +1,35 @@ +package io.github.bsayli.codegen.initializr.adapter.out.artifact; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public abstract class AbstractSingleTemplateArtifactAdapter implements ArtifactPort { + + private final TemplateRenderer renderer; + private final ArtifactDefinition artifactDefinition; + + protected AbstractSingleTemplateArtifactAdapter( + TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + this.renderer = renderer; + this.artifactDefinition = artifactDefinition; + } + + @Override + public final Iterable generate(ProjectBlueprint blueprint) { + TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); + Path outPath = Path.of(templateDefinition.outputPath()); + String template = templateDefinition.template(); + Map model = buildModel(blueprint); + GeneratedFile file = renderer.renderUtf8(outPath, template, model); + return List.of(file); + } + + protected abstract Map buildModel(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index 67c58fa..c1cf2af 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -2,22 +2,22 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; -public final class MavenPomAdapter implements MavenPomPort { +public final class MavenPomAdapter extends AbstractSingleTemplateArtifactAdapter + implements MavenPomPort { private static final String KEY_GROUP_ID = "groupId"; private static final String KEY_ARTIFACT_ID = "artifactId"; @@ -33,16 +33,13 @@ public final class MavenPomAdapter implements MavenPomPort { private static final PomDependency TEST_STARTER = PomDependency.of("org.springframework.boot", "spring-boot-starter-test", null, "test"); - private final TemplateRenderer renderer; - private final ArtifactProperties artifactProperties; private final PomDependencyMapper pomDependencyMapper; public MavenPomAdapter( TemplateRenderer renderer, - ArtifactProperties artifactProperties, + ArtifactDefinition artifactDefinition, PomDependencyMapper pomDependencyMapper) { - this.renderer = renderer; - this.artifactProperties = artifactProperties; + super(renderer, artifactDefinition); this.pomDependencyMapper = pomDependencyMapper; } @@ -52,15 +49,7 @@ public ArtifactKey artifactKey() { } @Override - public Iterable generate(ProjectBlueprint blueprint) { - Path outPath = Path.of(artifactProperties.outputPath()); - String template = artifactProperties.template(); - Map model = buildModel(blueprint); - GeneratedFile file = renderer.renderUtf8(outPath, template, model); - return List.of(file); - } - - private Map buildModel(ProjectBlueprint bp) { + protected Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); PlatformTarget pt = bp.getPlatformTarget(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index bd7a89c..f6d6d85 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -2,34 +2,21 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; -import java.util.List; import java.util.Map; -public final class ApplicationYamlAdapter implements ConfigFilesPort { +public class ApplicationYamlAdapter extends AbstractSingleTemplateArtifactAdapter + implements ConfigFilesPort { private static final String KEY_PROJECT_NAME = "projectName"; - private final TemplateRenderer renderer; - private final ArtifactProperties artifactProperties; - - public ApplicationYamlAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { - this.renderer = renderer; - this.artifactProperties = artifactProperties; - } - - @Override - public Iterable generate(ProjectBlueprint blueprint) { - Path outPath = Path.of(artifactProperties.outputPath()); - String template = artifactProperties.template(); - Map model = buildModel(blueprint); - return List.of(renderer.renderUtf8(outPath, template, model)); + public ApplicationYamlAdapter(TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + super(renderer, artifactDefinition); } @Override @@ -37,7 +24,8 @@ public ArtifactKey artifactKey() { return ArtifactKey.APPLICATION_YAML; } - private Map buildModel(ProjectBlueprint blueprint) { + @Override + protected Map buildModel(ProjectBlueprint blueprint) { return Map.ofEntries(entry(KEY_PROJECT_NAME, blueprint.getName().value())); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 648a907..2e2f411 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -2,24 +2,24 @@ import static java.util.Map.entry; +import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; import java.util.List; import java.util.Map; -public final class ReadmeAdapter implements ReadmePort { +public final class ReadmeAdapter extends AbstractSingleTemplateArtifactAdapter + implements ReadmePort { private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; @@ -33,34 +33,23 @@ public final class ReadmeAdapter implements ReadmePort { private static final String KEY_SPRING_BOOT_VERSION = "springBootVersion"; private static final String KEY_DEPENDENCIES = "dependencies"; - private final TemplateRenderer renderer; - private final ArtifactProperties artifactProperties; private final PomDependencyMapper pomDependencyMapper; public ReadmeAdapter( TemplateRenderer renderer, - ArtifactProperties artifactProperties, + ArtifactDefinition artifactDefinition, PomDependencyMapper pomDependencyMapper) { - this.renderer = renderer; - this.artifactProperties = artifactProperties; + super(renderer, artifactDefinition); this.pomDependencyMapper = pomDependencyMapper; } - @Override - public Iterable generate(ProjectBlueprint blueprint) { - Path outPath = Path.of(artifactProperties.outputPath()); - String template = artifactProperties.template(); - Map model = buildModel(blueprint); - GeneratedFile generatedFile = renderer.renderUtf8(outPath, template, model); - return List.of(generatedFile); - } - @Override public ArtifactKey artifactKey() { return ArtifactKey.README; } - private Map buildModel(ProjectBlueprint bp) { + @Override + protected Map buildModel(ProjectBlueprint bp) { ProjectIdentity id = bp.getIdentity(); BuildOptions bo = bp.getBuildOptions(); PlatformTarget pt = bp.getPlatformTarget(); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index 80eeb45..57d9ca8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -6,7 +6,8 @@ import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; @@ -24,15 +25,15 @@ public final class SourceScaffolderAdapter implements SourceScaffolderPort { private static final String PACKAGE_PATH_DELIMITER = "."; private static final String FILE_PATH_DELIMITER = "/"; private final TemplateRenderer renderer; - private final ArtifactProperties artifactProperties; + private final ArtifactDefinition artifactDefinition; private final StringCaseFormatter stringCaseFormatter; public SourceScaffolderAdapter( - TemplateRenderer renderer, - ArtifactProperties artifactProperties, - StringCaseFormatter stringCaseFormatter) { + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { this.renderer = renderer; - this.artifactProperties = artifactProperties; + this.artifactDefinition = artifactDefinition; this.stringCaseFormatter = stringCaseFormatter; } @@ -47,18 +48,20 @@ public Iterable generate(ProjectBlueprint blueprint) { ProjectIdentity id = blueprint.getIdentity(); String className = - stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION; + stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION; Map model = - Map.ofEntries( - entry(KEY_PROJECT_PACKAGE, packageName.value()), - entry(KEY_CLASS_NAME, className)); + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); + + TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); + Path baseDir = Path.of(templateDefinition.outputPath()); + String template = templateDefinition.template(); - Path baseDir = Path.of(artifactProperties.outputPath()); String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); - GeneratedFile file = renderer.renderUtf8(outPath, artifactProperties.template(), model); + GeneratedFile file = renderer.renderUtf8(outPath, template, model); return List.of(file); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index cd3bc8d..856f7c9 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,18 +1,69 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; public final class TestScaffolderAdapter implements TestScaffolderPort { - @Override - public Iterable generate(ProjectBlueprint blueprint) { - throw new UnsupportedOperationException("TestScaffolderAdapter.generate not implemented yet"); + + public static final String POSTFIX_APPLICATION_TESTS = "ApplicationTests"; + + private static final String KEY_PROJECT_PACKAGE = "projectPackageName"; + private static final String KEY_CLASS_NAME = "className"; + private static final String JAVA_FILE_EXTENSION = ".java"; + private static final String PACKAGE_PATH_DELIMITER = "."; + private static final String FILE_PATH_DELIMITER = "/"; + + private final TemplateRenderer renderer; + private final ArtifactDefinition artifactDefinition; + private final StringCaseFormatter stringCaseFormatter; + + public TestScaffolderAdapter( + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { + this.renderer = renderer; + this.artifactDefinition = artifactDefinition; + this.stringCaseFormatter = stringCaseFormatter; } @Override public ArtifactKey artifactKey() { - return null; + return ArtifactKey.TEST_SCAFFOLDER; + } + + @Override + public Iterable generate(ProjectBlueprint blueprint) { + PackageName packageName = blueprint.getPackageName(); + ProjectIdentity id = blueprint.getIdentity(); + + String className = + stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION_TESTS; + + Map model = + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); + + TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); + Path baseDir = Path.of(templateDefinition.outputPath()); + String template = templateDefinition.template(); + + String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); + Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); + + GeneratedFile file = renderer.renderUtf8(outPath, template, model); + return List.of(file); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index 9d5cacd..d413fb0 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,43 +1,30 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; +import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; import java.util.List; import java.util.Map; -public final class GitIgnoreAdapter implements GitIgnorePort { +public final class GitIgnoreAdapter extends AbstractSingleTemplateArtifactAdapter + implements GitIgnorePort { private static final String KEY_IGNORE_LIST = "ignoreList"; - private final TemplateRenderer renderer; - private final ArtifactProperties artifactProperties; - public GitIgnoreAdapter(TemplateRenderer renderer, ArtifactProperties artifactProperties) { - this.renderer = renderer; - this.artifactProperties = artifactProperties; + public GitIgnoreAdapter(TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + super(renderer, artifactDefinition); } @Override - public Iterable generate(ProjectBlueprint projectBlueprint) { - Path outPath = Path.of(artifactProperties.outputPath()); - String template = artifactProperties.template(); - Map model = buildModel(projectBlueprint.getBuildOptions()); - GeneratedFile generatedFile = renderer.renderUtf8(outPath, template, model); - return List.of(generatedFile); + protected Map buildModel(ProjectBlueprint blueprint) { + return Map.of(KEY_IGNORE_LIST, List.of()); } @Override public ArtifactKey artifactKey() { return ArtifactKey.GITIGNORE; } - - @SuppressWarnings("unused") - private Map buildModel(BuildOptions buildOptions) { - return Map.of(KEY_IGNORE_LIST, List.of()); - } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java index 794432f..79bbaee 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java @@ -7,8 +7,9 @@ public enum ArtifactKey { POM("pom"), GITIGNORE("gitignore"), APPLICATION_YAML("application-yaml"), - README("readme"), - SOURCE_SCAFFOLDER("source-scaffolder"); + SOURCE_SCAFFOLDER("source-scaffolder"), + TEST_SCAFFOLDER("test-scaffolder"), + README("readme"); private final String key; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java new file mode 100644 index 0000000..4de4599 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java @@ -0,0 +1,7 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record ArtifactDefinition(@Valid @NotNull List templates) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 777a93a..4609a08 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -13,30 +13,28 @@ @ConfigurationProperties(prefix = "codegen") public record CodegenProfilesProperties(@Valid @NotNull Map profiles) { - public ArtifactProperties artifact(ProfileType profile, ArtifactKey artifactKey) { - var p = requireProfile(profile); - var raw = requireArtifact(profile, p, artifactKey); - String fullTemplate = p.templateBasePath() + "/" + raw.template(); - return new ArtifactProperties(fullTemplate, raw.outputPath()); + public ArtifactDefinition artifact(ProfileType profile, ArtifactKey artifactKey) { + var profileProps = requireProfile(profile); + return requireArtifact(profile, profileProps, artifactKey); } public ProfileProperties requireProfile(ProfileType profile) { var key = profile.key(); - var p = profiles.get(key); - if (p == null) { + var profileProps = profiles.get(key); + if (profileProps == null) { throw new ProfileConfigurationException( ProfileConfigurationException.KEY_PROFILE_NOT_FOUND, key); } - return p; + return profileProps; } - ArtifactProperties requireArtifact( - ProfileType profile, ProfileProperties p, ArtifactKey artifactKey) { - var a = p.artifacts().get(artifactKey.key()); - if (a == null) { + ArtifactDefinition requireArtifact( + ProfileType profile, ProfileProperties profileProps, ArtifactKey artifactKey) { + var artifact = profileProps.artifacts().get(artifactKey.key()); + if (artifact == null) { throw new ProfileConfigurationException( ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); } - return a; + return artifact; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java index efcc0e0..6544a71 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java @@ -10,4 +10,4 @@ public record ProfileProperties( @NotBlank String templateBasePath, @Valid @NotNull List orderedArtifactKeys, - @Valid @NotNull Map artifacts) {} + @Valid @NotNull Map artifacts) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/TemplateDefinition.java similarity index 68% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/TemplateDefinition.java index 4071c48..a994066 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/TemplateDefinition.java @@ -2,4 +2,4 @@ import jakarta.validation.constraints.NotBlank; -public record ArtifactProperties(@NotBlank String template, @NotBlank String outputPath) {} +public record TemplateDefinition(@NotBlank String template, @NotBlank String outputPath) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java new file mode 100644 index 0000000..cebc5b4 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java @@ -0,0 +1,31 @@ +package io.github.bsayli.codegen.initializr.bootstrap.wiring; + +import io.github.bsayli.codegen.initializr.adapter.out.ProfileBasedArtifactsSelector; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ProjectArtifactsSelectorConfig { + + @Bean + public Map projectArtifactsPortRegistry( + ProjectArtifactsPort springBootMavenJavaArtifactsAdapter) { + + Map registry = new EnumMap<>(ProfileType.class); + registry.put(ProfileType.SPRINGBOOT_MAVEN_JAVA, springBootMavenJavaArtifactsAdapter); + return Collections.unmodifiableMap(registry); + } + + @Bean + public ProjectArtifactsSelector projectArtifactsSelector( + Map projectArtifactsPortRegistry) { + + return new ProfileBasedArtifactsSelector(projectArtifactsPortRegistry); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java index b5bdb07..167a046 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -7,6 +7,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source.SourceScaffolderAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test.TestScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; @@ -14,7 +15,7 @@ import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; -import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactProperties; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; import java.util.Collections; @@ -32,17 +33,27 @@ SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles, StringCaseFormatter stringCaseFormatter) { - ArtifactProperties props = + ArtifactDefinition props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.SOURCE_SCAFFOLDER); return new SourceScaffolderAdapter(renderer, props, stringCaseFormatter); } + @Bean + TestScaffolderAdapter springBootMavenJavaTestScaffolderAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + StringCaseFormatter stringCaseFormatter) { + ArtifactDefinition props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.TEST_SCAFFOLDER); + return new TestScaffolderAdapter(renderer, props, stringCaseFormatter); + } + @Bean MavenPomAdapter springBootMavenJavaPomAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles, PomDependencyMapper pomDependencyMapper) { - ArtifactProperties props = + ArtifactDefinition props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.POM); return new MavenPomAdapter(renderer, props, pomDependencyMapper); } @@ -50,7 +61,7 @@ MavenPomAdapter springBootMavenJavaPomAdapter( @Bean GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles) { - ArtifactProperties props = + ArtifactDefinition props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.GITIGNORE); return new GitIgnoreAdapter(renderer, props); } @@ -58,7 +69,7 @@ GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter( @Bean ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles) { - ArtifactProperties props = + ArtifactDefinition props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.APPLICATION_YAML); return new ApplicationYamlAdapter(renderer, props); } @@ -68,7 +79,7 @@ ReadmeAdapter springBootMavenJavaReadmeAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles, PomDependencyMapper pomDependencyMapper) { - ArtifactProperties props = + ArtifactDefinition props = profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.README); return new ReadmeAdapter(renderer, props, pomDependencyMapper); } @@ -79,6 +90,7 @@ Map springBootMavenJavaArtifactRegistry( GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter, ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter, SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter, + TestScaffolderAdapter springBootMavenTestScaffolderAdapter, ReadmeAdapter springBootMavenJavaReadmeAdapter) { Map registry = new EnumMap<>(ArtifactKey.class); @@ -86,6 +98,7 @@ Map springBootMavenJavaArtifactRegistry( registry.put(ArtifactKey.GITIGNORE, springBootMavenJavaGitIgnoreAdapter); registry.put(ArtifactKey.APPLICATION_YAML, springBootMavenJavaApplicationYamlAdapter); registry.put(ArtifactKey.SOURCE_SCAFFOLDER, springBootMavenJavaSourceScaffolderAdapter); + registry.put(ArtifactKey.TEST_SCAFFOLDER, springBootMavenTestScaffolderAdapter); registry.put(ArtifactKey.README, springBootMavenJavaReadmeAdapter); return Collections.unmodifiableMap(registry); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3a3e374..8156132 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,23 +7,47 @@ codegen: profiles: springboot-maven-java: template-base-path: springboot/maven/java - ordered-artifact-keys: [ pom, gitignore, application-yaml, source-scaffolder, readme ] # execution order + + ordered-artifact-keys: + - pom + - gitignore + - application-yaml + - source-scaffolder + - test-scaffolder + - readme + artifacts: + pom: - template: pom.xml.ftl - output-path: pom.xml + templates: + - template: pom.xml.ftl + output-path: pom.xml + gitignore: - template: .gitignore.ftl - output-path: .gitignore + templates: + - template: .gitignore.ftl + output-path: .gitignore + application-yaml: - template: application.yml.ftl - output-path: src/main/resources/application.yml + templates: + - template: application.yml.ftl + output-path: src/main/resources/application.yml + source-scaffolder: - template: MainClass.java.ftl - output-path: src/main/java + templates: + - template: MainClass.java.ftl + output-path: src/main/java + + test-scaffolder: + templates: + - template: MainClassTests.java.ftl + output-path: src/test/java + readme: - template: README.md.ftl - output-path: README.md + templates: + - template: README.md.ftl + output-path: README.md + templating: encoding: UTF-8 diff --git a/src/main/resources/templates/springboot/maven/java/MainClassTests.java.ftl b/src/main/resources/templates/springboot/maven/java/MainClassTests.java.ftl new file mode 100644 index 0000000..e6362a9 --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/MainClassTests.java.ftl @@ -0,0 +1,14 @@ +package ${projectPackageName}; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ${className} { + + @Test + void contextLoads() { + } + +} + From 86c8057995d9b7f639e0d711fac2ed0335c07158 Mon Sep 17 00:00:00 2001 From: bsayli Date: Fri, 14 Nov 2025 15:18:10 +0300 Subject: [PATCH 20/74] feat(generation): add full filesystem infrastructure + maven wrapper scaffolding - Implement MavenWrapperAdapter with model (wrapperVersion, mavenVersion) and integrate into SpringBootMavenJava profile wiring - Implement FileSystemProjectRootAdapter with dedicated exceptions: ProjectRootNotDirectoryException, ProjectRootAlreadyExistsException, ProjectRootIOException - Implement FileSystemProjectWriterAdapter using ProjectWriteException - Implement FileSystemProjectArchiverAdapter with: ProjectArchiveInvalidRootException, ProjectArchiveIOException and pure java.util.zip ZIP packaging - Remove legacy Commons Compress usage in archiver tests and provide pure-Java extractor - Add ProjectFilesystemConfig wiring for root, writer, and archiver ports - Ensure all filesystem and wrapper adapters follow hexagonal boundaries and consistent AdapterException hierarchy --- pom.xml | 8 +- .../exception/ProjectArchiveException.java | 13 +++ .../exception/ProjectArchiveIOException.java | 13 +++ .../ProjectArchiveInvalidRootException.java | 13 +++ .../ProjectRootAlreadyExistsException.java | 13 +++ .../exception/ProjectRootIOException.java | 13 +++ .../ProjectRootNotDirectoryException.java | 13 +++ .../exception/ProjectWriteException.java | 13 +++ .../FileSystemProjectArchiverAdapter.java | 97 +++++++++++++++++++ .../FileSystemProjectRootAdapter.java | 39 ++++++++ .../FileSystemProjectWriterAdapter.java | 37 +++++++ .../maven/java/build/MavenPomAdapter.java | 3 +- .../maven/java/docs/ReadmeAdapter.java | 3 +- .../AbstractJavaClassScaffolderAdapter.java | 64 ++++++++++++ .../java/source/SourceScaffolderAdapter.java | 45 ++------- .../java/test/TestScaffolderAdapter.java | 47 ++------- .../maven/java/vcs/GitIgnoreAdapter.java | 2 +- .../java/wrapper/MavenWrapperAdapter.java | 30 ++++-- .../port/out/artifacts/ArtifactKey.java | 1 + .../bootstrap/SpringBeansConfig.java | 6 -- .../wiring/ProjectFilesystemConfig.java | 29 ++++++ .../wiring/SpringBootMavenJavaConfig.java | 51 ++++++---- src/main/resources/application.yml | 9 +- src/main/resources/messages.properties | 9 +- .../maven/java/maven-wrapper.properties.ftl | 33 +++++++ .../adapters/ZipProjectArchiverTest.java | 17 ++-- .../ProjectGenerationServiceImplTest.java | 22 +++-- 27 files changed, 497 insertions(+), 146 deletions(-) create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveIOException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveInvalidRootException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootAlreadyExistsException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootIOException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootNotDirectoryException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectWriteException.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapter.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java create mode 100644 src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectFilesystemConfig.java create mode 100644 src/main/resources/templates/springboot/maven/java/maven-wrapper.properties.ftl diff --git a/pom.xml b/pom.xml index 71c246a..92ff08c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.7 @@ -47,7 +47,6 @@ 3.9.11 2.20.0 3.18.0 - 1.28.0 @@ -79,11 +78,6 @@ ${commons-lang3.version} - - org.apache.commons - commons-compress - ${commons-compress.version} - org.freemarker diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java new file mode 100644 index 0000000..2a8a31d --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectArchiveException extends AdapterException { + + private static final String KEY = "adapter.project.archive.failed"; + + public ProjectArchiveException(Path projectRoot, Throwable cause) { + super(KEY, cause, projectRoot); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveIOException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveIOException.java new file mode 100644 index 0000000..7e0077a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveIOException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectArchiveIOException extends AdapterException { + + private static final String KEY = "adapter.project.archive.io"; + + public ProjectArchiveIOException(Path root, Throwable cause) { + super(KEY, cause, root); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveInvalidRootException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveInvalidRootException.java new file mode 100644 index 0000000..29f6bec --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveInvalidRootException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectArchiveInvalidRootException extends AdapterException { + + private static final String KEY = "adapter.project.archive.invalid.root"; + + public ProjectArchiveInvalidRootException(Path root) { + super(KEY, root != null ? root : ""); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootAlreadyExistsException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootAlreadyExistsException.java new file mode 100644 index 0000000..f54669e --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootAlreadyExistsException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectRootAlreadyExistsException extends AdapterException { + + private static final String KEY = "adapter.project-root.already-exists"; + + public ProjectRootAlreadyExistsException(Path path) { + super(KEY, path); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootIOException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootIOException.java new file mode 100644 index 0000000..bebf3c7 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootIOException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectRootIOException extends AdapterException { + + private static final String KEY = "adapter.project-root.io.failed"; + + public ProjectRootIOException(Path path, Throwable cause) { + super(KEY, cause, path); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootNotDirectoryException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootNotDirectoryException.java new file mode 100644 index 0000000..8c21ddf --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectRootNotDirectoryException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectRootNotDirectoryException extends AdapterException { + + private static final String KEY = "adapter.project-root.not-directory"; + + public ProjectRootNotDirectoryException(Path path) { + super(KEY, path); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectWriteException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectWriteException.java new file mode 100644 index 0000000..42209ba --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectWriteException.java @@ -0,0 +1,13 @@ +package io.github.bsayli.codegen.initializr.adapter.error.exception; + +import java.nio.file.Path; + +@SuppressWarnings("java:S110") +public final class ProjectWriteException extends AdapterException { + + private static final String KEY = "adapter.project.write.failed"; + + public ProjectWriteException(Path path, Throwable cause) { + super(KEY, cause, path); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java new file mode 100644 index 0000000..1308d66 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java @@ -0,0 +1,97 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectArchiveIOException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectArchiveInvalidRootException; +import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class FileSystemProjectArchiverAdapter implements ProjectArchiverPort { + + private static final String ZIP_EXTENSION = ".zip"; + private static final char ZIP_SEPARATOR = '/'; + + @Override + public Path archive(Path projectRoot, String artifactId) { + if (projectRoot == null) { + throw new ProjectArchiveInvalidRootException(null); + } + + Path parent = projectRoot.getParent(); + if (parent == null) { + throw new ProjectArchiveInvalidRootException(projectRoot); + } + + if (!Files.exists(projectRoot) || !Files.isDirectory(projectRoot)) { + throw new ProjectArchiveInvalidRootException(projectRoot); + } + + String baseName = + (artifactId == null || artifactId.isBlank()) + ? projectRoot.getFileName().toString() + : artifactId; + + Path archivePath = parent.resolve(baseName + ZIP_EXTENSION); + + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(archivePath))) { + writeDirectoryToZip(projectRoot, baseName, zipOut); + return archivePath; + } catch (IOException e) { + throw new ProjectArchiveIOException(projectRoot, e); + } + } + + private void writeDirectoryToZip(Path root, String rootName, ZipOutputStream zos) + throws IOException { + Path normalizedRoot = root.toAbsolutePath().normalize(); + + try (Stream paths = Files.walk(normalizedRoot)) { + paths.forEachOrdered( + path -> { + try { + writeEntry(normalizedRoot, rootName, path, zos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + private void writeEntry(Path root, String rootName, Path current, ZipOutputStream zos) + throws IOException { + + Path relative = root.relativize(current); + StringBuilder entryName = new StringBuilder(); + entryName.append(rootName).append(ZIP_SEPARATOR); + + String rel = relative.toString(); + if (!rel.isEmpty()) { + String fsSep = root.getFileSystem().getSeparator(); + if (!fsSep.equals(String.valueOf(ZIP_SEPARATOR))) { + rel = rel.replace(fsSep, String.valueOf(ZIP_SEPARATOR)); + } + entryName.append(rel); + } + + boolean directory = Files.isDirectory(current); + if (directory && entryName.charAt(entryName.length() - 1) != ZIP_SEPARATOR) { + entryName.append(ZIP_SEPARATOR); + } + + ZipEntry entry = new ZipEntry(entryName.toString()); + zos.putNextEntry(entry); + + if (!directory) { + Files.copy(current, zos); + } + + zos.closeEntry(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapter.java new file mode 100644 index 0000000..0ceb55a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapter.java @@ -0,0 +1,39 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootAlreadyExistsException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootIOException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootNotDirectoryException; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileSystemProjectRootAdapter implements ProjectRootPort { + + @Override + public Path prepareRoot(Path targetDir, String artifactId, ProjectRootExistencePolicy policy) { + Path projectRoot = targetDir.resolve(artifactId); + + try { + if (Files.exists(projectRoot)) { + + if (!Files.isDirectory(projectRoot)) { + throw new ProjectRootNotDirectoryException(projectRoot); + } + + if (policy == ProjectRootExistencePolicy.FAIL_IF_EXISTS) { + throw new ProjectRootAlreadyExistsException(projectRoot); + } + + return projectRoot; + } + + Files.createDirectories(projectRoot); + return projectRoot; + + } catch (IOException e) { + throw new ProjectRootIOException(projectRoot, e); + } + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapter.java new file mode 100644 index 0000000..3c81548 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapter.java @@ -0,0 +1,37 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectWriteException; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +public class FileSystemProjectWriterAdapter implements ProjectWriterPort { + + @Override + public void writeBytes(Path projectRoot, Path relativePath, byte[] content) { + Path target = projectRoot.resolve(relativePath); + try { + Path parent = target.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.write( + target, + content, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE); + } catch (IOException e) { + throw new ProjectWriteException(target, e); + } + } + + @Override + public void writeText(Path projectRoot, Path relativePath, String content, Charset charset) { + byte[] bytes = content.getBytes(charset); + writeBytes(projectRoot, relativePath, bytes); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index c1cf2af..7cf5a0c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -16,8 +16,7 @@ import java.util.List; import java.util.Map; -public final class MavenPomAdapter extends AbstractSingleTemplateArtifactAdapter - implements MavenPomPort { +public class MavenPomAdapter extends AbstractSingleTemplateArtifactAdapter implements MavenPomPort { private static final String KEY_GROUP_ID = "groupId"; private static final String KEY_ARTIFACT_ID = "artifactId"; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 2e2f411..960cc16 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -18,8 +18,7 @@ import java.util.List; import java.util.Map; -public final class ReadmeAdapter extends AbstractSingleTemplateArtifactAdapter - implements ReadmePort { +public class ReadmeAdapter extends AbstractSingleTemplateArtifactAdapter implements ReadmePort { private static final String KEY_PROJECT_NAME = "projectName"; private static final String KEY_PROJECT_DESCRIPTION = "projectDescription"; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java new file mode 100644 index 0000000..fd6bf7a --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java @@ -0,0 +1,64 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; + +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public abstract class AbstractJavaClassScaffolderAdapter implements ArtifactPort { + + private static final String KEY_PROJECT_PACKAGE = "projectPackageName"; + private static final String KEY_CLASS_NAME = "className"; + private static final String JAVA_FILE_EXTENSION = ".java"; + + private static final String PACKAGE_PATH_DELIMITER = "."; + private static final String FILE_PATH_DELIMITER = "/"; + + private final TemplateRenderer renderer; + private final ArtifactDefinition artifactDefinition; + private final StringCaseFormatter stringCaseFormatter; + + protected AbstractJavaClassScaffolderAdapter( + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { + this.renderer = renderer; + this.artifactDefinition = artifactDefinition; + this.stringCaseFormatter = stringCaseFormatter; + } + + @Override + public final Iterable generate(ProjectBlueprint blueprint) { + String className = buildClassName(blueprint); + PackageName packageName = blueprint.getPackageName(); + + Map model = + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); + + TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); + Path baseDir = Path.of(templateDefinition.outputPath()); + String template = templateDefinition.template(); + + String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); + Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); + + GeneratedFile file = renderer.renderUtf8(outPath, template, model); + return List.of(file); + } + + protected String pascal(String value) { + return stringCaseFormatter.toPascalCase(value); + } + + protected abstract String buildClassName(ProjectBlueprint blueprint); +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index 57d9ca8..7a3b66b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -1,40 +1,24 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; -import static java.util.Map.entry; - +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.AbstractJavaClassScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; -import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; -import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -public final class SourceScaffolderAdapter implements SourceScaffolderPort { +public class SourceScaffolderAdapter extends AbstractJavaClassScaffolderAdapter + implements SourceScaffolderPort { public static final String POSTFIX_APPLICATION = "Application"; - private static final String KEY_PROJECT_PACKAGE = "projectPackageName"; - private static final String KEY_CLASS_NAME = "className"; - private static final String JAVA_FILE_EXTENSION = ".java"; - private static final String PACKAGE_PATH_DELIMITER = "."; - private static final String FILE_PATH_DELIMITER = "/"; - private final TemplateRenderer renderer; - private final ArtifactDefinition artifactDefinition; - private final StringCaseFormatter stringCaseFormatter; public SourceScaffolderAdapter( TemplateRenderer renderer, ArtifactDefinition artifactDefinition, StringCaseFormatter stringCaseFormatter) { - this.renderer = renderer; - this.artifactDefinition = artifactDefinition; - this.stringCaseFormatter = stringCaseFormatter; + super(renderer, artifactDefinition, stringCaseFormatter); } @Override @@ -43,25 +27,8 @@ public ArtifactKey artifactKey() { } @Override - public Iterable generate(ProjectBlueprint blueprint) { - PackageName packageName = blueprint.getPackageName(); + protected String buildClassName(ProjectBlueprint blueprint) { ProjectIdentity id = blueprint.getIdentity(); - - String className = - stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION; - - Map model = - Map.ofEntries( - entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); - - TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); - Path baseDir = Path.of(templateDefinition.outputPath()); - String template = templateDefinition.template(); - - String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); - Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); - - GeneratedFile file = renderer.renderUtf8(outPath, template, model); - return List.of(file); + return pascal(id.artifactId().value()) + POSTFIX_APPLICATION; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 856f7c9..98dbd29 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -1,42 +1,24 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; -import static java.util.Map.entry; - +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.AbstractJavaClassScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; -import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; -import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -public final class TestScaffolderAdapter implements TestScaffolderPort { +public class TestScaffolderAdapter extends AbstractJavaClassScaffolderAdapter + implements TestScaffolderPort { public static final String POSTFIX_APPLICATION_TESTS = "ApplicationTests"; - private static final String KEY_PROJECT_PACKAGE = "projectPackageName"; - private static final String KEY_CLASS_NAME = "className"; - private static final String JAVA_FILE_EXTENSION = ".java"; - private static final String PACKAGE_PATH_DELIMITER = "."; - private static final String FILE_PATH_DELIMITER = "/"; - - private final TemplateRenderer renderer; - private final ArtifactDefinition artifactDefinition; - private final StringCaseFormatter stringCaseFormatter; - public TestScaffolderAdapter( TemplateRenderer renderer, ArtifactDefinition artifactDefinition, StringCaseFormatter stringCaseFormatter) { - this.renderer = renderer; - this.artifactDefinition = artifactDefinition; - this.stringCaseFormatter = stringCaseFormatter; + super(renderer, artifactDefinition, stringCaseFormatter); } @Override @@ -45,25 +27,8 @@ public ArtifactKey artifactKey() { } @Override - public Iterable generate(ProjectBlueprint blueprint) { - PackageName packageName = blueprint.getPackageName(); + protected String buildClassName(ProjectBlueprint blueprint) { ProjectIdentity id = blueprint.getIdentity(); - - String className = - stringCaseFormatter.toPascalCase(id.artifactId().value()) + POSTFIX_APPLICATION_TESTS; - - Map model = - Map.ofEntries( - entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); - - TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); - Path baseDir = Path.of(templateDefinition.outputPath()); - String template = templateDefinition.template(); - - String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); - Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); - - GeneratedFile file = renderer.renderUtf8(outPath, template, model); - return List.of(file); + return pascal(id.artifactId().value()) + POSTFIX_APPLICATION_TESTS; } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index d413fb0..afcbcd3 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Map; -public final class GitIgnoreAdapter extends AbstractSingleTemplateArtifactAdapter +public class GitIgnoreAdapter extends AbstractSingleTemplateArtifactAdapter implements GitIgnorePort { private static final String KEY_IGNORE_LIST = "ignoreList"; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index 69c2b83..c2dcda1 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -1,19 +1,37 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; +import static java.util.Map.entry; + +import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; -import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.util.Map; -public final class MavenWrapperAdapter implements MavenWrapperPort { +public class MavenWrapperAdapter extends AbstractSingleTemplateArtifactAdapter + implements MavenWrapperPort { - @Override - public Iterable generate(ProjectBlueprint blueprint) { - throw new UnsupportedOperationException("MavenWrapperAdapter.generate not implemented yet"); + private static final String KEY_WRAPPER_VERSION = "wrapperVersion"; + private static final String KEY_MAVEN_VERSION = "mavenVersion"; + + private static final String DEFAULT_WRAPPER_VERSION = "3.3.3"; + private static final String DEFAULT_MAVEN_VERSION = "3.9.11"; + + public MavenWrapperAdapter(TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + super(renderer, artifactDefinition); } @Override public ArtifactKey artifactKey() { - return null; + return ArtifactKey.MAVEN_WRAPPER; + } + + @Override + protected Map buildModel(ProjectBlueprint blueprint) { + return Map.ofEntries( + entry(KEY_WRAPPER_VERSION, DEFAULT_WRAPPER_VERSION), + entry(KEY_MAVEN_VERSION, DEFAULT_MAVEN_VERSION)); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java index 79bbaee..93f14f5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java @@ -5,6 +5,7 @@ public enum ArtifactKey { POM("pom"), + MAVEN_WRAPPER("maven-wrapper"), GITIGNORE("gitignore"), APPLICATION_YAML("application-yaml"), SOURCE_SCAFFOLDER("source-scaffolder"), diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java deleted file mode 100644 index 877e242..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/SpringBeansConfig.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.bsayli.codegen.initializr.bootstrap; - -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SpringBeansConfig {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectFilesystemConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectFilesystemConfig.java new file mode 100644 index 0000000..b43c872 --- /dev/null +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectFilesystemConfig.java @@ -0,0 +1,29 @@ +package io.github.bsayli.codegen.initializr.bootstrap.wiring; + +import io.github.bsayli.codegen.initializr.adapter.out.filesystem.FileSystemProjectArchiverAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.filesystem.FileSystemProjectRootAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.filesystem.FileSystemProjectWriterAdapter; +import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootPort; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ProjectFilesystemConfig { + + @Bean + public ProjectRootPort fileSystemProjectRootAdapter() { + return new FileSystemProjectRootAdapter(); + } + + @Bean + public ProjectWriterPort fileSystemProjectWriterAdapter() { + return new FileSystemProjectWriterAdapter(); + } + + @Bean + public ProjectArchiverPort fileSystemProjectArchiverAdapter() { + return new FileSystemProjectArchiverAdapter(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java index 167a046..44a4c79 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -9,6 +9,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source.SourceScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test.TestScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper.MavenWrapperAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; @@ -28,26 +29,6 @@ @Configuration public class SpringBootMavenJavaConfig { - @Bean - SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter( - TemplateRenderer renderer, - CodegenProfilesProperties profiles, - StringCaseFormatter stringCaseFormatter) { - ArtifactDefinition props = - profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.SOURCE_SCAFFOLDER); - return new SourceScaffolderAdapter(renderer, props, stringCaseFormatter); - } - - @Bean - TestScaffolderAdapter springBootMavenJavaTestScaffolderAdapter( - TemplateRenderer renderer, - CodegenProfilesProperties profiles, - StringCaseFormatter stringCaseFormatter) { - ArtifactDefinition props = - profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.TEST_SCAFFOLDER); - return new TestScaffolderAdapter(renderer, props, stringCaseFormatter); - } - @Bean MavenPomAdapter springBootMavenJavaPomAdapter( TemplateRenderer renderer, @@ -58,6 +39,14 @@ MavenPomAdapter springBootMavenJavaPomAdapter( return new MavenPomAdapter(renderer, props, pomDependencyMapper); } + @Bean + MavenWrapperAdapter springBootMavenJavaWrapperAdapter( + TemplateRenderer renderer, CodegenProfilesProperties profiles) { + ArtifactDefinition props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.MAVEN_WRAPPER); + return new MavenWrapperAdapter(renderer, props); + } + @Bean GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter( TemplateRenderer renderer, CodegenProfilesProperties profiles) { @@ -74,6 +63,26 @@ ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter( return new ApplicationYamlAdapter(renderer, props); } + @Bean + SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + StringCaseFormatter stringCaseFormatter) { + ArtifactDefinition props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.SOURCE_SCAFFOLDER); + return new SourceScaffolderAdapter(renderer, props, stringCaseFormatter); + } + + @Bean + TestScaffolderAdapter springBootMavenJavaTestScaffolderAdapter( + TemplateRenderer renderer, + CodegenProfilesProperties profiles, + StringCaseFormatter stringCaseFormatter) { + ArtifactDefinition props = + profiles.artifact(ProfileType.SPRINGBOOT_MAVEN_JAVA, ArtifactKey.TEST_SCAFFOLDER); + return new TestScaffolderAdapter(renderer, props, stringCaseFormatter); + } + @Bean ReadmeAdapter springBootMavenJavaReadmeAdapter( TemplateRenderer renderer, @@ -87,6 +96,7 @@ ReadmeAdapter springBootMavenJavaReadmeAdapter( @Bean Map springBootMavenJavaArtifactRegistry( MavenPomAdapter springBootMavenJavaPomAdapter, + MavenWrapperAdapter springBootMavenJavaWrapperAdapter, GitIgnoreAdapter springBootMavenJavaGitIgnoreAdapter, ApplicationYamlAdapter springBootMavenJavaApplicationYamlAdapter, SourceScaffolderAdapter springBootMavenJavaSourceScaffolderAdapter, @@ -95,6 +105,7 @@ Map springBootMavenJavaArtifactRegistry( Map registry = new EnumMap<>(ArtifactKey.class); registry.put(ArtifactKey.POM, springBootMavenJavaPomAdapter); + registry.put(ArtifactKey.MAVEN_WRAPPER, springBootMavenJavaWrapperAdapter); registry.put(ArtifactKey.GITIGNORE, springBootMavenJavaGitIgnoreAdapter); registry.put(ArtifactKey.APPLICATION_YAML, springBootMavenJavaApplicationYamlAdapter); registry.put(ArtifactKey.SOURCE_SCAFFOLDER, springBootMavenJavaSourceScaffolderAdapter); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8156132..4bd130f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,7 +2,6 @@ spring: application: name: codegen-springboot-initializr - codegen: profiles: springboot-maven-java: @@ -10,6 +9,7 @@ codegen: ordered-artifact-keys: - pom + - maven-wrapper - gitignore - application-yaml - source-scaffolder @@ -17,12 +17,16 @@ codegen: - readme artifacts: - pom: templates: - template: pom.xml.ftl output-path: pom.xml + maven-wrapper: + templates: + - template: maven-wrapper.properties.ftl + output-path: .mvn/wrapper/maven-wrapper.properties + gitignore: templates: - template: .gitignore.ftl @@ -48,7 +52,6 @@ codegen: - template: README.md.ftl output-path: README.md - templating: encoding: UTF-8 handler: RETHROW diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index ffd8bde..0d39cab 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -78,4 +78,11 @@ adapter.artifacts.port.not.found=No artifact generator adapter registered for pr adapter.profile.unsupported=Unsupported profile combination: framework={0}, buildTool={1}, language={2} adapter.artifact.key.unknown=Unknown artifact key ''{0}''. adapter.generator.factory.not.found=No generator factory registered for artifact ''{0}''. -adapter.generator.key.mismatch=Generator artifact key mismatch (expected ''{0}'', actual ''{1}''). \ No newline at end of file +adapter.generator.key.mismatch=Generator artifact key mismatch (expected ''{0}'', actual ''{1}''). +adapter.project.write.failed=Failed to write generated file ''{0}''. +adapter.project-root.not-directory=Project root ''{0}'' exists but is not a directory. +adapter.project-root.already-exists=Project root ''{0}'' already exists. +adapter.project-root.io.failed=Failed to prepare project root at ''{0}''. +adapter.project.archive.failed=Failed to archive project at ''{0}''. +adapter.project.archive.invalid.root=Invalid project root ''{0}''. +adapter.project.archive.io=I/O error while archiving project ''{0}''. \ No newline at end of file diff --git a/src/main/resources/templates/springboot/maven/java/maven-wrapper.properties.ftl b/src/main/resources/templates/springboot/maven/java/maven-wrapper.properties.ftl new file mode 100644 index 0000000..ad0b9fc --- /dev/null +++ b/src/main/resources/templates/springboot/maven/java/maven-wrapper.properties.ftl @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Maven Wrapper core version (kept for visibility & tests) +wrapperVersion=${wrapperVersion} + +# Maven distribution to be used by the wrapper +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${mavenVersion}/apache-maven-${mavenVersion}-bin.zip +<#-- Optional checksum for Maven distribution --> +<#if distributionSha256Sum?? && distributionSha256Sum?has_content> + distributionSha256Sum=${distributionSha256Sum} + + +# Maven Wrapper JAR location (aligns with wrapperVersion above) +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/${wrapperVersion}/maven-wrapper-${wrapperVersion}.jar +<#-- Optional checksum for Wrapper jar --> +<#if wrapperSha256Sum?? && wrapperSha256Sum?has_content> + wrapperSha256Sum=${wrapperSha256Sum} + \ No newline at end of file diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java index 8a73fa4..7dc1b27 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java @@ -11,11 +11,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import org.apache.commons.compress.archivers.ArchiveEntry; -import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.annotation.Autowired; @@ -172,10 +170,9 @@ private File createExtractedProject(Path projectDir, File archivedFile) throws I boolean mkExtracted = extractedDir.mkdirs(); assertTrue(mkExtracted || extractedDir.exists(), "unarchived directory could not be created"); - try (ArchiveInputStream inputStream = - new ZipArchiveInputStream(new FileInputStream(archivedFile))) { - ArchiveEntry entry; - while ((entry = inputStream.getNextEntry()) != null) { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archivedFile))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) { File directory = new File(extractedDir, entry.getName()); boolean mk = directory.mkdirs(); @@ -185,10 +182,12 @@ private File createExtractedProject(Path projectDir, File archivedFile) throws I File parent = file.getParentFile(); boolean mk = parent.mkdirs(); assertTrue(mk || parent.exists(), "Failed to create parent directory: " + parent); + try (OutputStream outputStream = new FileOutputStream(file)) { - IOUtils.copy(inputStream, outputStream); + zis.transferTo(outputStream); } } + zis.closeEntry(); } } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java index 14becc4..93f249c 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java @@ -19,11 +19,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.apache.commons.compress.archivers.ArchiveEntry; -import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -120,10 +118,11 @@ private File createExtractedProject(File archivedProjectFile) throws IOException File extractedDir = new File(archivedProjectFile.getParentFile(), DIRECTORY_NAME_UNARCHIVED); ensureDir(extractedDir, "Failed to create extracted dir: " + extractedDir); - try (ArchiveInputStream inputStream = - new ZipArchiveInputStream(new FileInputStream(archivedProjectFile))) { - ArchiveEntry entry; - while ((entry = inputStream.getNextEntry()) != null) { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archivedProjectFile))) { + ZipEntry entry; + byte[] buffer = new byte[8192]; + + while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) { File directory = new File(extractedDir, entry.getName()); ensureDir(directory, "Failed to create directory: " + directory); @@ -131,10 +130,15 @@ private File createExtractedProject(File archivedProjectFile) throws IOException File file = new File(extractedDir, entry.getName()); File parent = file.getParentFile(); ensureDir(parent, "Failed to create parent directory: " + parent); + try (OutputStream outputStream = new FileOutputStream(file)) { - IOUtils.copy(inputStream, outputStream); + int len; + while ((len = zis.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } } } + zis.closeEntry(); } } From bd442871bdecdcf7b2104bdec86286fc6ebd50ac Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 18 Nov 2025 18:43:29 +0300 Subject: [PATCH 21/74] test(domain): add comprehensive unit tests for all domain value objects, policies and factories - Added full unit test suites for: * Naming: ProjectName, ProjectDescription * Identity: GroupId, ArtifactId, ProjectIdentity * PackageName and segment rules * Dependencies: Dependency, DependencyCoordinates, DependencyVersion, Dependencies * Tech stack: BuildOptions, PlatformTarget, JavaVersion/SpringBootVersion combos * CompatibilityPolicy and PlatformTargetSelector * ProjectBlueprintFactory end-to-end null/compatibility validation - Ensured correct message keys for all DomainViolationException cases - Normalized tests to follow unified naming and @Tag("unit", "domain") scheme - Fixed minor policy edge cases (control chars, reserved names, length rules) - All domain layer tests now passing and warning-clean --- .../naming/ProjectDescriptionPolicy.java | 4 +- .../policy/rule/ReservedPrefixRule.java | 1 - .../CreateProjectHandlerTest.java | 2 +- .../ProjectBlueprintMapperTest.java | 4 +- .../factory/ProjectBlueprintFactoryTest.java | 232 ++++++++++++++++++ .../value/dependency/DependenciesTest.java | 110 +++++++++ .../dependency/DependencyCoordinatesTest.java | 57 +++++ .../value/dependency/DependencyTest.java | 72 ++++++ .../dependency/DependencyVersionTest.java | 88 +++++++ .../model/value/identity/ArtifactIdTest.java | 103 ++++++++ .../model/value/identity/GroupIdTest.java | 71 ++++++ .../value/identity/ProjectIdentityTest.java | 55 +++++ .../value/naming/ProjectDescriptionTest.java | 70 ++++++ .../model/value/naming/ProjectNameTest.java | 123 ++++++++++ .../model/value/pkg/PackageNameTest.java | 107 ++++++++ .../tech/platform/PlatformTargetTest.java | 62 +++++ .../value/tech/stack/BuildOptionsTest.java | 61 +++++ .../policy/tech/CompatibilityPolicyTest.java | 119 +++++++++ .../tech/PlatformTargetSelectorTest.java | 65 +++++ .../port/out/artifact/GeneratedFileTest.java | 90 +++++++ 20 files changed, 1490 insertions(+), 6 deletions(-) create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactoryTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependenciesTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinatesTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersionTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactIdTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupIdTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentityTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectNameTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageNameTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptionsTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicyTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFileTest.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java index 4c542e2..f830a8b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.domain.policy.naming; import static io.github.bsayli.codegen.initializr.domain.error.code.Field.PROJECT_DESCRIPTION; -import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.INVALID_CHARS; +import static io.github.bsayli.codegen.initializr.domain.error.code.Violation.CONTROL_CHARS; import io.github.bsayli.codegen.initializr.domain.policy.rule.LengthBetweenRule; import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; @@ -34,7 +34,7 @@ private static void validate(String value) { Rule rule = CompositeRule.of( new LengthBetweenRule(MIN, MAX, PROJECT_DESCRIPTION), - new RegexMatchRule(NO_CONTROL_CHARS, PROJECT_DESCRIPTION, INVALID_CHARS)); + new RegexMatchRule(NO_CONTROL_CHARS, PROJECT_DESCRIPTION, CONTROL_CHARS)); rule.check(value); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java index 086e731..bb4a631 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java @@ -1,4 +1,3 @@ -// src/main/java/io/github/bsayli/codegen/initializr/domain/policy/rule/ReservedPrefixRule.java package io.github.bsayli.codegen.initializr.domain.policy.rule; import static io.github.bsayli.codegen.initializr.domain.error.code.ErrorKeys.compose; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java index dda7d13..d8068ee 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java @@ -29,7 +29,7 @@ @Tag("unit") @Tag("application") -@DisplayName("CreateProjectHandler") +@DisplayName("Unit Test: CreateProjectHandler") class CreateProjectHandlerTest { @TempDir Path tempDir; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java index 1e7e72a..c89a9fa 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.Test; @Tag("unit") -@Tag("mapper") -@DisplayName("ProjectBlueprintMapper") +@Tag("application") +@DisplayName("Unit Test: ProjectBlueprintMapper") class ProjectBlueprintMapperTest { private static ProjectBlueprint getProjectBlueprint() { diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactoryTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactoryTest.java new file mode 100644 index 0000000..137b116 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/factory/ProjectBlueprintFactoryTest.java @@ -0,0 +1,232 @@ +package io.github.bsayli.codegen.initializr.domain.factory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: ProjectBlueprintFactory") +class ProjectBlueprintFactoryTest { + + private static ProjectIdentity identity() { + return new ProjectIdentity(new GroupId("com.example"), new ArtifactId("demo-app")); + } + + private static ProjectName name() { + return new ProjectName("demo-app"); + } + + private static ProjectDescription description() { + return new ProjectDescription("simple demo project"); + } + + private static PackageName pkg() { + return new PackageName("com.example.demo"); + } + + private static BuildOptions buildOptions() { + return new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + } + + private static PlatformTarget target() { + return new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + } + + private static Dependencies dependencies() { + Dependency d = dep("org.acme", "alpha"); + return Dependencies.of(List.of(d)); + } + + private static Dependency dep(String groupId, String artifactId) { + return new Dependency( + new DependencyCoordinates(new GroupId(groupId), new ArtifactId(artifactId)), null, null); + } + + @Test + @DisplayName( + "of(identity, name, desc, package, options, target, dependencies) should create blueprint with same references") + void of_withDependenciesObject_shouldCreateBlueprint() { + ProjectIdentity identity = identity(); + ProjectName name = name(); + ProjectDescription description = description(); + PackageName packageName = pkg(); + BuildOptions options = buildOptions(); + PlatformTarget target = target(); + Dependencies dependencies = dependencies(); + + ProjectBlueprint bp = + ProjectBlueprintFactory.of( + identity, name, description, packageName, options, target, dependencies); + + assertThat(bp.getIdentity()).isSameAs(identity); + assertThat(bp.getName()).isSameAs(name); + assertThat(bp.getDescription()).isSameAs(description); + assertThat(bp.getPackageName()).isSameAs(packageName); + assertThat(bp.getBuildOptions()).isSameAs(options); + assertThat(bp.getPlatformTarget()).isSameAs(target); + assertThat(bp.getDependencies()).isSameAs(dependencies); + } + + @Test + @DisplayName("of(..., List) should wrap list into Dependencies") + void of_withDependencyList_shouldWrapIntoDependencies() { + var d1 = dep("org.acme", "alpha"); + var d2 = dep("org.example", "beta"); + + ProjectBlueprint bp = + ProjectBlueprintFactory.of( + identity(), name(), description(), pkg(), buildOptions(), target(), List.of(d1, d2)); + + assertThat(bp.getDependencies()).isNotNull(); + assertThat(bp.getDependencies().asList()).hasSize(2); + } + + @Test + @DisplayName("of(..., Dependency...) should wrap varargs into Dependencies") + void of_withVarargs_shouldWrapIntoDependencies() { + var d1 = dep("org.acme", "alpha"); + var d2 = dep("org.example", "beta"); + + ProjectBlueprint bp = + ProjectBlueprintFactory.of( + identity(), name(), description(), pkg(), buildOptions(), target(), d1, d2); + + assertThat(bp.getDependencies()).isNotNull(); + assertThat(bp.getDependencies().asList()).hasSize(2); + } + + @Test + @DisplayName("null identity should fail with project.identity.not.blank") + void nullIdentity_shouldFailIdentityRequired() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + null, name(), description(), pkg(), buildOptions(), target(), dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("project.identity.not.blank")); + } + + @Test + @DisplayName("null project name should fail with project.name.not.blank") + void nullProjectName_shouldFailNotBlankRule() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), + null, + description(), + pkg(), + buildOptions(), + target(), + dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("project.name.not.blank")); + } + + @Test + @DisplayName("null package name should fail with project.package-name.not.blank") + void nullPackageName_shouldFailNotBlankRule() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), + name(), + description(), + null, + buildOptions(), + target(), + dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("project.package-name.not.blank")); + } + + @Test + @DisplayName("null build options should fail with project.build-options.not.blank") + void nullBuildOptions_shouldFailBuildOptionsRequired() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), name(), description(), pkg(), null, target(), dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("project.build-options.not.blank")); + } + + @Test + @DisplayName("null platform target should fail with platform.target.not.blank") + void nullPlatformTarget_shouldFailTargetRequired() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), name(), description(), pkg(), buildOptions(), null, dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.not.blank")); + } + + @Test + @DisplayName("null dependencies should fail with dependency.list.not.blank") + void nullDependencies_shouldFailDependenciesRequired() { + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), + name(), + description(), + pkg(), + buildOptions(), + target(), + (Dependencies) null)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("dependency.list.not.blank")); + } + + @Test + @DisplayName("incompatible platform target should delegate to CompatibilityPolicy and fail") + void incompatiblePlatformTarget_shouldFailCompatibility() { + BuildOptions options = buildOptions(); + PlatformTarget incompatible = + new PlatformTarget(JavaVersion.JAVA_25, SpringBootVersion.V3_4_10); + + assertThatThrownBy( + () -> + ProjectBlueprintFactory.of( + identity(), + name(), + description(), + pkg(), + options, + incompatible, + dependencies())) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.incompatible")); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependenciesTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependenciesTest.java new file mode 100644 index 0000000..7b4355f --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependenciesTest.java @@ -0,0 +1,110 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: Dependencies") +class DependenciesTest { + + private static Dependency dep(String groupId, String artifactId) { + return new Dependency( + new DependencyCoordinates(new GroupId(groupId), new ArtifactId(artifactId)), null, null); + } + + @Test + @DisplayName("of(null) should fail with LIST_REQUIRED") + void of_nullList_shouldFailListRequired() { + assertThatThrownBy(() -> Dependencies.of(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.list.not.blank"); + }); + } + + @Test + @DisplayName("of(emptyList) should return empty Dependencies") + void of_emptyList_shouldReturnEmptyDependencies() { + Dependencies deps = Dependencies.of(List.of()); + + assertThat(deps.asList()).isEmpty(); + assertThat(deps.isEmpty()).isTrue(); + } + + @Test + @DisplayName("of(non-empty list) should sort by groupId:artifactId and be immutable") + void of_nonEmptyList_shouldSortAndBeImmutable() { + Dependency depZ = dep("org.zeta", "beta"); + Dependency depA = dep("org.alpha", "alpha"); + + List raw = new ArrayList<>(); + raw.add(depZ); + raw.add(depA); + + Dependencies deps = Dependencies.of(raw); + + var list = deps.asList(); + assertThat(list).hasSize(2); + + assertThat(list.get(0).coordinates().groupId().value()).isEqualTo("org.alpha"); + assertThat(list.get(0).coordinates().artifactId().value()).isEqualTo("alpha"); + assertThat(list.get(1).coordinates().groupId().value()).isEqualTo("org.zeta"); + assertThat(list.get(1).coordinates().artifactId().value()).isEqualTo("beta"); + + raw.add(dep("org.extra", "extra")); + + var snapshot = deps.asList(); + assertThat(snapshot).hasSize(2); + + Dependency extraDep = dep("org.any", "any"); + assertThatThrownBy(() -> snapshot.add(extraDep)) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("of(list with null item) should fail ITEM_REQUIRED") + void of_listWithNullItem_shouldFailItemRequired() { + Dependency d1 = dep("org.acme", "alpha"); + List raw = new ArrayList<>(); + raw.add(d1); + raw.add(null); + + assertThatThrownBy(() -> Dependencies.of(raw)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.item.not.blank"); + }); + } + + @Test + @DisplayName("of(list with duplicate coordinates) should fail DUPLICATE_COORDS") + void of_listWithDuplicateCoords_shouldFailDuplicateCoords() { + Dependency d1 = dep("org.acme", "common"); + Dependency d2 = dep("org.acme", "common"); + + List raw = List.of(d1, d2); + + assertThatThrownBy(() -> Dependencies.of(raw)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.duplicate.coordinates"); + assertThat(dve.getArgs()).containsExactly("org.acme:common"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinatesTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinatesTest.java new file mode 100644 index 0000000..d56d2fb --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyCoordinatesTest.java @@ -0,0 +1,57 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: DependencyCoordinates") +class DependencyCoordinatesTest { + + @Test + @DisplayName("valid groupId and artifactId should be accepted") + void validCoordinates_shouldBeAccepted() { + GroupId g = new GroupId("org.acme"); + ArtifactId a = new ArtifactId("demo-artifact"); + + DependencyCoordinates coords = new DependencyCoordinates(g, a); + + assertThat(coords.groupId()).isSameAs(g); + assertThat(coords.artifactId()).isSameAs(a); + } + + @Test + @DisplayName("null groupId should fail COORDINATES_REQUIRED") + void nullGroupId_shouldFailCoordinatesRequired() { + ArtifactId a = new ArtifactId("demo-artifact"); + + assertThatThrownBy(() -> new DependencyCoordinates(null, a)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.coordinates.not.blank"); + }); + } + + @Test + @DisplayName("null artifactId should fail COORDINATES_REQUIRED") + void nullArtifactId_shouldFailCoordinatesRequired() { + GroupId g = new GroupId("org.acme"); + + assertThatThrownBy(() -> new DependencyCoordinates(g, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.coordinates.not.blank"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyTest.java new file mode 100644 index 0000000..4313cb0 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyTest.java @@ -0,0 +1,72 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: Dependency") +class DependencyTest { + + private static DependencyCoordinates coords(String groupId, String artifactId) { + return new DependencyCoordinates(new GroupId(groupId), new ArtifactId(artifactId)); + } + + @Test + @DisplayName("non-null coordinates should be accepted") + void nonNullCoordinates_shouldBeAccepted() { + DependencyCoordinates coords = coords("org.acme", "demo"); + Dependency d = new Dependency(coords, new DependencyVersion("1.0.0"), DependencyScope.RUNTIME); + + assertThat(d.coordinates()).isSameAs(coords); + assertThat(d.version().value()).isEqualTo("1.0.0"); + assertThat(d.scope()).isEqualTo(DependencyScope.RUNTIME); + } + + @Test + @DisplayName("null coordinates should fail COORDINATES_REQUIRED") + void nullCoordinates_shouldFailCoordinatesRequired() { + assertThatThrownBy(() -> new Dependency(null, null, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.coordinates.not.blank"); + }); + } + + @Test + @DisplayName("isDefaultScope should be true when scope is null") + void isDefaultScope_shouldBeTrueWhenScopeIsNull() { + Dependency d = new Dependency(coords("org.acme", "demo"), new DependencyVersion("1.0.0"), null); + + assertThat(d.isDefaultScope()).isTrue(); + } + + @Test + @DisplayName("isDefaultScope should be true when scope is COMPILE") + void isDefaultScope_shouldBeTrueWhenScopeIsCompile() { + Dependency d = + new Dependency( + coords("org.acme", "demo"), new DependencyVersion("1.0.0"), DependencyScope.COMPILE); + + assertThat(d.isDefaultScope()).isTrue(); + } + + @Test + @DisplayName("isDefaultScope should be false when scope is not COMPILE") + void isDefaultScope_shouldBeFalseWhenScopeIsNotCompile() { + Dependency d = + new Dependency( + coords("org.acme", "demo"), new DependencyVersion("1.0.0"), DependencyScope.RUNTIME); + + assertThat(d.isDefaultScope()).isFalse(); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersionTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersionTest.java new file mode 100644 index 0000000..e7a1e74 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyVersionTest.java @@ -0,0 +1,88 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.dependency; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: DependencyVersion") +class DependencyVersionTest { + + @Test + @DisplayName("valid version should be accepted as-is") + void validVersion_shouldBeAccepted() { + DependencyVersion v = new DependencyVersion("1.2.3"); + assertThat(v.value()).isEqualTo("1.2.3"); + } + + @Test + @DisplayName("version with surrounding spaces should be trimmed") + void versionWithSpaces_shouldBeTrimmed() { + DependencyVersion v = new DependencyVersion(" 1.2.3-SNAPSHOT "); + assertThat(v.value()).isEqualTo("1.2.3-SNAPSHOT"); + } + + @Test + @DisplayName("null version should fail NOT_BLANK rule") + void nullVersion_shouldFailNotBlankRule() { + assertThatThrownBy(() -> new DependencyVersion(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.version.not.blank"); + }); + } + + @Test + @DisplayName("blank version should fail NOT_BLANK rule") + void blankVersion_shouldFailNotBlankRule() { + assertThatThrownBy(() -> new DependencyVersion(" ")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.version.not.blank"); + }); + } + + @Test + @DisplayName("too long version should fail LENGTH rule") + void tooLongVersion_shouldFailLengthRule() { + String longValue = "a".repeat(101); + + assertThatThrownBy(() -> new DependencyVersion(longValue)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.version.length"); + }); + } + + @Test + @DisplayName("version with invalid chars should fail INVALID_CHARS rule") + void invalidChars_shouldFailInvalidCharsRule() { + String bad = "1.0.0!final"; + + assertThatThrownBy(() -> new DependencyVersion(bad)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("dependency.version.invalid.chars"); + }); + } + + @Test + @DisplayName("version with allowed special chars should be accepted") + void allowedSpecialChars_shouldBeAccepted() { + DependencyVersion v = new DependencyVersion("1.0.0-RC1+[classifier]_extra(1),{meta}:$var"); + assertThat(v.value()).isEqualTo("1.0.0-RC1+[classifier]_extra(1),{meta}:$var"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactIdTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactIdTest.java new file mode 100644 index 0000000..5d9e9b2 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ArtifactIdTest.java @@ -0,0 +1,103 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.identity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: ArtifactId") +class ArtifactIdTest { + + @Test + @DisplayName("valid raw value should be normalized and accepted") + void validValue_shouldNormalizeAndAccept() { + ArtifactId id = new ArtifactId(" My_Artifact Id "); + + assertThat(id.value()).isEqualTo("my-artifact-id"); + } + + @Test + @DisplayName("null should throw NOT_BLANK violation with correct message key") + void nullValue_shouldThrowNotBlank() { + assertThatThrownBy(() -> new ArtifactId(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.not.blank"); + }); + } + + @Test + @DisplayName("too short value should fail LENGTH rule") + void tooShort_shouldFailLengthRule() { + assertThatThrownBy(() -> new ArtifactId("ab")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.length"); + }); + } + + @Test + @DisplayName("value with invalid characters should fail INVALID_CHARS rule") + void invalidCharacters_shouldFailInvalidCharsRule() { + assertThatThrownBy(() -> new ArtifactId("my$app")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.invalid.chars"); + }); + } + + @Test + @DisplayName("value starting with non letter should fail STARTS_WITH_LETTER rule") + void startsWithNonLetter_shouldFailStartsWithLetterRule() { + assertThatThrownBy(() -> new ArtifactId("1artifact")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.starts.with.letter"); + }); + } + + @Test + @DisplayName("leading dash should fail STARTS_WITH_LETTER rule") + void leadingDash_shouldFailStartsWithLetterRule() { + assertThatThrownBy(() -> new ArtifactId("-artifact")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.starts.with.letter"); + }); + } + + @Test + @DisplayName("trailing dash should fail EDGE_CHAR rule") + void trailingDash_shouldFailEdgeCharRule() { + assertThatThrownBy(() -> new ArtifactId("artifact-")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.artifact-id.edge.char"); + }); + } + + @Test + @DisplayName("multiple consecutive dashes are normalized to a single dash") + void consecutiveDashes_areNormalizedToSingleDash() { + ArtifactId id = new ArtifactId("my--artifact---id"); + + assertThat(id.value()).isEqualTo("my-artifact-id"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupIdTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupIdTest.java new file mode 100644 index 0000000..8d35951 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/GroupIdTest.java @@ -0,0 +1,71 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.identity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: GroupId") +class GroupIdTest { + + @Test + @DisplayName("valid raw value should be normalized and accepted") + void validValue_shouldNormalizeAndAccept() { + GroupId groupId = new GroupId(" Com.Example.App "); + + assertThat(groupId.value()).isEqualTo("com.example.app"); + } + + @Test + @DisplayName("null should throw NOT_BLANK violation with correct message key") + void nullValue_shouldThrowNotBlank() { + assertThatThrownBy(() -> new GroupId(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.group-id.not.blank"); + }); + } + + @Test + @DisplayName("too short value should fail LENGTH rule") + void tooShort_shouldFailLengthRule() { + assertThatThrownBy(() -> new GroupId("ab")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.group-id.length"); + }); + } + + @Test + @DisplayName("value with invalid segment format should fail SEGMENT_FORMAT rule") + void invalidSegmentFormat_shouldFailSegmentFormatRule() { + assertThatThrownBy(() -> new GroupId("com..example")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.group-id.segment.format"); + }); + } + + @Test + @DisplayName("segment starting with non-letter should fail SEGMENT_FORMAT rule") + void segmentStartingWithNonLetter_shouldFailSegmentFormatRule() { + assertThatThrownBy(() -> new GroupId("com.1example")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.group-id.segment.format"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentityTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentityTest.java new file mode 100644 index 0000000..fafe26d --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/identity/ProjectIdentityTest.java @@ -0,0 +1,55 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.identity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: ProjectIdentity") +class ProjectIdentityTest { + + @Test + @DisplayName("valid groupId and artifactId should be accepted") + void validIdentity_shouldBeAccepted() { + GroupId groupId = new GroupId("io.github.bsayli"); + ArtifactId artifactId = new ArtifactId("demo-app"); + + ProjectIdentity identity = new ProjectIdentity(groupId, artifactId); + + assertThat(identity.groupId()).isSameAs(groupId); + assertThat(identity.artifactId()).isSameAs(artifactId); + } + + @Test + @DisplayName("null groupId should fail IDENTITY_REQUIRED") + void nullGroupId_shouldFailIdentityRequired() { + ArtifactId artifactId = new ArtifactId("demo-app"); + + assertThatThrownBy(() -> new ProjectIdentity(null, artifactId)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.identity.not.blank"); + }); + } + + @Test + @DisplayName("null artifactId should fail IDENTITY_REQUIRED") + void nullArtifactId_shouldFailIdentityRequired() { + GroupId groupId = new GroupId("io.github.bsayli"); + + assertThatThrownBy(() -> new ProjectIdentity(groupId, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.identity.not.blank"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java new file mode 100644 index 0000000..152357b --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java @@ -0,0 +1,70 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.naming; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: ProjectDescription") +class ProjectDescriptionTest { + + @Test + @DisplayName("valid description should be normalized and accepted") + void validDescription_shouldNormalizeAndAccept() { + ProjectDescription desc = new ProjectDescription(" This is A Test "); + + assertThat(desc.value()).isEqualTo("this is a test"); + assertThat(desc.isEmpty()).isFalse(); + } + + @Test + @DisplayName("null description should become empty string and be valid") + void nullDescription_shouldBecomeEmptyAndValid() { + ProjectDescription desc = new ProjectDescription(null); + + assertThat(desc.value()).isEmpty(); + assertThat(desc.isEmpty()).isTrue(); + } + + @Test + @DisplayName("blank description should normalize to empty string and be valid") + void blankDescription_shouldNormalizeToEmptyAndBeValid() { + ProjectDescription desc = new ProjectDescription(" "); + + assertThat(desc.value()).isEmpty(); + assertThat(desc.isEmpty()).isTrue(); + } + + @Test + @DisplayName("too long description should fail LENGTH rule") + void tooLongDescription_shouldFailLengthRule() { + String longText = "a".repeat(281); + + assertThatThrownBy(() -> new ProjectDescription(longText)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.description.length"); + }); + } + + @Test + @DisplayName("description with control chars should fail CONTROL_CHARS rule") + void controlChars_shouldFailInvalidCharsRule() { + String bad = "valid" + '\u0001' + "text"; + + assertThatThrownBy(() -> new ProjectDescription(bad)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.description.control.chars"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectNameTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectNameTest.java new file mode 100644 index 0000000..be98674 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectNameTest.java @@ -0,0 +1,123 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.naming; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: ProjectName") +class ProjectNameTest { + + @Test + @DisplayName("valid raw value should be normalized and accepted") + void validValue_shouldNormalizeAndAccept() { + ProjectName name = new ProjectName(" My Project_Name "); + + assertThat(name.value()).isEqualTo("my-project-name"); + } + + @Test + @DisplayName("null should throw NOT_BLANK violation with correct message key") + void nullValue_shouldThrowNotBlank() { + assertThatThrownBy(() -> new ProjectName(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.not.blank"); + }); + } + + @Test + @DisplayName("too short value should fail LENGTH rule") + void tooShort_shouldFailLengthRule() { + assertThatThrownBy(() -> new ProjectName("ab")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.length"); + }); + } + + @Test + @DisplayName("value with invalid characters should fail INVALID_CHARS rule") + void invalidCharacters_shouldFailInvalidCharsRule() { + assertThatThrownBy(() -> new ProjectName("my$app")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.invalid.chars"); + }); + } + + @Test + @DisplayName("value starting with non letter should fail STARTS_WITH_LETTER rule") + void startsWithNonLetter_shouldFailStartsWithLetterRule() { + assertThatThrownBy(() -> new ProjectName("1project")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.starts.with.letter"); + }); + } + + @Test + @DisplayName("leading dash should fail STARTS_WITH_LETTER rule") + void leadingDash_shouldFailStartsWithLetterRule() { + assertThatThrownBy(() -> new ProjectName("-my-project")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.starts.with.letter"); + }); + } + + @Test + @DisplayName("trailing dash should fail EDGE_CHAR rule") + void trailingDash_shouldFailEdgeCharRule() { + assertThatThrownBy(() -> new ProjectName("my-project-")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.edge.char"); + }); + } + + @Test + @DisplayName("multiple consecutive dashes are normalized to a single dash") + void consecutiveDashes_areNormalizedToSingleDash() { + ProjectName name = new ProjectName("my--project---demo"); + + assertThat(name.value()).isEqualTo("my-project-demo"); + } + + @Test + @DisplayName("reserved base names (CON, PRN, COM1, LPT2...) should fail RESERVED rule") + void reservedNames_shouldFailReservedRule() { + assertThatThrownBy(() -> new ProjectName("con")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.reserved"); + }); + + assertThatThrownBy(() -> new ProjectName("COM1")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.name.reserved"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageNameTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageNameTest.java new file mode 100644 index 0000000..c9b3188 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/pkg/PackageNameTest.java @@ -0,0 +1,107 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.pkg; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: PackageName") +class PackageNameTest { + + @Test + @DisplayName("valid raw value should be normalized and accepted") + void validValue_shouldNormalizeAndAccept() { + PackageName pkg = new PackageName(" Com_Example-Api "); + + assertThat(pkg.value()).isEqualTo("com.example.api"); + } + + @Test + @DisplayName("null should throw NOT_BLANK violation with correct message key") + void nullValue_shouldThrowNotBlank() { + assertThatThrownBy(() -> new PackageName(null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.not.blank"); + }); + } + + @Test + @DisplayName("blank or only separators should throw NOT_BLANK after normalization") + void blankOrOnlySeparators_shouldThrowNotBlankAfterNormalization() { + assertThatThrownBy(() -> new PackageName(" ")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.not.blank"); + }); + + assertThatThrownBy(() -> new PackageName(" - _ - ")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.not.blank"); + }); + } + + @Test + @DisplayName("too short normalized value should fail LENGTH rule") + void tooShort_shouldFailLengthRule() { + assertThatThrownBy(() -> new PackageName("ab")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.length"); + }); + } + + @Test + @DisplayName("segment with invalid format should fail SEGMENT_FORMAT rule") + void invalidSegmentFormat_shouldFailSegmentFormatRule() { + assertThatThrownBy(() -> new PackageName("com.1example")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.segment.format"); + }); + } + + @Test + @DisplayName("reserved prefixes (java, javax, sun, com.sun) should fail RESERVED_PREFIX rule") + void reservedPrefix_shouldFailReservedPrefixRule() { + assertThatThrownBy(() -> new PackageName("java.util")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.reserved.prefix"); + }); + + assertThatThrownBy(() -> new PackageName("javax.mail")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.reserved.prefix"); + }); + + assertThatThrownBy(() -> new PackageName("com.sun.tools")) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.package-name.reserved.prefix"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetTest.java new file mode 100644 index 0000000..fb0399f --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/platform/PlatformTargetTest.java @@ -0,0 +1,62 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.platform; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: PlatformTarget") +class PlatformTargetTest { + + @Test + @DisplayName("valid java and springBoot should be accepted") + void validTarget_shouldBeAccepted() { + PlatformTarget target = new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_4_10); + + assertThat(target.java()).isEqualTo(JavaVersion.JAVA_21); + assertThat(target.springBoot()).isEqualTo(SpringBootVersion.V3_4_10); + assertThat(target.java().asString()).isEqualTo("21"); + assertThat(target.springBoot().value()).isEqualTo("3.4.10"); + } + + @Test + @DisplayName("null java should fail TARGET_REQUIRED") + void nullJava_shouldFailTargetRequired() { + assertThatThrownBy(() -> new PlatformTarget(null, SpringBootVersion.V3_4_10)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("platform.target.not.blank"); + }); + } + + @Test + @DisplayName("null springBoot should fail TARGET_REQUIRED") + void nullSpringBoot_shouldFailTargetRequired() { + assertThatThrownBy(() -> new PlatformTarget(JavaVersion.JAVA_21, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("platform.target.not.blank"); + }); + } + + @Test + @DisplayName("null java and springBoot should fail TARGET_REQUIRED") + void nullJavaAndSpringBoot_shouldFailTargetRequired() { + assertThatThrownBy(() -> new PlatformTarget(null, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("platform.target.not.blank"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptionsTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptionsTest.java new file mode 100644 index 0000000..c6f43ad --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/tech/stack/BuildOptionsTest.java @@ -0,0 +1,61 @@ +package io.github.bsayli.codegen.initializr.domain.model.value.tech.stack; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: BuildOptions") +class BuildOptionsTest { + + @Test + @DisplayName("valid framework, buildTool and language should be accepted") + void validOptions_shouldBeAccepted() { + BuildOptions options = new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + assertThat(options.framework()).isEqualTo(Framework.SPRING_BOOT); + assertThat(options.buildTool()).isEqualTo(BuildTool.MAVEN); + assertThat(options.language()).isEqualTo(Language.JAVA); + } + + @Test + @DisplayName("null framework should fail BUILD_OPTIONS_REQUIRED") + void nullFramework_shouldFailBuildOptionsRequired() { + assertThatThrownBy(() -> new BuildOptions(null, BuildTool.MAVEN, Language.JAVA)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.build-options.not.blank"); + }); + } + + @Test + @DisplayName("null buildTool should fail BUILD_OPTIONS_REQUIRED") + void nullBuildTool_shouldFailBuildOptionsRequired() { + assertThatThrownBy(() -> new BuildOptions(Framework.SPRING_BOOT, null, Language.JAVA)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.build-options.not.blank"); + }); + } + + @Test + @DisplayName("null language should fail BUILD_OPTIONS_REQUIRED") + void nullLanguage_shouldFailBuildOptionsRequired() { + assertThatThrownBy(() -> new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, null)) + .isInstanceOf(DomainViolationException.class) + .satisfies( + ex -> { + DomainViolationException dve = (DomainViolationException) ex; + assertThat(dve.getMessageKey()).isEqualTo("project.build-options.not.blank"); + }); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicyTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicyTest.java new file mode 100644 index 0000000..e75b4a8 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/CompatibilityPolicyTest.java @@ -0,0 +1,119 @@ +package io.github.bsayli.codegen.initializr.domain.policy.tech; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: CompatibilityPolicy") +class CompatibilityPolicyTest { + + private static BuildOptions buildOptions() { + return new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + } + + private static PlatformTarget target(JavaVersion java, SpringBootVersion boot) { + return new PlatformTarget(java, boot); + } + + @Test + @DisplayName("ensureCompatible should fail when options or target is null") + @SuppressWarnings("DataFlowIssue") + void ensureCompatible_nullOptionsOrTarget_shouldFailTargetMissing() { + BuildOptions options = buildOptions(); + PlatformTarget target = target(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + + assertThatThrownBy(() -> CompatibilityPolicy.ensureCompatible(null, target)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.missing")); + + assertThatThrownBy(() -> CompatibilityPolicy.ensureCompatible(options, null)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.missing")); + + assertThatThrownBy(() -> CompatibilityPolicy.ensureCompatible(null, null)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.missing")); + } + + @Test + @DisplayName("ensureCompatible should accept all supported Spring Boot / Java combinations") + void ensureCompatible_supportedTargets_shouldPass() { + BuildOptions options = buildOptions(); + + assertThatCode( + () -> + CompatibilityPolicy.ensureCompatible( + options, target(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6))) + .doesNotThrowAnyException(); + + assertThatCode( + () -> + CompatibilityPolicy.ensureCompatible( + options, target(JavaVersion.JAVA_25, SpringBootVersion.V3_5_6))) + .doesNotThrowAnyException(); + + assertThatCode( + () -> + CompatibilityPolicy.ensureCompatible( + options, target(JavaVersion.JAVA_21, SpringBootVersion.V3_4_10))) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("ensureCompatible should fail for incompatible Spring Boot / Java combinations") + void ensureCompatible_incompatibleTarget_shouldFail() { + BuildOptions options = buildOptions(); + PlatformTarget incompatible = target(JavaVersion.JAVA_25, SpringBootVersion.V3_4_10); + + assertThatThrownBy(() -> CompatibilityPolicy.ensureCompatible(options, incompatible)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> { + assertThat(dve.getMessageKey()).isEqualTo("platform.target.incompatible"); + assertThat(dve.getArgs()) + .containsExactly( + incompatible.springBoot().value(), incompatible.java().asString()); + }); + } + + @Test + @DisplayName("allowedJavaFor should return supported Java versions for each Spring Boot version") + void allowedJavaFor_shouldReturnSupportedJavaVersions() { + Set for3510 = CompatibilityPolicy.allowedJavaFor(SpringBootVersion.V3_4_10); + Set for356 = CompatibilityPolicy.allowedJavaFor(SpringBootVersion.V3_5_6); + + assertThat(for3510).containsExactlyInAnyOrder(JavaVersion.JAVA_21); + assertThat(for356).containsExactlyInAnyOrder(JavaVersion.JAVA_21, JavaVersion.JAVA_25); + } + + @Test + @DisplayName("allSupportedTargets should return all combinations defined in the matrix") + void allSupportedTargets_shouldReturnAllMatrixCombinations() { + List targets = CompatibilityPolicy.allSupportedTargets(); + + assertThat(targets) + .containsExactlyInAnyOrder( + target(JavaVersion.JAVA_21, SpringBootVersion.V3_4_10), + target(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6), + target(JavaVersion.JAVA_25, SpringBootVersion.V3_5_6)); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java new file mode 100644 index 0000000..f3ee2f3 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java @@ -0,0 +1,65 @@ +package io.github.bsayli.codegen.initializr.domain.policy.tech; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: PlatformTargetSelector") +class PlatformTargetSelectorTest { + + private static BuildOptions buildOptions() { + return new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + } + + @Test + @DisplayName("select with compatible target should return PlatformTarget") + void select_compatibleTarget_shouldReturnRequestedTarget() { + BuildOptions options = buildOptions(); + + PlatformTarget result = + PlatformTargetSelector.select(options, JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + + assertThat(result.java()).isEqualTo(JavaVersion.JAVA_21); + assertThat(result.springBoot()).isEqualTo(SpringBootVersion.V3_5_6); + } + + @Test + @DisplayName("select with incompatible target should delegate to CompatibilityPolicy and fail") + void select_incompatibleTarget_shouldFailCompatibility() { + BuildOptions options = buildOptions(); + + assertThatThrownBy( + () -> + PlatformTargetSelector.select( + options, JavaVersion.JAVA_25, SpringBootVersion.V3_4_10)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("platform.target.incompatible")); + } + + @Test + @DisplayName("supportedTargetsFor should return all supported targets from CompatibilityPolicy") + void supportedTargetsFor_shouldReturnAllSupportedTargets() { + BuildOptions options = buildOptions(); + + List targets = PlatformTargetSelector.supportedTargetsFor(options); + + assertThat(targets) + .isNotEmpty() + .contains(new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6)); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFileTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFileTest.java new file mode 100644 index 0000000..6836842 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFileTest.java @@ -0,0 +1,90 @@ +package io.github.bsayli.codegen.initializr.domain.port.out.artifact; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.domain.error.exception.DomainViolationException; +import java.nio.file.Path; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("domain") +@DisplayName("Unit Test: GeneratedFile") +class GeneratedFileTest { + + @Test + @DisplayName("Text with valid args should be created successfully") + void text_validArgs_shouldCreateInstance() { + GeneratedFile.Text text = new GeneratedFile.Text(Path.of("pom.xml"), "", UTF_8); + + assertThat(text.relativePath()).isEqualTo(Path.of("pom.xml")); + assertThat(text.content()).isEqualTo(""); + assertThat(text.charset()).isEqualTo(UTF_8); + } + + @Test + @DisplayName("Text with null path should fail with file.path.not.blank") + void text_nullPath_shouldFailPathNotBlank() { + assertThatThrownBy(() -> new GeneratedFile.Text(null, "x", UTF_8)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("file.path.not.blank")); + } + + @Test + @DisplayName("Text with null content should fail with file.content.not.blank") + void text_nullContent_shouldFailContentNotBlank() { + assertThatThrownBy(() -> new GeneratedFile.Text(Path.of("pom.xml"), null, UTF_8)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("file.content.not.blank")); + } + + @Test + @DisplayName("Binary should defensively copy bytes in ctor and accessor") + void binary_shouldDefensivelyCopyBytes() { + byte[] original = new byte[] {1, 2, 3}; + GeneratedFile.Binary binary = new GeneratedFile.Binary(Path.of("bin.dat"), original); + + original[0] = 9; + + byte[] fromGetter = binary.bytes(); + assertThat(fromGetter).containsExactly(1, 2, 3); + + fromGetter[1] = 8; + + byte[] fromGetterAgain = binary.bytes(); + assertThat(fromGetterAgain).containsExactly(1, 2, 3); + } + + @Test + @DisplayName("Binary with null path should fail with file.path.not.blank") + void binary_nullPath_shouldFailPathNotBlank() { + assertThatThrownBy(() -> new GeneratedFile.Binary(null, new byte[] {1})) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("file.path.not.blank")); + } + + @Test + @DisplayName("Binary with null bytes should fail with file.content.not.blank") + void binary_nullBytes_shouldFailContentNotBlank() { + assertThatThrownBy(() -> new GeneratedFile.Binary(Path.of("bin.dat"), null)) + .isInstanceOfSatisfying( + DomainViolationException.class, + dve -> assertThat(dve.getMessageKey()).isEqualTo("file.content.not.blank")); + } + + @Test + @DisplayName("Binary equals/hashCode should depend on path and bytes") + void binary_equalsAndHashCode_shouldDependOnPathAndBytes() { + GeneratedFile.Binary b1 = new GeneratedFile.Binary(Path.of("bin.dat"), new byte[] {1, 2, 3}); + GeneratedFile.Binary b2 = new GeneratedFile.Binary(Path.of("bin.dat"), new byte[] {1, 2, 3}); + GeneratedFile.Binary b3 = new GeneratedFile.Binary(Path.of("other.bin"), new byte[] {1, 2, 3}); + + assertThat(b1).isEqualTo(b2).hasSameHashCodeAs(b2).isNotEqualTo(b3); + } +} From 14c030489af300b14391c97e93475fba0242bc7b Mon Sep 17 00:00:00 2001 From: bsayli Date: Fri, 21 Nov 2025 18:54:13 +0300 Subject: [PATCH 22/74] test(adapter): add complete unit test suite for adapter layer and introduce reusable test support utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added comprehensive unit tests for all adapter components: • ProfileBasedArtifactsSelector • SpringBootMavenJavaArtifactsAdapter • MavenPomAdapter / ReadmeAdapter / ApplicationYamlAdapter • Java class scaffolder adapters (source/test) • GitIgnoreAdapter / MavenWrapperAdapter • FileSystemProjectArchiverAdapter • FileSystemProjectRootAdapter • FileSystemProjectWriterAdapter - Introduced reusable test support utilities: • CapturingTemplateRenderer • NoopTemplateRenderer • Fake/RecordingPomDependencyMapper - Performed refactors for consistency across adapter tests: • Unified model assertions with containsEntry(...) • Ensured proper casting for Iterable • Fixed cross-platform ZIP entry normalization • Improved filesystem error simulation for IO exception scenarios This commit significantly improves adapter layer test coverage, clarity, and reliability. --- .../out/build/maven/shared/PomDependency.java | 40 ++--- .../naming/ProjectDescriptionPolicy.java | 6 +- .../ProfileBasedArtifactsSelectorTest.java | 63 ++++++++ ...ringBootMavenJavaArtifactsAdapterTest.java | 53 +++++++ ...ractSingleTemplateArtifactAdapterTest.java | 70 +++++++++ .../maven/shared/PomDependencyMapperTest.java | 88 +++++++++++ .../FileSystemProjectArchiverAdapterTest.java | 108 +++++++++++++ .../FileSystemProjectRootAdapterTest.java | 87 +++++++++++ .../FileSystemProjectWriterAdapterTest.java | 82 ++++++++++ .../maven/java/build/MavenPomAdapterTest.java | 143 ++++++++++++++++++ .../config/ApplicationYamlAdapterTest.java | 73 +++++++++ .../maven/java/docs/ReadmeAdapterTest.java | 138 +++++++++++++++++ ...bstractJavaClassScaffolderAdapterTest.java | 83 ++++++++++ .../source/SourceScaffolderAdapterTest.java | 88 +++++++++++ .../java/test/TestScaffolderAdapterTest.java | 89 +++++++++++ .../maven/java/vcs/GitIgnoreAdapterTest.java | 63 ++++++++ .../java/wrapper/MavenWrapperAdapterTest.java | 70 +++++++++ .../naming/StringCaseFormatterTest.java | 100 ++++++++++++ .../value/naming/ProjectDescriptionTest.java | 4 +- .../build/RecordingPomDependencyMapper.java | 22 +++ .../templating/CapturingTemplateRenderer.java | 22 +++ .../templating/NoopTemplateRenderer.java | 15 ++ 22 files changed, 1472 insertions(+), 35 deletions(-) create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapperTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatterTest.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/testsupport/build/RecordingPomDependencyMapper.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/CapturingTemplateRenderer.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/NoopTemplateRenderer.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java index 6d2f8ed..b885926 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java @@ -1,39 +1,17 @@ package io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared; -public final class PomDependency { - private final String groupId; - private final String artifactId; - private final String version; // nullable - private final String scope; // nullable - - private PomDependency(String groupId, String artifactId, String version, String scope) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.scope = scope; - } - +public record PomDependency( + String groupId, + String artifactId, + String version, + String scope +) { public static PomDependency of(String groupId, String artifactId) { return new PomDependency(groupId, artifactId, null, null); } - public static PomDependency of(String groupId, String artifactId, String version, String scope) { + public static PomDependency of( + String groupId, String artifactId, String version, String scope) { return new PomDependency(groupId, artifactId, version, scope); } - - public String getGroupId() { - return groupId; - } - - public String getArtifactId() { - return artifactId; - } - - public String getVersion() { - return version; - } - - public String getScope() { - return scope; - } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java index f830a8b..58d677b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java @@ -26,8 +26,10 @@ public static String enforce(String raw) { } private static String normalize(String raw) { - if (raw == null) return ""; - return raw.trim().replaceAll("\\s+", " ").toLowerCase(Locale.ROOT); + if (raw == null) { + return ""; + } + return raw.trim().replaceAll("\\s+", " "); } private static void validate(String value) { diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java new file mode 100644 index 0000000..f0cb722 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java @@ -0,0 +1,63 @@ +package io.github.bsayli.codegen.initializr.adapter.out; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactsPortNotFoundException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.UnsupportedProfileTypeException; +import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@DisplayName("Unit Test: ProfileBasedArtifactsSelector") +class ProfileBasedArtifactsSelectorTest { + + @Test + @DisplayName("Should throw UnsupportedProfileTypeException when ProfileType.from() returns null") + void shouldThrowWhenProfileUnsupported() { + BuildOptions options = mock(BuildOptions.class); + + assertThatThrownBy(() -> new ProfileBasedArtifactsSelector(Map.of()).select(options)) + .isInstanceOf(UnsupportedProfileTypeException.class); + } + + @Test + @DisplayName("Should throw ArtifactsPortNotFoundException when no port registered for type") + void shouldThrowWhenPortMissing() { + BuildOptions opts = new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + ProfileType type = ProfileType.from(opts); + assertThat(type).isNotNull(); + + ProfileBasedArtifactsSelector selector = + new ProfileBasedArtifactsSelector(Map.of()); // empty registry + + assertThatThrownBy(() -> selector.select(opts)) + .isInstanceOf(ArtifactsPortNotFoundException.class); + } + + @Test + @DisplayName("Should return registered ProjectArtifactsPort for matching profile") + void shouldReturnMatchingPort() { + BuildOptions opts = new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + ProfileType type = ProfileType.from(opts); + + ProjectArtifactsPort port = mock(ProjectArtifactsPort.class); + + ProfileBasedArtifactsSelector selector = new ProfileBasedArtifactsSelector(Map.of(type, port)); + + ProjectArtifactsPort result = selector.select(opts); + + assertThat(result).isSameAs(port); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java new file mode 100644 index 0000000..4a58067 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java @@ -0,0 +1,53 @@ +package io.github.bsayli.codegen.initializr.adapter.out; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.util.Collections; +import java.util.List; +import java.util.stream.StreamSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@DisplayName("Unit Test: SpringBootMavenJavaArtifactsAdapter") +class SpringBootMavenJavaArtifactsAdapterTest { + + @Test + @DisplayName("Should return empty list when no artifact ports are configured") + void shouldReturnEmptyWhenNoPorts() { + SpringBootMavenJavaArtifactsAdapter adapter = + new SpringBootMavenJavaArtifactsAdapter(List.of()); + + ProjectBlueprint blueprint = new ProjectBlueprint(null, null, null, null, null, null, null); + + List result = + StreamSupport.stream(adapter.generate(blueprint).spliterator(), false).toList(); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("Should delegate generate() exactly once to each ArtifactPort") + void shouldDelegateToEachArtifactPort() { + ProjectBlueprint blueprint = new ProjectBlueprint(null, null, null, null, null, null, null); + + ArtifactPort p1 = mock(ArtifactPort.class); + ArtifactPort p2 = mock(ArtifactPort.class); + + when(p1.generate(blueprint)).thenReturn(Collections.emptyList()); + when(p2.generate(blueprint)).thenReturn(Collections.emptyList()); + + SpringBootMavenJavaArtifactsAdapter adapter = + new SpringBootMavenJavaArtifactsAdapter(List.of(p1, p2)); + + adapter.generate(blueprint); + + verify(p1, times(1)).generate(blueprint); + verify(p2, times(1)).generate(blueprint); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java new file mode 100644 index 0000000..0050fce --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java @@ -0,0 +1,70 @@ +package io.github.bsayli.codegen.initializr.adapter.out.artifact; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: AbstractSingleTemplateArtifactAdapter") +class AbstractSingleTemplateArtifactAdapterTest { + + @Test + @DisplayName("generate() should use first template, render with model, and return single file") + void generate_shouldRenderSingleTemplateAndReturnFile() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = + new TemplateDefinition("test-template.ftl", "output/test.txt"); + + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + TestSingleTemplateAdapter adapter = new TestSingleTemplateAdapter(renderer, artifactDefinition); + + ProjectBlueprint blueprint = new ProjectBlueprint(null, null, null, null, null, null, null); + + Path relativePath = Path.of("output/test.txt"); + GeneratedFile.Text expectedFile = + new GeneratedFile.Text(relativePath, "rendered-content", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("test-template.ftl"); + assertThat(renderer.capturedModel).isEqualTo(Map.of("key", "value")); + + assertThat(result).singleElement().isSameAs(expectedFile); + } + + private static final class TestSingleTemplateAdapter + extends AbstractSingleTemplateArtifactAdapter { + + TestSingleTemplateAdapter(TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + super(renderer, artifactDefinition); + } + + @Override + protected Map buildModel(ProjectBlueprint blueprint) { + return Map.of("key", "value"); + } + + @Override + public ArtifactKey artifactKey() { + return null; + } + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapperTest.java new file mode 100644 index 0000000..636517f --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependencyMapperTest.java @@ -0,0 +1,88 @@ +package io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyScope; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: PomDependencyMapper") +class PomDependencyMapperTest { + + private final PomDependencyMapper mapper = new PomDependencyMapper(); + + private static Dependency dep( + String groupId, String artifactId, String version, DependencyScope scope) { + DependencyVersion v = (version == null) ? null : new DependencyVersion(version); + return new Dependency( + new DependencyCoordinates(new GroupId(groupId), new ArtifactId(artifactId)), v, scope); + } + + @Test + @DisplayName("from(empty Dependencies) should return empty list") + void from_emptyDependencies_shouldReturnEmptyList() { + Dependencies deps = Dependencies.of(List.of()); + + List result = mapper.from(deps); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("from(Dependencies) should map coordinates, optional version and scope correctly") + void from_dependencies_shouldMapFieldsCorrectly() { + Dependency d1 = dep("org.acme", "alpha", null, null); + Dependency d2 = dep("org.acme", "beta", "1.2.3", DependencyScope.RUNTIME); + Dependency d3 = dep("org.acme", "gamma", "2.0.0-RC1", DependencyScope.TEST); + + Dependencies deps = Dependencies.of(List.of(d1, d2, d3)); + + List result = mapper.from(deps); + + assertThat(result).hasSize(3); + + Map byArtifactId = + result.stream().collect(Collectors.toMap(PomDependency::artifactId, p -> p)); + + PomDependency alpha = byArtifactId.get("alpha"); + assertThat(alpha.groupId()).isEqualTo("org.acme"); + assertThat(alpha.version()).isNull(); + assertThat(alpha.scope()).isNull(); + + PomDependency beta = byArtifactId.get("beta"); + assertThat(beta.groupId()).isEqualTo("org.acme"); + assertThat(beta.version()).isEqualTo("1.2.3"); + assertThat(beta.scope()).isEqualTo("runtime"); + + PomDependency gamma = byArtifactId.get("gamma"); + assertThat(gamma.groupId()).isEqualTo("org.acme"); + assertThat(gamma.version()).isEqualTo("2.0.0-RC1"); + assertThat(gamma.scope()).isEqualTo("test"); + } + + @Test + @DisplayName("from(Dependency) should map single dependency to PomDependency") + void from_singleDependency_shouldMapToPomDependency() { + Dependency domainDep = + dep("com.example", "demo-lib", "0.9.0-SNAPSHOT", DependencyScope.PROVIDED); + + PomDependency pomDep = mapper.from(domainDep); + + assertThat(pomDep.groupId()).isEqualTo("com.example"); + assertThat(pomDep.artifactId()).isEqualTo("demo-lib"); + assertThat(pomDep.version()).isEqualTo("0.9.0-SNAPSHOT"); + assertThat(pomDep.scope()).isEqualTo("provided"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapterTest.java new file mode 100644 index 0000000..a15b591 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapterTest.java @@ -0,0 +1,108 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectArchiveInvalidRootException; +import io.github.bsayli.codegen.initializr.application.port.out.archive.ProjectArchiverPort; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: FileSystemProjectArchiverAdapter") +class FileSystemProjectArchiverAdapterTest { + + private final ProjectArchiverPort archiver = new FileSystemProjectArchiverAdapter(); + + private static List zipEntries(ZipFile zipFile) { + Enumeration entries = zipFile.entries(); + List names = new java.util.ArrayList<>(); + while (entries.hasMoreElements()) { + names.add(entries.nextElement().getName()); + } + return names; + } + + @Test + @DisplayName("archive() should create zip with artifactId as root directory and include files") + void archive_shouldCreateZipWithArtifactIdRoot(@TempDir Path tempDir) throws IOException { + Path projectRoot = Files.createDirectory(tempDir.resolve("demo-app")); + + Path mainJava = projectRoot.resolve("src/main/java"); + Files.createDirectories(mainJava); + Files.writeString(mainJava.resolve("App.java"), "class App {}", StandardCharsets.UTF_8); + + Path testJava = projectRoot.resolve("src/test/java"); + Files.createDirectories(testJava); + Files.writeString(testJava.resolve("AppTest.java"), "class AppTest {}", StandardCharsets.UTF_8); + + Path archivePath = archiver.archive(projectRoot, "my-artifact"); + + assertThat(archivePath).isEqualTo(tempDir.resolve("my-artifact.zip")); + assertThat(Files.exists(archivePath)).isTrue(); + + try (ZipFile zipFile = new ZipFile(archivePath.toFile())) { + List entryNames = zipEntries(zipFile); + assertThat(entryNames) + .contains("my-artifact/") + .anySatisfy(name -> assertThat(name).startsWith("my-artifact/src/")) + .contains("my-artifact/src/main/java/App.java") + .contains("my-artifact/src/test/java/AppTest.java"); + } + } + + @Test + @DisplayName("archive() should fall back to directory name when artifactId is null") + void archive_shouldUseDirectoryNameWhenArtifactIdNull(@TempDir Path tempDir) throws IOException { + Path projectRoot = Files.createDirectory(tempDir.resolve("demo-app")); + + Files.writeString(projectRoot.resolve("README.md"), "# Demo", StandardCharsets.UTF_8); + + Path archivePath = archiver.archive(projectRoot, null); + + assertThat(archivePath).isEqualTo(tempDir.resolve("demo-app.zip")); + assertThat(Files.exists(archivePath)).isTrue(); + + try (ZipFile zipFile = new ZipFile(archivePath.toFile())) { + List entryNames = zipEntries(zipFile); + assertThat(entryNames).contains("demo-app/").contains("demo-app/README.md"); + } + } + + @Test + @DisplayName("archive() should throw ProjectArchiveInvalidRootException when root is null") + void archive_shouldThrowWhenRootIsNull() { + assertThatThrownBy(() -> archiver.archive(null, "anything")) + .isInstanceOf(ProjectArchiveInvalidRootException.class); + } + + @Test + @DisplayName("archive() should throw ProjectArchiveInvalidRootException when root has no parent") + void archive_shouldThrowWhenRootHasNoParent(@TempDir Path tempDir) { + Path rootWithoutParent = tempDir.getRoot(); + + assertThatThrownBy(() -> archiver.archive(rootWithoutParent, "artifact")) + .isInstanceOf(ProjectArchiveInvalidRootException.class); + } + + @Test + @DisplayName( + "archive() should throw ProjectArchiveInvalidRootException when path is not a directory") + void archive_shouldThrowWhenNotDirectory(@TempDir Path tempDir) throws IOException { + Path fileAsRoot = Files.createFile(tempDir.resolve("not-a-directory.txt")); + + assertThatThrownBy(() -> archiver.archive(fileAsRoot, "artifact")) + .isInstanceOf(ProjectArchiveInvalidRootException.class); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapterTest.java new file mode 100644 index 0000000..802b6df --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectRootAdapterTest.java @@ -0,0 +1,87 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootAlreadyExistsException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootIOException; +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectRootNotDirectoryException; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: FileSystemProjectRootAdapter") +class FileSystemProjectRootAdapterTest { + + private final FileSystemProjectRootAdapter adapter = new FileSystemProjectRootAdapter(); + @TempDir Path tempDir; + + @Test + @DisplayName("Should create directory when project root does not exist") + void shouldCreateDirectoryWhenNotExists() { + Path result = + adapter.prepareRoot(tempDir, "demo-app", ProjectRootExistencePolicy.FAIL_IF_EXISTS); + + assertThat(result).exists().isDirectory(); + assertThat(result.getFileName().toString()).isEqualTo("demo-app"); + } + + @Test + @DisplayName( + "Should throw ProjectRootAlreadyExistsException when directory exists and policy=FAIL_IF_EXISTS") + void shouldFailIfExists() throws IOException { + Path existing = tempDir.resolve("demo-app"); + Files.createDirectories(existing); + + assertThatThrownBy( + () -> + adapter.prepareRoot(tempDir, "demo-app", ProjectRootExistencePolicy.FAIL_IF_EXISTS)) + .isInstanceOf(ProjectRootAlreadyExistsException.class); + } + + @Test + @DisplayName("Should return directory when exists and policy=OVERWRITE") + void shouldReturnExistingDirWhenOverwrite() throws IOException { + Path existing = tempDir.resolve("demo-app"); + Files.createDirectories(existing); + + Path result = adapter.prepareRoot(tempDir, "demo-app", ProjectRootExistencePolicy.OVERWRITE); + + assertThat(result).isEqualTo(existing); + } + + @Test + @DisplayName("Should throw ProjectRootNotDirectoryException when exists and is a file") + void shouldThrowIfExistsButNotDirectory() throws IOException { + Path file = tempDir.resolve("demo-app"); + Files.writeString(file, "not a directory"); + + assertThatThrownBy( + () -> + adapter.prepareRoot(tempDir, "demo-app", ProjectRootExistencePolicy.FAIL_IF_EXISTS)) + .isInstanceOf(ProjectRootNotDirectoryException.class); + } + + @Test + @DisplayName("Should wrap IO errors in ProjectRootIOException") + void shouldWrapIOException() throws IOException { + Path targetDir = Files.createTempDirectory("locked"); + File dir = targetDir.toFile(); + + assertThat(dir.setReadable(true)).isTrue(); + assertThat(dir.setWritable(false)).isTrue(); + assertThat(dir.setExecutable(true)).isTrue(); + + assertThatThrownBy( + () -> adapter.prepareRoot(targetDir, "demo-app", ProjectRootExistencePolicy.OVERWRITE)) + .isInstanceOf(ProjectRootIOException.class); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapterTest.java new file mode 100644 index 0000000..efa94fa --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectWriterAdapterTest.java @@ -0,0 +1,82 @@ +package io.github.bsayli.codegen.initializr.adapter.out.filesystem; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.adapter.error.exception.ProjectWriteException; +import io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectWriterPort; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: FileSystemProjectWriterAdapter") +class FileSystemProjectWriterAdapterTest { + + private final ProjectWriterPort writer = new FileSystemProjectWriterAdapter(); + + @Test + @DisplayName("writeBytes() should create parent directories and write file") + void writeBytes_shouldCreateDirsAndWrite() throws IOException { + Path temp = Files.createTempDirectory("writer-test"); + Path relative = Path.of("a/b/c.txt"); + + byte[] content = "hello-bytes".getBytes(StandardCharsets.UTF_8); + + writer.writeBytes(temp, relative, content); + + Path target = temp.resolve(relative); + assertThat(Files.exists(target)).isTrue(); + assertThat(Files.readString(target)).isEqualTo("hello-bytes"); + } + + @Test + @DisplayName("writeBytes() should overwrite existing file") + void writeBytes_shouldOverwriteExistingFile() throws IOException { + Path temp = Files.createTempDirectory("writer-test2"); + Path relative = Path.of("file.txt"); + + Files.writeString(temp.resolve(relative), "old"); + + writer.writeBytes(temp, relative, "new".getBytes(StandardCharsets.UTF_8)); + + assertThat(Files.readString(temp.resolve(relative))).isEqualTo("new"); + } + + @Test + @DisplayName("writeText() should write file with provided charset") + void writeText_shouldWriteWithCharset() throws IOException { + Path temp = Files.createTempDirectory("writer-test3"); + Path relative = Path.of("utf16.txt"); + + writer.writeText(temp, relative, "Merhaba Dünya", StandardCharsets.UTF_16); + + assertThat(Files.readString(temp.resolve(relative), StandardCharsets.UTF_16)) + .isEqualTo("Merhaba Dünya"); + } + + @Test + @DisplayName("writeBytes() should wrap IOExceptions in ProjectWriteException") + void writeBytes_shouldWrapIOException() throws IOException { + Path temp = Files.createTempDirectory("writer-test4"); + + // Make directory read-only to force IOException + File root = temp.toFile(); + assertThat(root.setWritable(false)).isTrue(); + + Path relative = Path.of("fail/here.txt"); + byte[] content = "boom".getBytes(StandardCharsets.UTF_8); + + assertThatThrownBy(() -> writer.writeBytes(temp, relative, content)) + .isInstanceOf(ProjectWriteException.class); + + // Restore permission for cleanup + assertThat(root.setWritable(true)).isTrue(); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java new file mode 100644 index 0000000..cd54f80 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java @@ -0,0 +1,143 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyScope; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.build.RecordingPomDependencyMapper; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: MavenPomAdapter") +class MavenPomAdapterTest { + + private static ProjectBlueprint blueprintWithDependencies() { + ProjectIdentity identity = + new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); + + ProjectName name = new ProjectName("Demo App"); + ProjectDescription description = new ProjectDescription("Sample Project"); + PackageName pkg = new PackageName("com.acme.demo"); + + BuildOptions buildOptions = + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + PlatformTarget target = new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + + Dependency dep = + new Dependency( + new DependencyCoordinates(new GroupId("org.acme"), new ArtifactId("custom-dep")), + new DependencyVersion("1.0.0"), + DependencyScope.RUNTIME); + + Dependencies dependencies = Dependencies.of(List.of(dep)); + + return new ProjectBlueprint( + identity, name, description, pkg, buildOptions, target, dependencies); + } + + @Test + @DisplayName("artifactKey() should return POM") + void artifactKey_shouldReturnPom() { + MavenPomAdapter adapter = + new MavenPomAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition(List.of(new TemplateDefinition("pom.ftl", "pom.xml"))), + new RecordingPomDependencyMapper(List.of())); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.POM); + } + + @Test + @DisplayName("buildModel via generate() should populate all POM fields and dependencies") + void generate_shouldBuildCorrectModelForPom() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + RecordingPomDependencyMapper mapper = + new RecordingPomDependencyMapper( + List.of(PomDependency.of("org.acme", "custom-dep", "1.0.0", "runtime"))); + + TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + MavenPomAdapter adapter = new MavenPomAdapter(renderer, artifactDefinition, mapper); + + ProjectBlueprint blueprint = blueprintWithDependencies(); + + Path relativePath = Path.of("pom.xml"); + GeneratedFile.Text dummyFile = + new GeneratedFile.Text(relativePath, "", StandardCharsets.UTF_8); + renderer.nextFile = dummyFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(dummyFile); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("pom.ftl"); + assertThat(renderer.capturedModel).isNotNull(); + + Map model = renderer.capturedModel; + + assertThat(model) + .containsEntry("groupId", "com.acme") + .containsEntry("artifactId", "demo-app") + .containsEntry("javaVersion", "21") + .containsEntry("springBootVersion", "3.5.6") + .containsEntry("projectName", "demo-app") + .containsEntry("projectDescription", "Sample Project"); + + assertThat(mapper.capturedDependencies).isSameAs(blueprint.getDependencies()); + + @SuppressWarnings("unchecked") + List deps = (List) model.get("dependencies"); + assertThat(deps).hasSize(3); + + PomDependency core = deps.getFirst(); + assertThat(core.groupId()).isEqualTo("org.springframework.boot"); + assertThat(core.artifactId()).isEqualTo("spring-boot-starter"); + assertThat(core.version()).isNull(); + assertThat(core.scope()).isNull(); + + PomDependency mapped = deps.get(1); + assertThat(mapped.groupId()).isEqualTo("org.acme"); + assertThat(mapped.artifactId()).isEqualTo("custom-dep"); + assertThat(mapped.version()).isEqualTo("1.0.0"); + assertThat(mapped.scope()).isEqualTo("runtime"); + + PomDependency testStarter = deps.get(2); + assertThat(testStarter.groupId()).isEqualTo("org.springframework.boot"); + assertThat(testStarter.artifactId()).isEqualTo("spring-boot-starter-test"); + assertThat(testStarter.scope()).isEqualTo("test"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java new file mode 100644 index 0000000..213b2d9 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java @@ -0,0 +1,73 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: ApplicationYamlAdapter") +class ApplicationYamlAdapterTest { + + @Test + @DisplayName("artifactKey() should return APPLICATION_YAML") + void artifactKey_shouldReturnApplicationYaml() { + ApplicationYamlAdapter adapter = + new ApplicationYamlAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition( + List.of(new TemplateDefinition("application-yaml.ftl", "application.yml")))); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.APPLICATION_YAML); + } + + @Test + @DisplayName("generate() should build model with normalized projectName and render single file") + void generate_shouldBuildModelAndRenderFile() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = + new TemplateDefinition("application-yaml.ftl", "src/main/resources/application.yml"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + ApplicationYamlAdapter adapter = new ApplicationYamlAdapter(renderer, artifactDefinition); + + ProjectBlueprint blueprint = + new ProjectBlueprint( + null, + new ProjectName("Demo App"), + new ProjectDescription("Sample Project"), + null, + null, + null, + null); + + Path relativePath = Path.of("src/main/resources/application.yml"); + GeneratedFile.Text expectedFile = + new GeneratedFile.Text( + relativePath, "spring.application.name=demo-app", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("application-yaml.ftl"); + assertThat(renderer.capturedModel).isNotNull().containsEntry("projectName", "demo-app"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java new file mode 100644 index 0000000..9a36481 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java @@ -0,0 +1,138 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyScope; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.build.RecordingPomDependencyMapper; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: ReadmeAdapter") +class ReadmeAdapterTest { + + private static ProjectBlueprint blueprintWithDependencies() { + ProjectIdentity identity = + new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); + + ProjectName name = new ProjectName("Demo App"); + ProjectDescription description = new ProjectDescription("Sample Project"); + PackageName pkg = new PackageName("com.acme.demo"); + + BuildOptions buildOptions = + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + PlatformTarget target = new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + + Dependency dep = + new Dependency( + new DependencyCoordinates(new GroupId("org.acme"), new ArtifactId("custom-dep")), + new DependencyVersion("1.0.0"), + DependencyScope.RUNTIME); + + Dependencies dependencies = Dependencies.of(List.of(dep)); + + return new ProjectBlueprint( + identity, name, description, pkg, buildOptions, target, dependencies); + } + + @Test + @DisplayName("artifactKey() should return README") + void artifactKey_shouldReturnReadme() { + ReadmeAdapter adapter = + new ReadmeAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition(List.of(new TemplateDefinition("README.ftl", "README.md"))), + new PomDependencyMapper()); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.README); + } + + @Test + @DisplayName("generate() should build correct README model and delegate dependencies mapping") + void generate_shouldBuildCorrectModelForReadme() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + List mappedDeps = + List.of(PomDependency.of("org.acme", "custom-dep", "1.0.0", "runtime")); + RecordingPomDependencyMapper mapper = new RecordingPomDependencyMapper(mappedDeps); + + TemplateDefinition templateDefinition = new TemplateDefinition("README.ftl", "README.md"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + ReadmeAdapter adapter = new ReadmeAdapter(renderer, artifactDefinition, mapper); + + ProjectBlueprint blueprint = blueprintWithDependencies(); + + Path relativePath = Path.of("README.md"); + GeneratedFile.Text dummyFile = + new GeneratedFile.Text(relativePath, "# Readme", StandardCharsets.UTF_8); + renderer.nextFile = dummyFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(dummyFile); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("README.ftl"); + assertThat(renderer.capturedModel).isNotNull(); + + Map model = renderer.capturedModel; + + assertThat(model) + .containsEntry("projectName", "demo-app") + .containsEntry("projectDescription", "Sample Project") + .containsEntry("groupId", "com.acme") + .containsEntry("artifactId", "demo-app") + .containsEntry("packageName", "com.acme.demo") + .containsEntry("buildTool", "MAVEN") + .containsEntry("language", "JAVA") + .containsEntry("framework", "SPRING_BOOT") + .containsEntry("javaVersion", "21") + .containsEntry("springBootVersion", "3.5.6"); + + assertThat(mapper.capturedDependencies).isSameAs(blueprint.getDependencies()); + + @SuppressWarnings("unchecked") + List deps = (List) model.get("dependencies"); + assertThat(deps).isSameAs(mappedDeps).hasSize(1); + + PomDependency d = deps.getFirst(); + assertThat(d.groupId()).isEqualTo("org.acme"); + assertThat(d.artifactId()).isEqualTo("custom-dep"); + assertThat(d.version()).isEqualTo("1.0.0"); + assertThat(d.scope()).isEqualTo("runtime"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java new file mode 100644 index 0000000..f2156a2 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java @@ -0,0 +1,83 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: AbstractJavaClassScaffolderAdapter") +class AbstractJavaClassScaffolderAdapterTest { + + @Test + @DisplayName("generate() should build correct path, model and return single file") + void generate_shouldBuildOutPathAndModelAndReturnFile() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = + new TemplateDefinition("java-class.ftl", "src/main/java"); + + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + StringCaseFormatter formatter = new StringCaseFormatter(); + + TestJavaClassScaffolderAdapter adapter = + new TestJavaClassScaffolderAdapter(renderer, artifactDefinition, formatter); + + ProjectBlueprint blueprint = + new ProjectBlueprint(null, null, null, new PackageName("com.acme.demo"), null, null, null); + + Path expectedPath = Path.of("src/main/java/com/acme/demo/DemoApplication.java"); + + GeneratedFile.Text expectedFile = + new GeneratedFile.Text(expectedPath, "class DemoApplication {}", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); + assertThat(renderer.capturedTemplateName).isEqualTo("java-class.ftl"); + + assertThat(renderer.capturedModel) + .isNotNull() + .containsEntry("projectPackageName", "com.acme.demo") + .containsEntry("className", "DemoApplication"); + } + + private static final class TestJavaClassScaffolderAdapter + extends AbstractJavaClassScaffolderAdapter { + + TestJavaClassScaffolderAdapter( + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { + super(renderer, artifactDefinition, stringCaseFormatter); + } + + @Override + protected String buildClassName(ProjectBlueprint blueprint) { + return "DemoApplication"; + } + + @Override + public ArtifactKey artifactKey() { + return null; + } + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java new file mode 100644 index 0000000..116675f --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java @@ -0,0 +1,88 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.source; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: SourceScaffolderAdapter") +class SourceScaffolderAdapterTest { + + private static ProjectBlueprint blueprint() { + ProjectIdentity identity = + new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); + + ProjectName name = new ProjectName("Demo App"); + ProjectDescription description = new ProjectDescription("Sample Project"); + PackageName pkg = new PackageName("com.acme.demo"); + + return new ProjectBlueprint(identity, name, description, pkg, null, null, null); + } + + @Test + @DisplayName("artifactKey() should return SOURCE_SCAFFOLDER") + void artifactKey_shouldReturnSourceScaffolder() { + SourceScaffolderAdapter adapter = + new SourceScaffolderAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition(List.of(new TemplateDefinition("source.ftl", "src/main/java"))), + new StringCaseFormatter()); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.SOURCE_SCAFFOLDER); + } + + @Test + @DisplayName( + "generate() should build class name from artifactId (PascalCase + Application) and render file under package path") + void generate_shouldBuildClassNameFromArtifactIdAndRenderFile() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = new TemplateDefinition("source.ftl", "src/main/java"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + SourceScaffolderAdapter adapter = + new SourceScaffolderAdapter(renderer, artifactDefinition, new StringCaseFormatter()); + + ProjectBlueprint blueprint = blueprint(); + + Path expectedPath = Path.of("src/main/java/com/acme/demo/DemoAppApplication.java"); + + GeneratedFile.Text expectedFile = + new GeneratedFile.Text(expectedPath, "class DemoAppApplication {}", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); + assertThat(renderer.capturedTemplateName).isEqualTo("source.ftl"); + + Map model = renderer.capturedModel; + assertThat(model) + .isNotNull() + .containsEntry("projectPackageName", "com.acme.demo") + .containsEntry("className", "DemoAppApplication"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java new file mode 100644 index 0000000..93d800e --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java @@ -0,0 +1,89 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: TestScaffolderAdapter") +class TestScaffolderAdapterTest { + + private static ProjectBlueprint blueprint() { + ProjectIdentity identity = + new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); + + ProjectName name = new ProjectName("Demo App"); + ProjectDescription description = new ProjectDescription("Sample Project"); + PackageName pkg = new PackageName("com.acme.demo"); + + return new ProjectBlueprint(identity, name, description, pkg, null, null, null); + } + + @Test + @DisplayName("artifactKey() should return TEST_SCAFFOLDER") + void artifactKey_shouldReturnTestScaffolder() { + TestScaffolderAdapter adapter = + new TestScaffolderAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition(List.of(new TemplateDefinition("test.ftl", "src/test/java"))), + new StringCaseFormatter()); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.TEST_SCAFFOLDER); + } + + @Test + @DisplayName( + "generate() should build className = PascalCase(artifactId) + ApplicationTests and render file") + void generate_shouldBuildClassNameAndRenderFile() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = new TemplateDefinition("test.ftl", "src/test/java"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + TestScaffolderAdapter adapter = + new TestScaffolderAdapter(renderer, artifactDefinition, new StringCaseFormatter()); + + ProjectBlueprint blueprint = blueprint(); + + Path expectedPath = Path.of("src/test/java/com/acme/demo/DemoAppApplicationTests.java"); + + GeneratedFile.Text expectedFile = + new GeneratedFile.Text( + expectedPath, "class DemoAppApplicationTests {}", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); + assertThat(renderer.capturedTemplateName).isEqualTo("test.ftl"); + + Map model = renderer.capturedModel; + assertThat(model) + .isNotNull() + .containsEntry("projectPackageName", "com.acme.demo") + .containsEntry("className", "DemoAppApplicationTests"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java new file mode 100644 index 0000000..37957e5 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java @@ -0,0 +1,63 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: GitIgnoreAdapter") +class GitIgnoreAdapterTest { + + @Test + @DisplayName("artifactKey() should return GITIGNORE") + void artifactKey_shouldReturnGitignore() { + GitIgnoreAdapter adapter = + new GitIgnoreAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition(List.of(new TemplateDefinition("gitignore.ftl", ".gitignore")))); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.GITIGNORE); + } + + @Test + @DisplayName("generate() should render .gitignore with an empty ignoreList model") + void generate_shouldRenderGitignoreWithEmptyIgnoreList() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = new TemplateDefinition("gitignore.ftl", ".gitignore"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + GitIgnoreAdapter adapter = new GitIgnoreAdapter(renderer, artifactDefinition); + + ProjectBlueprint blueprint = new ProjectBlueprint(null, null, null, null, null, null, null); + + Path relativePath = Path.of(".gitignore"); + GeneratedFile.Text expectedFile = + new GeneratedFile.Text(relativePath, "# gitignore", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("gitignore.ftl"); + + Map model = renderer.capturedModel; + assertThat(model).isNotNull().containsEntry("ignoreList", List.of()); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java new file mode 100644 index 0000000..9a2f5db --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java @@ -0,0 +1,70 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; +import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import io.github.bsayli.codegen.initializr.testsupport.templating.CapturingTemplateRenderer; +import io.github.bsayli.codegen.initializr.testsupport.templating.NoopTemplateRenderer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: MavenWrapperAdapter") +class MavenWrapperAdapterTest { + + @Test + @DisplayName("artifactKey() should return MAVEN_WRAPPER") + void artifactKey_shouldReturnMavenWrapper() { + MavenWrapperAdapter adapter = + new MavenWrapperAdapter( + new NoopTemplateRenderer(), + new ArtifactDefinition( + List.of( + new TemplateDefinition( + "maven-wrapper.ftl", ".mvn/wrapper/maven-wrapper.properties")))); + + assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.MAVEN_WRAPPER); + } + + @Test + @DisplayName("generate() should build model with default wrapper and maven versions") + void generate_shouldBuildModelWithDefaultVersions() { + CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); + + TemplateDefinition templateDefinition = + new TemplateDefinition("maven-wrapper.ftl", ".mvn/wrapper/maven-wrapper.properties"); + ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + + MavenWrapperAdapter adapter = new MavenWrapperAdapter(renderer, artifactDefinition); + + ProjectBlueprint blueprint = new ProjectBlueprint(null, null, null, null, null, null, null); + + Path relativePath = Path.of(".mvn/wrapper/maven-wrapper.properties"); + GeneratedFile.Text expectedFile = + new GeneratedFile.Text(relativePath, "distributionUrl=...", StandardCharsets.UTF_8); + renderer.nextFile = expectedFile; + + Iterable result = adapter.generate(blueprint); + + assertThat(result).singleElement().isSameAs(expectedFile); + + assertThat(renderer.capturedOutPath).isEqualTo(relativePath); + assertThat(renderer.capturedTemplateName).isEqualTo("maven-wrapper.ftl"); + + Map model = renderer.capturedModel; + assertThat(model) + .isNotNull() + .containsEntry("wrapperVersion", "3.3.3") + .containsEntry("mavenVersion", "3.9.11"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatterTest.java new file mode 100644 index 0000000..7eabc88 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/shared/naming/StringCaseFormatterTest.java @@ -0,0 +1,100 @@ +package io.github.bsayli.codegen.initializr.adapter.shared.naming; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +@Tag("unit") +@Tag("adapter") +@DisplayName("Unit Test: StringCaseFormatter") +class StringCaseFormatterTest { + + private final StringCaseFormatter formatter = new StringCaseFormatter(); + + @ParameterizedTest + @NullSource + @ValueSource(strings = {" ", "---___*** ###"}) + @DisplayName("null, blank or delimiters-only input should return empty string") + void nullBlankOrDelimitersOnly_shouldReturnEmpty(String input) { + String result = formatter.toPascalCase(input); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("single word should be capitalized") + void singleWord_shouldBeCapitalized() { + String result = formatter.toPascalCase("hello"); + + assertThat(result).isEqualTo("Hello"); + } + + @Test + @DisplayName("single word with mixed case should be normalized to PascalCase") + void singleWord_mixedCase_shouldBeNormalized() { + String result = formatter.toPascalCase("hELLo"); + + assertThat(result).isEqualTo("Hello"); + } + + @Test + @DisplayName("already PascalCase word should be normalized to first upper and rest lower") + void pascalCaseWord_shouldNormalizeRestToLower() { + String result = formatter.toPascalCase("MyValue"); + + assertThat(result).isEqualTo("Myvalue"); + } + + @Test + @DisplayName("space separated words should become concatenated PascalCase") + void spaceSeparatedWords_shouldBecomePascalCase() { + String result = formatter.toPascalCase("hello world example"); + + assertThat(result).isEqualTo("HelloWorldExample"); + } + + @Test + @DisplayName("multiple spaces and leading/trailing spaces should be ignored") + void multipleSpaces_shouldBeHandled() { + String result = formatter.toPascalCase(" hello world "); + + assertThat(result).isEqualTo("HelloWorld"); + } + + @Test + @DisplayName("kebab-case should become PascalCase") + void kebabCase_shouldBecomePascalCase() { + String result = formatter.toPascalCase("my-service-name"); + + assertThat(result).isEqualTo("MyServiceName"); + } + + @Test + @DisplayName("snake_case should become PascalCase") + void snakeCase_shouldBecomePascalCase() { + String result = formatter.toPascalCase("my_service_name"); + + assertThat(result).isEqualTo("MyServiceName"); + } + + @Test + @DisplayName("mixed delimiters and digits should be handled correctly") + void mixedDelimiters_andDigits_shouldBeHandled() { + String result = formatter.toPascalCase(" my-service_42 NAME "); + + assertThat(result).isEqualTo("MyService42Name"); + } + + @Test + @DisplayName("non-alphanumeric delimiters should be treated as separators") + void nonAlphanumericDelimiters_shouldBeSeparators() { + String result = formatter.toPascalCase("app@core#service!"); + + assertThat(result).isEqualTo("AppCoreService"); + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java index 152357b..eb96c0c 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/model/value/naming/ProjectDescriptionTest.java @@ -16,9 +16,9 @@ class ProjectDescriptionTest { @Test @DisplayName("valid description should be normalized and accepted") void validDescription_shouldNormalizeAndAccept() { - ProjectDescription desc = new ProjectDescription(" This is A Test "); + ProjectDescription desc = new ProjectDescription(" This is a test "); - assertThat(desc.value()).isEqualTo("this is a test"); + assertThat(desc.value()).isEqualTo("This is a test"); assertThat(desc.isEmpty()).isFalse(); } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/testsupport/build/RecordingPomDependencyMapper.java b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/build/RecordingPomDependencyMapper.java new file mode 100644 index 0000000..5447774 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/build/RecordingPomDependencyMapper.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.testsupport.build; + +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; +import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import java.util.List; + +public final class RecordingPomDependencyMapper extends PomDependencyMapper { + + private final List toReturn; + public Dependencies capturedDependencies; + + public RecordingPomDependencyMapper(List toReturn) { + this.toReturn = toReturn; + } + + @Override + public List from(Dependencies dependencies) { + this.capturedDependencies = dependencies; + return toReturn; + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/CapturingTemplateRenderer.java b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/CapturingTemplateRenderer.java new file mode 100644 index 0000000..b9fb297 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/CapturingTemplateRenderer.java @@ -0,0 +1,22 @@ +package io.github.bsayli.codegen.initializr.testsupport.templating; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.file.Path; +import java.util.Map; + +public final class CapturingTemplateRenderer implements TemplateRenderer { + + public Path capturedOutPath; + public String capturedTemplateName; + public Map capturedModel; + public GeneratedFile nextFile; + + @Override + public GeneratedFile renderUtf8(Path outPath, String templateName, Map model) { + this.capturedOutPath = outPath; + this.capturedTemplateName = templateName; + this.capturedModel = model; + return nextFile; + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/NoopTemplateRenderer.java b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/NoopTemplateRenderer.java new file mode 100644 index 0000000..206df77 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/testsupport/templating/NoopTemplateRenderer.java @@ -0,0 +1,15 @@ +package io.github.bsayli.codegen.initializr.testsupport.templating; + +import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Map; + +public final class NoopTemplateRenderer implements TemplateRenderer { + + @Override + public GeneratedFile renderUtf8(Path outPath, String templateName, Map model) { + return new GeneratedFile.Text(outPath, "", StandardCharsets.UTF_8); + } +} From a6a37445d35d0bf9e4e22c7d0aade23df36c26eb Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 23 Nov 2025 15:14:11 +0300 Subject: [PATCH 23/74] Refactor adapter/bootstrap package structure for clarity and consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Moved artifact-related base adapters to adapter.out.shared.artifact - Consolidated profile-specific adapters under adapter.out.profile.springboot.maven.java - Unified ProfileType and selector components under adapter.out.profile - Normalized domain/application ports naming (artifact → singular) - Introduced bootstrap.templating and bootstrap.error.exception packages - Updated wiring configs to use new package paths and registry locations - Ensured naming consistency across mapper/util packages (StringCaseFormatter, PomDependencyMapper) - Removed ambiguous or duplicated structural fragments to align with hexagonal rules --- .../error/exception/AdapterException.java | 2 +- .../ArtifactKeyMismatchException.java | 2 +- .../ArtifactsPortNotFoundException.java | 2 +- .../GeneratorFactoryNotFoundException.java | 2 +- .../UnknownArtifactKeyException.java | 2 +- .../out/build/maven/shared/PomDependency.java | 12 +- .../FileSystemProjectArchiverAdapter.java | 142 +++++++++--------- .../ProfileBasedArtifactsSelector.java | 3 +- .../{ => out}/profile/ProfileType.java | 2 +- .../SpringBootMavenJavaArtifactsAdapter.java | 4 +- .../maven/java/build/MavenPomAdapter.java | 6 +- .../java/config/ApplicationYamlAdapter.java | 6 +- .../maven/java/docs/ReadmeAdapter.java | 6 +- .../AbstractJavaClassScaffolderAdapter.java | 2 +- .../java/source/SourceScaffolderAdapter.java | 4 +- .../java/test/TestScaffolderAdapter.java | 4 +- .../maven/java/vcs/GitIgnoreAdapter.java | 6 +- .../java/wrapper/MavenWrapperAdapter.java | 6 +- ...AbstractSingleTemplateArtifactAdapter.java | 4 +- .../{artifacts => artifact}/ArtifactKey.java | 2 +- .../{artifacts => artifact}/ArtifactPort.java | 2 +- .../ConfigFilesPort.java | 2 +- .../GitIgnorePort.java | 2 +- .../{artifacts => artifact}/MavenPomPort.java | 2 +- .../MavenWrapperPort.java | 2 +- .../{artifacts => artifact}/ReadmePort.java | 2 +- .../SourceScaffolderPort.java | 2 +- .../TestScaffolderPort.java | 2 +- .../CreateProjectCommand.java | 2 +- .../CreateProjectHandler.java | 2 +- .../CreateProjectResult.java | 2 +- .../CreateProjectUseCase.java | 2 +- .../DependencyInput.java | 2 +- .../ProjectBlueprintMapper.java | 2 +- .../config/ArtifactKeyConverter.java | 2 +- .../config/CodegenProfilesProperties.java | 6 +- .../bootstrap/config/ProfileProperties.java | 2 +- .../InfrastructureException.java | 2 +- .../ProfileConfigurationException.java | 2 +- .../FreeMarkerTemplatingConfig.java} | 6 +- .../FreeMarkerTemplatingProperties.java | 2 +- .../ProjectArtifactsSelectorConfig.java | 4 +- .../wiring/SpringBootMavenJavaConfig.java | 10 +- .../naming/ProjectDescriptionPolicy.java | 1 - .../ProfileBasedArtifactsSelectorTest.java | 7 +- ...ringBootMavenJavaArtifactsAdapterTest.java | 4 +- .../maven/java/build/MavenPomAdapterTest.java | 2 +- .../config/ApplicationYamlAdapterTest.java | 2 +- .../maven/java/docs/ReadmeAdapterTest.java | 2 +- ...bstractJavaClassScaffolderAdapterTest.java | 2 +- .../source/SourceScaffolderAdapterTest.java | 2 +- .../java/test/TestScaffolderAdapterTest.java | 2 +- .../maven/java/vcs/GitIgnoreAdapterTest.java | 2 +- .../java/wrapper/MavenWrapperAdapterTest.java | 2 +- ...ractSingleTemplateArtifactAdapterTest.java | 4 +- .../CreateProjectHandlerTest.java | 2 +- .../ProjectBlueprintMapperTest.java | 2 +- 57 files changed, 156 insertions(+), 163 deletions(-) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{ => profile}/ProfileBasedArtifactsSelector.java (89%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/{ => out}/profile/ProfileType.java (94%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{ => profile/springboot/maven/java}/SpringBootMavenJavaArtifactsAdapter.java (88%) rename src/main/java/io/github/bsayli/codegen/initializr/adapter/out/{ => shared}/artifact/AbstractSingleTemplateArtifactAdapter.java (93%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/ArtifactKey.java (98%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/ArtifactPort.java (96%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/ConfigFilesPort.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/GitIgnorePort.java (90%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/MavenPomPort.java (90%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/MavenWrapperPort.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/ReadmePort.java (90%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/SourceScaffolderPort.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/port/out/{artifacts => artifact}/TestScaffolderPort.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/CreateProjectCommand.java (97%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/CreateProjectHandler.java (99%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/CreateProjectResult.java (90%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/CreateProjectUseCase.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/DependencyInput.java (91%) rename src/main/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/ProjectBlueprintMapper.java (99%) rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/{ => exception}/InfrastructureException.java (93%) rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/{ => exception}/ProfileConfigurationException.java (88%) rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/{freemarker/FreeMarkerTemplatingConfiguration.java => templating/FreeMarkerTemplatingConfig.java} (91%) rename src/main/java/io/github/bsayli/codegen/initializr/bootstrap/{freemarker => templating}/FreeMarkerTemplatingProperties.java (89%) rename src/test/java/io/github/bsayli/codegen/initializr/adapter/out/{ => profile}/ProfileBasedArtifactsSelectorTest.java (91%) rename src/test/java/io/github/bsayli/codegen/initializr/adapter/out/{ => profile/springboot/maven/java}/SpringBootMavenJavaArtifactsAdapterTest.java (94%) rename src/test/java/io/github/bsayli/codegen/initializr/adapter/out/{ => shared}/artifact/AbstractSingleTemplateArtifactAdapterTest.java (96%) rename src/test/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/CreateProjectHandlerTest.java (99%) rename src/test/java/io/github/bsayli/codegen/initializr/application/usecase/{createproject => project}/ProjectBlueprintMapperTest.java (99%) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java index fbb56e8..d74b7fa 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/AdapterException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.bootstrap.error.InfrastructureException; +import io.github.bsayli.codegen.initializr.bootstrap.error.exception.InfrastructureException; public abstract class AdapterException extends InfrastructureException { protected AdapterException(String messageKey, Object... args) { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java index c61782f..6981627 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactKeyMismatchException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; @SuppressWarnings("java:S110") public final class ArtifactKeyMismatchException extends AdapterException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java index 74cccaa..962d087 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ArtifactsPortNotFoundException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileType; @SuppressWarnings("java:S110") public final class ArtifactsPortNotFoundException extends AdapterException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java index 02a8d7b..b8790c1 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; @SuppressWarnings("java:S110") public final class GeneratorFactoryNotFoundException extends AdapterException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java index 4383aec..8775f9b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/UnknownArtifactKeyException.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.adapter.error.exception; -import io.github.bsayli.codegen.initializr.bootstrap.error.InfrastructureException; +import io.github.bsayli.codegen.initializr.bootstrap.error.exception.InfrastructureException; @SuppressWarnings("java:S110") public final class UnknownArtifactKeyException extends InfrastructureException { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java index b885926..bc02b7e 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/build/maven/shared/PomDependency.java @@ -1,17 +1,11 @@ package io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared; -public record PomDependency( - String groupId, - String artifactId, - String version, - String scope -) { +public record PomDependency(String groupId, String artifactId, String version, String scope) { public static PomDependency of(String groupId, String artifactId) { return new PomDependency(groupId, artifactId, null, null); } - public static PomDependency of( - String groupId, String artifactId, String version, String scope) { + public static PomDependency of(String groupId, String artifactId, String version, String scope) { return new PomDependency(groupId, artifactId, version, scope); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java index 1308d66..9b7a83f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/filesystem/FileSystemProjectArchiverAdapter.java @@ -13,85 +13,85 @@ public class FileSystemProjectArchiverAdapter implements ProjectArchiverPort { - private static final String ZIP_EXTENSION = ".zip"; - private static final char ZIP_SEPARATOR = '/'; - - @Override - public Path archive(Path projectRoot, String artifactId) { - if (projectRoot == null) { - throw new ProjectArchiveInvalidRootException(null); - } - - Path parent = projectRoot.getParent(); - if (parent == null) { - throw new ProjectArchiveInvalidRootException(projectRoot); - } - - if (!Files.exists(projectRoot) || !Files.isDirectory(projectRoot)) { - throw new ProjectArchiveInvalidRootException(projectRoot); - } - - String baseName = - (artifactId == null || artifactId.isBlank()) - ? projectRoot.getFileName().toString() - : artifactId; - - Path archivePath = parent.resolve(baseName + ZIP_EXTENSION); - - try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(archivePath))) { - writeDirectoryToZip(projectRoot, baseName, zipOut); - return archivePath; - } catch (IOException e) { - throw new ProjectArchiveIOException(projectRoot, e); - } + private static final String ZIP_EXTENSION = ".zip"; + private static final char ZIP_SEPARATOR = '/'; + + @Override + public Path archive(Path projectRoot, String artifactId) { + if (projectRoot == null) { + throw new ProjectArchiveInvalidRootException(null); } - private void writeDirectoryToZip(Path root, String rootName, ZipOutputStream zos) - throws IOException { - Path normalizedRoot = root.toAbsolutePath().normalize(); - - try (Stream paths = Files.walk(normalizedRoot)) { - paths.forEachOrdered( - path -> { - try { - writeEntry(normalizedRoot, rootName, path, zos); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } catch (UncheckedIOException e) { - throw e.getCause(); - } + Path parent = projectRoot.getParent(); + if (parent == null) { + throw new ProjectArchiveInvalidRootException(projectRoot); } - private void writeEntry(Path root, String rootName, Path current, ZipOutputStream zos) - throws IOException { + if (!Files.exists(projectRoot) || !Files.isDirectory(projectRoot)) { + throw new ProjectArchiveInvalidRootException(projectRoot); + } - Path relative = root.relativize(current); - StringBuilder entryName = new StringBuilder(); - entryName.append(rootName).append(ZIP_SEPARATOR); + String baseName = + (artifactId == null || artifactId.isBlank()) + ? projectRoot.getFileName().toString() + : artifactId; - String rel = relative.toString(); - if (!rel.isEmpty()) { - String fsSep = root.getFileSystem().getSeparator(); - if (!fsSep.equals(String.valueOf(ZIP_SEPARATOR))) { - rel = rel.replace(fsSep, String.valueOf(ZIP_SEPARATOR)); - } - entryName.append(rel); - } + Path archivePath = parent.resolve(baseName + ZIP_EXTENSION); - boolean directory = Files.isDirectory(current); - if (directory && entryName.charAt(entryName.length() - 1) != ZIP_SEPARATOR) { - entryName.append(ZIP_SEPARATOR); - } + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(archivePath))) { + writeDirectoryToZip(projectRoot, baseName, zipOut); + return archivePath; + } catch (IOException e) { + throw new ProjectArchiveIOException(projectRoot, e); + } + } + + private void writeDirectoryToZip(Path root, String rootName, ZipOutputStream zos) + throws IOException { + Path normalizedRoot = root.toAbsolutePath().normalize(); + + try (Stream paths = Files.walk(normalizedRoot)) { + paths.forEachOrdered( + path -> { + try { + writeEntry(normalizedRoot, rootName, path, zos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + private void writeEntry(Path root, String rootName, Path current, ZipOutputStream zos) + throws IOException { + + Path relative = root.relativize(current); + StringBuilder entryName = new StringBuilder(); + entryName.append(rootName).append(ZIP_SEPARATOR); + + String rel = relative.toString(); + if (!rel.isEmpty()) { + String fsSep = root.getFileSystem().getSeparator(); + if (!fsSep.equals(String.valueOf(ZIP_SEPARATOR))) { + rel = rel.replace(fsSep, String.valueOf(ZIP_SEPARATOR)); + } + entryName.append(rel); + } - ZipEntry entry = new ZipEntry(entryName.toString()); - zos.putNextEntry(entry); + boolean directory = Files.isDirectory(current); + if (directory && entryName.charAt(entryName.length() - 1) != ZIP_SEPARATOR) { + entryName.append(ZIP_SEPARATOR); + } - if (!directory) { - Files.copy(current, zos); - } + ZipEntry entry = new ZipEntry(entryName.toString()); + zos.putNextEntry(entry); - zos.closeEntry(); + if (!directory) { + Files.copy(current, zos); } -} \ No newline at end of file + + zos.closeEntry(); + } +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelector.java similarity index 89% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelector.java index 148c679..508ad10 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelector.java @@ -1,8 +1,7 @@ -package io.github.bsayli.codegen.initializr.adapter.out; +package io.github.bsayli.codegen.initializr.adapter.out.profile; import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactsPortNotFoundException; import io.github.bsayli.codegen.initializr.adapter.error.exception.UnsupportedProfileTypeException; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileType.java similarity index 94% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileType.java index 117b656..e28586b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/profile/ProfileType.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileType.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.profile; +package io.github.bsayli.codegen.initializr.adapter.out.profile; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapter.java similarity index 88% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapter.java index 297a84a..24ac143 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapter.java @@ -1,7 +1,7 @@ -package io.github.bsayli.codegen.initializr.adapter.out; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.util.List; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java index 7cf5a0c..5864f85 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapter.java @@ -2,12 +2,12 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.shared.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenPomPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.MavenPomPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java index f6d6d85..8a8dbb5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapter.java @@ -2,10 +2,10 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.shared.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ConfigFilesPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ConfigFilesPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import java.util.Map; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java index 960cc16..c8727bd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapter.java @@ -2,12 +2,12 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.shared.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ReadmePort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ReadmePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java index fd6bf7a..9b2e53c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java @@ -4,7 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java index 7a3b66b..ccbfc9b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapter.java @@ -3,8 +3,8 @@ import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.AbstractJavaClassScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.SourceScaffolderPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.SourceScaffolderPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java index 98dbd29..4978f6a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapter.java @@ -3,8 +3,8 @@ import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.shared.AbstractJavaClassScaffolderAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.TestScaffolderPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.TestScaffolderPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java index afcbcd3..3f3e20a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapter.java @@ -1,9 +1,9 @@ package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs; -import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.shared.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.GitIgnorePort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.GitIgnorePort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import java.util.List; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java index c2dcda1..8e6041b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapter.java @@ -2,10 +2,10 @@ import static java.util.Map.entry; -import io.github.bsayli.codegen.initializr.adapter.out.artifact.AbstractSingleTemplateArtifactAdapter; +import io.github.bsayli.codegen.initializr.adapter.out.shared.artifact.AbstractSingleTemplateArtifactAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.MavenWrapperPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.MavenWrapperPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import java.util.Map; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java similarity index 93% rename from src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java rename to src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java index 3c4524c..afb3850 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java @@ -1,7 +1,7 @@ -package io.github.bsayli.codegen.initializr.adapter.out.artifact; +package io.github.bsayli.codegen.initializr.adapter.out.shared.artifact; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactKey.java similarity index 98% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactKey.java index 93f14f5..63bd3bf 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactKey.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactKey.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; import io.github.bsayli.codegen.initializr.adapter.error.exception.UnknownArtifactKeyException; import java.util.Arrays; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactPort.java similarity index 96% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactPort.java index 7a78e35..fcfa837 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ArtifactPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ArtifactPort.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ConfigFilesPort.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ConfigFilesPort.java index bd04385..31de809 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ConfigFilesPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ConfigFilesPort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface ConfigFilesPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/GitIgnorePort.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/GitIgnorePort.java index 1c5d47d..2877603 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/GitIgnorePort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/GitIgnorePort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface GitIgnorePort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenPomPort.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenPomPort.java index b7163b7..413cf21 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenPomPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenPomPort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface MavenPomPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenWrapperPort.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenWrapperPort.java index 4066d1e..7625fb8 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/MavenWrapperPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/MavenWrapperPort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface MavenWrapperPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ReadmePort.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ReadmePort.java index 6b74273..de213a2 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/ReadmePort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/ReadmePort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface ReadmePort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/SourceScaffolderPort.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/SourceScaffolderPort.java index 01cf93e..fddb715 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/SourceScaffolderPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/SourceScaffolderPort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface SourceScaffolderPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/TestScaffolderPort.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/TestScaffolderPort.java index 95403bb..770ac7a 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifacts/TestScaffolderPort.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/port/out/artifact/TestScaffolderPort.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.port.out.artifacts; +package io.github.bsayli.codegen.initializr.application.port.out.artifact; public interface TestScaffolderPort extends ArtifactPort {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectCommand.java similarity index 97% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectCommand.java index b7c17de..968c768 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectCommand.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectCommand.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandler.java similarity index 99% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandler.java index 8577aaa..2b26faa 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandler.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandler.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectResult.java similarity index 90% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectResult.java index ff2d834..e7e34fb 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectResult.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectResult.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import java.nio.file.Path; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectUseCase.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectUseCase.java index 39ea89c..92261a4 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectUseCase.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectUseCase.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; public interface CreateProjectUseCase { CreateProjectResult handle(CreateProjectCommand command); diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/DependencyInput.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/DependencyInput.java index 9e06939..ccb0397 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/DependencyInput.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/DependencyInput.java @@ -1,3 +1,3 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; public record DependencyInput(String groupId, String artifactId, String version, String scope) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapper.java similarity index 99% rename from src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java rename to src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapper.java index c9e7db8..11da5a7 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapper.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapper.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import io.github.bsayli.codegen.initializr.domain.factory.ProjectBlueprintFactory; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java index a5723fc..660a921 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactKeyConverter.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.NonNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 4609a08..76d863c 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -1,8 +1,8 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.error.exception.ProfileConfigurationException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import java.util.Map; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java index 6544a71..dc6a207 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ProfileProperties.java @@ -1,6 +1,6 @@ package io.github.bsayli.codegen.initializr.bootstrap.config; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/InfrastructureException.java similarity index 93% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/InfrastructureException.java index 5df6cfc..0f00914 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/InfrastructureException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/InfrastructureException.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.bootstrap.error; +package io.github.bsayli.codegen.initializr.bootstrap.error.exception; import java.io.Serial; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/ProfileConfigurationException.java similarity index 88% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/ProfileConfigurationException.java index 165955f..dc85199 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/ProfileConfigurationException.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/error/exception/ProfileConfigurationException.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.bootstrap.error; +package io.github.bsayli.codegen.initializr.bootstrap.error.exception; public final class ProfileConfigurationException extends InfrastructureException { public static final String KEY_PROFILE_NOT_FOUND = "bootstrap.profile.not.found"; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingConfig.java similarity index 91% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingConfig.java index dab58c8..2551c98 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingConfiguration.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingConfig.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.bootstrap.freemarker; +package io.github.bsayli.codegen.initializr.bootstrap.templating; import static freemarker.template.Configuration.VERSION_2_3_34; @@ -12,7 +12,7 @@ @org.springframework.context.annotation.Configuration @EnableConfigurationProperties(FreeMarkerTemplatingProperties.class) -public class FreeMarkerTemplatingConfiguration { +public class FreeMarkerTemplatingConfig { public static final String NUMBER_FORMAT_COMPUTER = "computer"; @@ -20,7 +20,7 @@ public class FreeMarkerTemplatingConfiguration { private final FreeMarkerTemplatingProperties props; - public FreeMarkerTemplatingConfiguration(FreeMarkerTemplatingProperties props) { + public FreeMarkerTemplatingConfig(FreeMarkerTemplatingProperties props) { this.props = props; } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingProperties.java similarity index 89% rename from src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java rename to src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingProperties.java index f168629..8fd70df 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/freemarker/FreeMarkerTemplatingProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/templating/FreeMarkerTemplatingProperties.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.bootstrap.freemarker; +package io.github.bsayli.codegen.initializr.bootstrap.templating; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java index cebc5b4..d80fc6b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/ProjectArtifactsSelectorConfig.java @@ -1,7 +1,7 @@ package io.github.bsayli.codegen.initializr.bootstrap.wiring; -import io.github.bsayli.codegen.initializr.adapter.out.ProfileBasedArtifactsSelector; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileBasedArtifactsSelector; +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsSelector; import java.util.Collections; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java index 44a4c79..c15759f 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/wiring/SpringBootMavenJavaConfig.java @@ -1,8 +1,9 @@ package io.github.bsayli.codegen.initializr.bootstrap.wiring; import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactKeyMismatchException; -import io.github.bsayli.codegen.initializr.adapter.out.SpringBootMavenJavaArtifactsAdapter; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileType; +import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.SpringBootMavenJavaArtifactsAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.build.MavenPomAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.config.ApplicationYamlAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.docs.ReadmeAdapter; @@ -11,14 +12,13 @@ import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.vcs.GitIgnoreAdapter; import io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java.wrapper.MavenWrapperAdapter; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactPort; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.CodegenProfilesProperties; -import io.github.bsayli.codegen.initializr.bootstrap.error.ProfileConfigurationException; +import io.github.bsayli.codegen.initializr.bootstrap.error.exception.ProfileConfigurationException; import java.util.Collections; import java.util.EnumMap; import java.util.List; diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java index 58d677b..14ea055 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/naming/ProjectDescriptionPolicy.java @@ -7,7 +7,6 @@ import io.github.bsayli.codegen.initializr.domain.policy.rule.RegexMatchRule; import io.github.bsayli.codegen.initializr.domain.policy.rule.base.CompositeRule; import io.github.bsayli.codegen.initializr.domain.policy.rule.base.Rule; -import java.util.Locale; import java.util.regex.Pattern; public final class ProjectDescriptionPolicy { diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelectorTest.java similarity index 91% rename from src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelectorTest.java index f0cb722..7135d35 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/ProfileBasedArtifactsSelectorTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/ProfileBasedArtifactsSelectorTest.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.adapter.out; +package io.github.bsayli.codegen.initializr.adapter.out.profile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -6,7 +6,6 @@ import io.github.bsayli.codegen.initializr.adapter.error.exception.ArtifactsPortNotFoundException; import io.github.bsayli.codegen.initializr.adapter.error.exception.UnsupportedProfileTypeException; -import io.github.bsayli.codegen.initializr.adapter.profile.ProfileType; import io.github.bsayli.codegen.initializr.application.port.out.ProjectArtifactsPort; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; @@ -26,7 +25,9 @@ class ProfileBasedArtifactsSelectorTest { void shouldThrowWhenProfileUnsupported() { BuildOptions options = mock(BuildOptions.class); - assertThatThrownBy(() -> new ProfileBasedArtifactsSelector(Map.of()).select(options)) + ProfileBasedArtifactsSelector selector = new ProfileBasedArtifactsSelector(Map.of()); + + assertThatThrownBy(() -> selector.select(options)) .isInstanceOf(UnsupportedProfileTypeException.class); } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterTest.java similarity index 94% rename from src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterTest.java index 4a58067..c0985d7 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/SpringBootMavenJavaArtifactsAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterTest.java @@ -1,9 +1,9 @@ -package io.github.bsayli.codegen.initializr.adapter.out; +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactPort; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactPort; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; import java.util.Collections; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java index cd54f80..acb0d5c 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java index 213b2d9..812b245 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java index 9a36481..2964a84 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java @@ -4,7 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependency; import io.github.bsayli.codegen.initializr.adapter.out.build.maven.shared.PomDependencyMapper; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java index f2156a2..c143a9b 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java @@ -4,7 +4,7 @@ import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java index 116675f..8500940 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java index 93d800e..d81412d 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.adapter.shared.naming.StringCaseFormatter; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java index 37957e5..ecccd97 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java index 9a2f5db..de9b089 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java similarity index 96% rename from src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java index 0050fce..2e16a5f 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/artifact/AbstractSingleTemplateArtifactAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java @@ -1,9 +1,9 @@ -package io.github.bsayli.codegen.initializr.adapter.out.artifact; +package io.github.bsayli.codegen.initializr.adapter.out.shared.artifact; import static org.assertj.core.api.Assertions.assertThat; import io.github.bsayli.codegen.initializr.adapter.out.templating.TemplateRenderer; -import io.github.bsayli.codegen.initializr.application.port.out.artifacts.ArtifactKey; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; import io.github.bsayli.codegen.initializr.bootstrap.config.ArtifactDefinition; import io.github.bsayli.codegen.initializr.bootstrap.config.TemplateDefinition; import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandlerTest.java similarity index 99% rename from src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandlerTest.java index d8068ee..df6affb 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/CreateProjectHandlerTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/CreateProjectHandlerTest.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import static io.github.bsayli.codegen.initializr.domain.port.out.filesystem.ProjectRootExistencePolicy.FAIL_IF_EXISTS; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapperTest.java similarity index 99% rename from src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java rename to src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapperTest.java index c89a9fa..f6817d2 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/createproject/ProjectBlueprintMapperTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/application/usecase/project/ProjectBlueprintMapperTest.java @@ -1,4 +1,4 @@ -package io.github.bsayli.codegen.initializr.application.usecase.createproject; +package io.github.bsayli.codegen.initializr.application.usecase.project; import static org.assertj.core.api.Assertions.assertThat; From 6c98f194249b64f4109e5ade47e34e16b7a94ce0 Mon Sep 17 00:00:00 2001 From: bsayli Date: Sun, 23 Nov 2025 16:37:39 +0300 Subject: [PATCH 24/74] fix(codegen): add template basePath support to artifact generation & update adapters/tests - Introduce `basePath` into ArtifactDefinition to prefix all template lookups - Update CodegenProfilesProperties to return enhanced ArtifactDefinition with resolved basePath - Update AbstractSingleTemplateArtifactAdapter to prepend basePath to templateName - Update AbstractJavaClassScaffolderAdapter to use basePath for template resolution - Normalize template-base-path values in application.yml (ensure trailing slash) - Refactor and fix all affected adapter unit tests (POM, source, test scaffolder) - Add consistent path expectations to CapturingTemplateRenderer assertions - Improve dummy adapter usage in AbstractJavaClassScaffolderAdapterTest for clarity This fixes incorrect template path resolution and aligns all adapters with the new profile-driven template structure. --- .../GeneratorFactoryNotFoundException.java | 12 ------ .../exception/ProjectArchiveException.java | 13 ------- .../AbstractJavaClassScaffolderAdapter.java | 17 ++++---- ...AbstractSingleTemplateArtifactAdapter.java | 11 ++++-- .../bootstrap/config/ArtifactDefinition.java | 5 ++- .../config/CodegenProfilesProperties.java | 17 ++++++-- .../exception/DomainConflictException.java | 9 ----- .../value/dependency/DependencyFactory.java | 39 ------------------- src/main/resources/application.yml | 2 +- .../maven/java/build/MavenPomAdapterTest.java | 10 +++-- .../config/ApplicationYamlAdapterTest.java | 8 +++- .../maven/java/docs/ReadmeAdapterTest.java | 10 +++-- ...bstractJavaClassScaffolderAdapterTest.java | 9 +++-- .../source/SourceScaffolderAdapterTest.java | 10 +++-- .../java/test/TestScaffolderAdapterTest.java | 10 +++-- .../maven/java/vcs/GitIgnoreAdapterTest.java | 10 +++-- .../java/wrapper/MavenWrapperAdapterTest.java | 8 +++- ...ractSingleTemplateArtifactAdapterTest.java | 7 +++- 18 files changed, 92 insertions(+), 115 deletions(-) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java deleted file mode 100644 index b8790c1..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/GeneratorFactoryNotFoundException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.bsayli.codegen.initializr.adapter.error.exception; - -import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; - -@SuppressWarnings("java:S110") -public final class GeneratorFactoryNotFoundException extends AdapterException { - private static final String KEY = "adapter.generator.factory.not.found"; - - public GeneratorFactoryNotFoundException(ArtifactKey artifactKey) { - super(KEY, artifactKey.key()); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java deleted file mode 100644 index 2a8a31d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/error/exception/ProjectArchiveException.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.bsayli.codegen.initializr.adapter.error.exception; - -import java.nio.file.Path; - -@SuppressWarnings("java:S110") -public final class ProjectArchiveException extends AdapterException { - - private static final String KEY = "adapter.project.archive.failed"; - - public ProjectArchiveException(Path projectRoot, Throwable cause) { - super(KEY, cause, projectRoot); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java index 9b2e53c..cff3468 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java @@ -28,9 +28,9 @@ public abstract class AbstractJavaClassScaffolderAdapter implements ArtifactPort private final StringCaseFormatter stringCaseFormatter; protected AbstractJavaClassScaffolderAdapter( - TemplateRenderer renderer, - ArtifactDefinition artifactDefinition, - StringCaseFormatter stringCaseFormatter) { + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { this.renderer = renderer; this.artifactDefinition = artifactDefinition; this.stringCaseFormatter = stringCaseFormatter; @@ -42,17 +42,18 @@ public final Iterable generate(ProjectBlueprint bluepri PackageName packageName = blueprint.getPackageName(); Map model = - Map.ofEntries( - entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), + entry(KEY_CLASS_NAME, className)); TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); Path baseDir = Path.of(templateDefinition.outputPath()); - String template = templateDefinition.template(); + String templateName = artifactDefinition.basePath() + templateDefinition.template(); String packagePath = packageName.value().replace(PACKAGE_PATH_DELIMITER, FILE_PATH_DELIMITER); Path outPath = baseDir.resolve(packagePath).resolve(className + JAVA_FILE_EXTENSION); - GeneratedFile file = renderer.renderUtf8(outPath, template, model); + GeneratedFile file = renderer.renderUtf8(outPath, templateName, model); return List.of(file); } @@ -61,4 +62,4 @@ protected String pascal(String value) { } protected abstract String buildClassName(ProjectBlueprint blueprint); -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java index afb3850..ca91d13 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java @@ -16,7 +16,7 @@ public abstract class AbstractSingleTemplateArtifactAdapter implements ArtifactP private final ArtifactDefinition artifactDefinition; protected AbstractSingleTemplateArtifactAdapter( - TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { this.renderer = renderer; this.artifactDefinition = artifactDefinition; } @@ -24,12 +24,15 @@ protected AbstractSingleTemplateArtifactAdapter( @Override public final Iterable generate(ProjectBlueprint blueprint) { TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); + Path outPath = Path.of(templateDefinition.outputPath()); - String template = templateDefinition.template(); + String templateName = artifactDefinition.basePath() + templateDefinition.template(); + Map model = buildModel(blueprint); - GeneratedFile file = renderer.renderUtf8(outPath, template, model); + GeneratedFile file = renderer.renderUtf8(outPath, templateName, model); + return List.of(file); } protected abstract Map buildModel(ProjectBlueprint blueprint); -} +} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java index 4de4599..53e912d 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java @@ -4,4 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; -public record ArtifactDefinition(@Valid @NotNull List templates) {} +public record ArtifactDefinition( + String basePath, + @Valid @NotNull List templates +) {} \ No newline at end of file diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 76d863c..652ce22 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -29,12 +29,21 @@ public ProfileProperties requireProfile(ProfileType profile) { } ArtifactDefinition requireArtifact( - ProfileType profile, ProfileProperties profileProps, ArtifactKey artifactKey) { - var artifact = profileProps.artifacts().get(artifactKey.key()); + ProfileType profile, ProfileProperties profileProps, ArtifactKey artifactKey) { + + ArtifactDefinition artifact = profileProps.artifacts().get(artifactKey.key()); if (artifact == null) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); + ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); + } + + String basePath = profileProps.templateBasePath(); + + if (basePath == null || basePath.isBlank()) { + throw new ProfileConfigurationException( + ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING, profile.key()); } - return artifact; + + return new ArtifactDefinition(basePath, artifact.templates()); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java deleted file mode 100644 index 9f44f59..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/error/exception/DomainConflictException.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.error.exception; - -import io.github.bsayli.codegen.initializr.domain.error.code.ErrorCode; - -public class DomainConflictException extends DomainException { - public DomainConflictException(ErrorCode code, Object... args) { - super(code, args); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java deleted file mode 100644 index c8f6a60..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/DependencyFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.bsayli.codegen.initializr.domain.model.value.dependency; - -import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; -import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; - -public final class DependencyFactory { - - private DependencyFactory() {} - - public static DependencyCoordinates coordinates(GroupId groupId, ArtifactId artifactId) { - return new DependencyCoordinates(groupId, artifactId); - } - - public static Dependency of(DependencyCoordinates coordinates) { - return new Dependency(coordinates, null, null); - } - - public static Dependency of(DependencyCoordinates coordinates, DependencyVersion version) { - return new Dependency(coordinates, version, null); - } - - public static Dependency of( - DependencyCoordinates coordinates, DependencyVersion version, DependencyScope scope) { - return new Dependency(coordinates, version, scope); - } - - public static Dependency of(GroupId groupId, ArtifactId artifactId) { - return of(coordinates(groupId, artifactId)); - } - - public static Dependency of(GroupId groupId, ArtifactId artifactId, DependencyVersion version) { - return of(coordinates(groupId, artifactId), version); - } - - public static Dependency of( - GroupId groupId, ArtifactId artifactId, DependencyVersion version, DependencyScope scope) { - return of(coordinates(groupId, artifactId), version, scope); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4bd130f..e44f8bd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: codegen: profiles: springboot-maven-java: - template-base-path: springboot/maven/java + template-base-path: springboot/maven/java/ ordered-artifact-keys: - pom diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java index acb0d5c..371fd5d 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/build/MavenPomAdapterTest.java @@ -42,6 +42,8 @@ @DisplayName("Unit Test: MavenPomAdapter") class MavenPomAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + private static ProjectBlueprint blueprintWithDependencies() { ProjectIdentity identity = new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); @@ -73,7 +75,8 @@ void artifactKey_shouldReturnPom() { MavenPomAdapter adapter = new MavenPomAdapter( new NoopTemplateRenderer(), - new ArtifactDefinition(List.of(new TemplateDefinition("pom.ftl", "pom.xml"))), + new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("pom.ftl", "pom.xml"))), new RecordingPomDependencyMapper(List.of())); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.POM); @@ -88,7 +91,8 @@ void generate_shouldBuildCorrectModelForPom() { List.of(PomDependency.of("org.acme", "custom-dep", "1.0.0", "runtime"))); TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); MavenPomAdapter adapter = new MavenPomAdapter(renderer, artifactDefinition, mapper); @@ -104,7 +108,7 @@ void generate_shouldBuildCorrectModelForPom() { assertThat(result).singleElement().isSameAs(dummyFile); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("pom.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "pom.ftl"); assertThat(renderer.capturedModel).isNotNull(); Map model = renderer.capturedModel; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java index 812b245..9af740f 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/config/ApplicationYamlAdapterTest.java @@ -23,6 +23,8 @@ @DisplayName("Unit Test: ApplicationYamlAdapter") class ApplicationYamlAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + @Test @DisplayName("artifactKey() should return APPLICATION_YAML") void artifactKey_shouldReturnApplicationYaml() { @@ -30,6 +32,7 @@ void artifactKey_shouldReturnApplicationYaml() { new ApplicationYamlAdapter( new NoopTemplateRenderer(), new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("application-yaml.ftl", "application.yml")))); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.APPLICATION_YAML); @@ -42,7 +45,8 @@ void generate_shouldBuildModelAndRenderFile() { TemplateDefinition templateDefinition = new TemplateDefinition("application-yaml.ftl", "src/main/resources/application.yml"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); ApplicationYamlAdapter adapter = new ApplicationYamlAdapter(renderer, artifactDefinition); @@ -67,7 +71,7 @@ void generate_shouldBuildModelAndRenderFile() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("application-yaml.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "application-yaml.ftl"); assertThat(renderer.capturedModel).isNotNull().containsEntry("projectName", "demo-app"); } } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java index 2964a84..3784075 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/docs/ReadmeAdapterTest.java @@ -43,6 +43,8 @@ @DisplayName("Unit Test: ReadmeAdapter") class ReadmeAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + private static ProjectBlueprint blueprintWithDependencies() { ProjectIdentity identity = new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); @@ -74,7 +76,8 @@ void artifactKey_shouldReturnReadme() { ReadmeAdapter adapter = new ReadmeAdapter( new NoopTemplateRenderer(), - new ArtifactDefinition(List.of(new TemplateDefinition("README.ftl", "README.md"))), + new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("README.ftl", "README.md"))), new PomDependencyMapper()); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.README); @@ -90,7 +93,8 @@ void generate_shouldBuildCorrectModelForReadme() { RecordingPomDependencyMapper mapper = new RecordingPomDependencyMapper(mappedDeps); TemplateDefinition templateDefinition = new TemplateDefinition("README.ftl", "README.md"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); ReadmeAdapter adapter = new ReadmeAdapter(renderer, artifactDefinition, mapper); @@ -106,7 +110,7 @@ void generate_shouldBuildCorrectModelForReadme() { assertThat(result).singleElement().isSameAs(dummyFile); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("README.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "README.ftl"); assertThat(renderer.capturedModel).isNotNull(); Map model = renderer.capturedModel; diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java index c143a9b..cb07a57 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapterTest.java @@ -23,6 +23,8 @@ @DisplayName("Unit Test: AbstractJavaClassScaffolderAdapter") class AbstractJavaClassScaffolderAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + @Test @DisplayName("generate() should build correct path, model and return single file") void generate_shouldBuildOutPathAndModelAndReturnFile() { @@ -31,7 +33,8 @@ void generate_shouldBuildOutPathAndModelAndReturnFile() { TemplateDefinition templateDefinition = new TemplateDefinition("java-class.ftl", "src/main/java"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); StringCaseFormatter formatter = new StringCaseFormatter(); @@ -52,7 +55,7 @@ void generate_shouldBuildOutPathAndModelAndReturnFile() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); - assertThat(renderer.capturedTemplateName).isEqualTo("java-class.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "java-class.ftl"); assertThat(renderer.capturedModel) .isNotNull() @@ -77,7 +80,7 @@ protected String buildClassName(ProjectBlueprint blueprint) { @Override public ArtifactKey artifactKey() { - return null; + return ArtifactKey.SOURCE_SCAFFOLDER; } } } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java index 8500940..aa2dacc 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/source/SourceScaffolderAdapterTest.java @@ -29,6 +29,8 @@ @DisplayName("Unit Test: SourceScaffolderAdapter") class SourceScaffolderAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + private static ProjectBlueprint blueprint() { ProjectIdentity identity = new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); @@ -46,7 +48,8 @@ void artifactKey_shouldReturnSourceScaffolder() { SourceScaffolderAdapter adapter = new SourceScaffolderAdapter( new NoopTemplateRenderer(), - new ArtifactDefinition(List.of(new TemplateDefinition("source.ftl", "src/main/java"))), + new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("source.ftl", "src/main/java"))), new StringCaseFormatter()); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.SOURCE_SCAFFOLDER); @@ -59,7 +62,8 @@ void generate_shouldBuildClassNameFromArtifactIdAndRenderFile() { CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); TemplateDefinition templateDefinition = new TemplateDefinition("source.ftl", "src/main/java"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); SourceScaffolderAdapter adapter = new SourceScaffolderAdapter(renderer, artifactDefinition, new StringCaseFormatter()); @@ -77,7 +81,7 @@ void generate_shouldBuildClassNameFromArtifactIdAndRenderFile() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); - assertThat(renderer.capturedTemplateName).isEqualTo("source.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "source.ftl"); Map model = renderer.capturedModel; assertThat(model) diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java index d81412d..9778080 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/test/TestScaffolderAdapterTest.java @@ -29,6 +29,8 @@ @DisplayName("Unit Test: TestScaffolderAdapter") class TestScaffolderAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + private static ProjectBlueprint blueprint() { ProjectIdentity identity = new ProjectIdentity(new GroupId("com.acme"), new ArtifactId("demo-app")); @@ -46,7 +48,8 @@ void artifactKey_shouldReturnTestScaffolder() { TestScaffolderAdapter adapter = new TestScaffolderAdapter( new NoopTemplateRenderer(), - new ArtifactDefinition(List.of(new TemplateDefinition("test.ftl", "src/test/java"))), + new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("test.ftl", "src/test/java"))), new StringCaseFormatter()); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.TEST_SCAFFOLDER); @@ -59,7 +62,8 @@ void generate_shouldBuildClassNameAndRenderFile() { CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); TemplateDefinition templateDefinition = new TemplateDefinition("test.ftl", "src/test/java"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); TestScaffolderAdapter adapter = new TestScaffolderAdapter(renderer, artifactDefinition, new StringCaseFormatter()); @@ -78,7 +82,7 @@ void generate_shouldBuildClassNameAndRenderFile() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(expectedPath); - assertThat(renderer.capturedTemplateName).isEqualTo("test.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "test.ftl"); Map model = renderer.capturedModel; assertThat(model) diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java index ecccd97..b8eb6de 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/vcs/GitIgnoreAdapterTest.java @@ -22,13 +22,16 @@ @DisplayName("Unit Test: GitIgnoreAdapter") class GitIgnoreAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + @Test @DisplayName("artifactKey() should return GITIGNORE") void artifactKey_shouldReturnGitignore() { GitIgnoreAdapter adapter = new GitIgnoreAdapter( new NoopTemplateRenderer(), - new ArtifactDefinition(List.of(new TemplateDefinition("gitignore.ftl", ".gitignore")))); + new ArtifactDefinition( + BASE_PATH, List.of(new TemplateDefinition("gitignore.ftl", ".gitignore")))); assertThat(adapter.artifactKey()).isEqualTo(ArtifactKey.GITIGNORE); } @@ -39,7 +42,8 @@ void generate_shouldRenderGitignoreWithEmptyIgnoreList() { CapturingTemplateRenderer renderer = new CapturingTemplateRenderer(); TemplateDefinition templateDefinition = new TemplateDefinition("gitignore.ftl", ".gitignore"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); GitIgnoreAdapter adapter = new GitIgnoreAdapter(renderer, artifactDefinition); @@ -55,7 +59,7 @@ void generate_shouldRenderGitignoreWithEmptyIgnoreList() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("gitignore.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "gitignore.ftl"); Map model = renderer.capturedModel; assertThat(model).isNotNull().containsEntry("ignoreList", List.of()); diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java index de9b089..0f0cef1 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/wrapper/MavenWrapperAdapterTest.java @@ -22,6 +22,8 @@ @DisplayName("Unit Test: MavenWrapperAdapter") class MavenWrapperAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + @Test @DisplayName("artifactKey() should return MAVEN_WRAPPER") void artifactKey_shouldReturnMavenWrapper() { @@ -29,6 +31,7 @@ void artifactKey_shouldReturnMavenWrapper() { new MavenWrapperAdapter( new NoopTemplateRenderer(), new ArtifactDefinition( + BASE_PATH, List.of( new TemplateDefinition( "maven-wrapper.ftl", ".mvn/wrapper/maven-wrapper.properties")))); @@ -43,7 +46,8 @@ void generate_shouldBuildModelWithDefaultVersions() { TemplateDefinition templateDefinition = new TemplateDefinition("maven-wrapper.ftl", ".mvn/wrapper/maven-wrapper.properties"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); MavenWrapperAdapter adapter = new MavenWrapperAdapter(renderer, artifactDefinition); @@ -59,7 +63,7 @@ void generate_shouldBuildModelWithDefaultVersions() { assertThat(result).singleElement().isSameAs(expectedFile); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("maven-wrapper.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "maven-wrapper.ftl"); Map model = renderer.capturedModel; assertThat(model) diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java index 2e16a5f..8b8a4ba 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapterTest.java @@ -22,6 +22,8 @@ @DisplayName("Unit Test: AbstractSingleTemplateArtifactAdapter") class AbstractSingleTemplateArtifactAdapterTest { + private static final String BASE_PATH = "springboot/maven/java/"; + @Test @DisplayName("generate() should use first template, render with model, and return single file") void generate_shouldRenderSingleTemplateAndReturnFile() { @@ -30,7 +32,8 @@ void generate_shouldRenderSingleTemplateAndReturnFile() { TemplateDefinition templateDefinition = new TemplateDefinition("test-template.ftl", "output/test.txt"); - ArtifactDefinition artifactDefinition = new ArtifactDefinition(List.of(templateDefinition)); + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(BASE_PATH, List.of(templateDefinition)); TestSingleTemplateAdapter adapter = new TestSingleTemplateAdapter(renderer, artifactDefinition); @@ -44,7 +47,7 @@ void generate_shouldRenderSingleTemplateAndReturnFile() { Iterable result = adapter.generate(blueprint); assertThat(renderer.capturedOutPath).isEqualTo(relativePath); - assertThat(renderer.capturedTemplateName).isEqualTo("test-template.ftl"); + assertThat(renderer.capturedTemplateName).isEqualTo(BASE_PATH + "test-template.ftl"); assertThat(renderer.capturedModel).isEqualTo(Map.of("key", "value")); assertThat(result).singleElement().isSameAs(expectedFile); From 5c22c164dbf2f905ff57ad29cb213911ebb2c0f6 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:12:02 +0300 Subject: [PATCH 25/74] chore(ci): add full CI/coverage/security pipeline (JaCoCo, Failsafe, Codecov, CodeQL) Added a complete CI and quality pipeline for the project: - Enabled JaCoCo for unit + integration tests * mvn verify now produces jacoco + jacoco-it reports * Added surefire + failsafe plugin setup for Test/IT separation - Added Codecov integration * Introduced codecov.yml with project/patch thresholds * build.yml now uploads both jacoco and jacoco-it reports - Added CodeQL workflow (codeql.yml) * Java analysis on push/PR + weekly cron * Uses github/codeql-action v4 * Builds project with -DskipTests=true for analysis - Updated build.yml (Build & Test workflow) * JDK 21 caching enabled * mvn -B clean verify now runs full pipeline These changes establish a production-grade CI foundation: consistent test separation, coverage reporting, and static analysis. --- .github/workflows/build.yml | 20 +++- .github/workflows/codeql.yml | 44 +++++++ codecov.yml | 25 ++++ pom.xml | 38 ++++++ .../config/CodegenProfilesPropertiesTest.java | 110 ++++++++++++++++++ 5 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 codecov.yml create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d021614..d952337 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,13 @@ on: branches: [ main ] pull_request: +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -17,6 +21,18 @@ jobs: with: java-version: '21' distribution: 'temurin' + cache: maven - - name: Build with Maven - run: mvn -B clean verify \ No newline at end of file + - name: Build with Maven (tests + coverage) + run: mvn -B -q -ntp clean verify + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: | + target/site/jacoco/jacoco.xml + target/site/jacoco-it/jacoco.xml + flags: codegen-springboot-initializr + name: codegen-springboot-initializr + fail_ci_if_error: false \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..d82d5d5 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,44 @@ +name: CodeQL + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '18 3 * * 1' + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + name: Analyze (CodeQL) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: java + queries: +security-and-quality + + - name: Build (codegen-springboot-initializr) + run: mvn -q -ntp -DskipTests=true clean package + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:java" \ No newline at end of file diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..3b5215e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,25 @@ +coverage: + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: 70% + +comment: + layout: "reach, diff, flags, files" + behavior: default + +ignore: + - "target/**" + - "**/target/**" + - "**/generated/**" + - "**/generated-sources/**" + +flags: + codegen-springboot-initializr: + paths: + - "src/main/java/" + - "src/test/java/" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 92ff08c..61eb99c 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ 3.9.11 2.20.0 3.18.0 + 0.8.13 @@ -153,6 +154,43 @@ + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + prepare-agent + + prepare-agent + + + + + prepare-agent-integration + + prepare-agent-integration + + + + + report + verify + + report + + + + + report-integration + verify + + report-integration + + + + diff --git a/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java b/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java new file mode 100644 index 0000000..b2621e6 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java @@ -0,0 +1,110 @@ +package io.github.bsayli.codegen.initializr.bootstrap.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.github.bsayli.codegen.initializr.adapter.out.profile.ProfileType; +import io.github.bsayli.codegen.initializr.application.port.out.artifact.ArtifactKey; +import io.github.bsayli.codegen.initializr.bootstrap.error.exception.ProfileConfigurationException; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +@Tag("bootstrap") +@DisplayName("Unit Test: CodegenProfilesProperties") +class CodegenProfilesPropertiesTest { + + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; + private static final ArtifactKey ARTIFACT_KEY = ArtifactKey.POM; + private static final String PROFILE_KEY = PROFILE.key(); + private static final String ARTIFACT_MAP_KEY = ARTIFACT_KEY.key(); + private static final String TEMPLATE_BASE_PATH = "springboot/maven/java/"; + + @Test + @DisplayName("artifact() should return ArtifactDefinition with profile basePath and artifact templates") + void artifact_shouldReturnDefinitionWithProfileBasePathAndArtifactTemplates() { + TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); + + ArtifactDefinition artifactDefinition = + new ArtifactDefinition("artifact-specific/", List.of(templateDefinition)); + + ProfileProperties profileProperties = + new ProfileProperties( + TEMPLATE_BASE_PATH, + List.of(ARTIFACT_KEY), + Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); + + CodegenProfilesProperties properties = + new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + + ArtifactDefinition result = properties.artifact(PROFILE, ARTIFACT_KEY); + + assertThat(result.basePath()).isEqualTo(TEMPLATE_BASE_PATH); + assertThat(result.templates()).isSameAs(artifactDefinition.templates()); + } + + @Test + @DisplayName("requireProfile() should throw ProfileConfigurationException when profile is missing") + void requireProfile_shouldThrowWhenProfileMissing() { + CodegenProfilesProperties properties = new CodegenProfilesProperties(Map.of()); + + assertThatThrownBy(() -> properties.requireProfile(PROFILE)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_PROFILE_NOT_FOUND); + assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); + }); + } + + @Test + @DisplayName("artifact() should throw ProfileConfigurationException when artifact is missing") + void artifact_shouldThrowWhenArtifactMissing() { + ProfileProperties profileProperties = + new ProfileProperties(TEMPLATE_BASE_PATH, List.of(ARTIFACT_KEY), Map.of()); + + CodegenProfilesProperties properties = + new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + + assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND); + assertThat(ex.getArgs()).containsExactly(ARTIFACT_MAP_KEY, PROFILE_KEY); + }); + } + + @Test + @DisplayName("artifact() should throw ProfileConfigurationException when templateBasePath is blank") + void artifact_shouldThrowWhenTemplateBasePathBlank() { + CodegenProfilesProperties properties = getCodegenProfilesProperties(); + + assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING); + assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); + }); + } + + private static CodegenProfilesProperties getCodegenProfilesProperties() { + TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); + + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(null, List.of(templateDefinition)); + + ProfileProperties profileProperties = + new ProfileProperties( + " ", List.of(ARTIFACT_KEY), Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); + + return new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + } +} \ No newline at end of file From 336432b3b1f57ae7411e4c6cab11b5def2c2c27d Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:22:11 +0300 Subject: [PATCH 26/74] docs(readme): modernize README header with correct badges and add Release badge - Updated README title to full project descriptor (Hexagonal, Templated, Zero-Boilerplate) - Added GitHub Release badge with correct repository path - Fixed Build, CodeQL, Codecov, Java, Spring Boot, Maven and License badges - Standardized badge layout and link targets - Prepared header section for future full documentation rewrite --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8a5dd69..8ca9f20 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -# Codegen Spring Boot Initializr - -![Build](https://github.com/bsayli/spring-boot-openapi-generics-clients/actions/workflows/build.yml/badge.svg) -![Java](https://img.shields.io/badge/Java-21-red) -![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-green) -![Maven](https://img.shields.io/badge/Maven-3.9-blue) -![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) +# Codegen Spring Boot Initializr — Hexagonal, Templated, Zero-Boilerplate Project Generator + +[![Build](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml) +[![Release](https://img.shields.io/github/v/release/bsayli/codegen-springboot-initializr?logo=github&label=release)](https://github.com/bsayli/codegen-springboot-initializr/releases/latest) +[![CodeQL](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml) +[![codecov](https://codecov.io/gh/bsayli/codegen-springboot-initializr/branch/main/graph/badge.svg)](https://codecov.io/gh/bsayli/codegen-springboot-initializr) +[![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.7-green?logo=springboot)](https://spring.io/projects/spring-boot) +[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Social preview From b3d2f7b0abd9ac4fcb9ca9b7fd15bcd9d867412b Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:27:01 +0300 Subject: [PATCH 27/74] refactor: remove legacy project-generation and CLI modules - Deleted old `cli` module relying on deprecated project-generation workflow - Removed entire `projectgeneration` package (metadata builders, service, DTOs) - Cleaned up all associated tests and outdated helper classes - These modules are now obsolete due to the new hexagonal architecture (domain/application/adapter separation with profile-driven artifact pipeline) This keeps the codebase minimal, consistent and fully aligned with the new architecture. --- .../codegen/initializr/cli/CliRunner.java | 162 --------------- .../adapters/GitIgnoreFileGenerator.java | 33 --- .../ProjectRootDirectoryInitializer.java | 36 ---- .../adapters/ZipProjectArchiver.java | 100 --------- .../SpringBootApplicationYamlGenerator.java | 34 --- .../SpringBootJavaMainClassGenerator.java | 55 ----- .../SpringBootJavaTestClassGenerator.java | 52 ----- ...ingBootMavenJavaProjectBuildGenerator.java | 58 ------ .../SpringBootMavenJavaReadMeGenerator.java | 30 --- ...ngBootJavaMainClassGeneratorConstants.java | 10 - ...ngBootJavaTestClassGeneratorConstants.java | 10 - .../maven/MavenBuildWrapperGenerator.java | 36 ---- .../MavenJavaProjectLayoutGenerator.java | 66 ------ .../templating/FreeMarkerTemplateEngine.java | 49 ----- .../FreeMarkerTemplateConfiguration.java | 65 ------ ...ringBootMavenJavaProjectConfiguration.java | 36 ---- .../FreeMarkerTemplateProperties.java | 7 - .../MavenJavaSourceFolderProperties.java | 17 -- .../generator/ProjectGenerator.java | 18 -- .../base/AbstractProjectGenerator.java | 118 ----------- .../SpringBootMavenJavaProjectGenerator.java | 39 ---- .../projectgeneration/model/Dependency.java | 87 -------- .../model/ProjectMetadata.java | 103 --------- .../projectgeneration/model/ProjectType.java | 7 - .../model/maven/MavenPlugin.java | 69 ------ .../model/maven/MavenPom.java | 80 ------- .../spring/SpringBootJavaProjectMetadata.java | 52 ----- .../model/templating/TemplateType.java | 25 --- .../naming/NameConverter.java | 30 --- .../ports/ApplicationYamlGenerator.java | 11 - ...FrameworkProjectStarterClassGenerator.java | 11 - .../FrameworkSpecificTestUnitGenerator.java | 11 - .../ports/GitIgnoreGenerator.java | 11 - .../ports/ProjectArchiver.java | 10 - .../ports/ProjectBuildGenerator.java | 11 - .../ports/ProjectBuildWrapperGenerator.java | 11 - .../ports/ProjectDirectoryInitializer.java | 11 - .../ports/ProjectDocumentationGenerator.java | 11 - .../ports/ProjectLayoutGenerator.java | 11 - .../ports/TemplateEngine.java | 16 -- .../registry/ProjectGeneratorRegistry.java | 10 - .../SimpleProjectGeneratorRegistry.java | 23 -- ...ProjectGeneratorRegistryConfiguration.java | 34 --- .../service/ProjectGenerationService.java | 11 - .../service/ProjectGenerationServiceImpl.java | 34 --- .../exception/ProjectGenerationException.java | 12 -- src/main/resources/application.yml | 14 -- src/main/resources/templates/gitignore.ftl | 76 ------- .../templates/maven-wrapper.properties.ftl | 33 --- .../templates/springBootApplication.yml.ftl | 8 - .../templates/springBootJavaPom.xml.ftl | 67 ------ .../templates/springBootMainClass.java.ftl | 13 -- .../templates/springBootMavenJavaReadMe.ftl | 53 ----- .../templates/springBootTestClass.java.ftl | 14 -- .../adapters/GitIgnoreFileGeneratorTest.java | 86 -------- .../ProjectRootDirectoryInitializerTest.java | 96 --------- .../adapters/ZipProjectArchiverTest.java | 196 ------------------ ...pringBootApplicationYamlGeneratorTest.java | 87 -------- .../SpringBootJavaMainClassGeneratorTest.java | 107 ---------- .../SpringBootJavaTestClassGeneratorTest.java | 70 ------- ...ootMavenJavaProjectBuildGeneratorTest.java | 141 ------------- ...pringBootMavenJavaReadMeGeneratorTest.java | 94 --------- .../maven/MavenBuildWrapperGeneratorTest.java | 65 ------ .../MavenJavaProjectLayoutGeneratorTest.java | 78 ------- .../FreeMarkerTemplateEngineTest.java | 56 ----- .../FreeMarkerTemplateConfigurationTest.java | 42 ---- .../SimpleProjectGeneratorRegistryTest.java | 36 ---- .../ProjectGenerationServiceImplTest.java | 181 ---------------- 68 files changed, 3346 deletions(-) delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializer.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiver.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaMainClassGeneratorConstants.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaTestClassGeneratorConstants.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngine.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfiguration.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/SpringBootMavenJavaProjectConfiguration.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/FreeMarkerTemplateProperties.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/MavenJavaSourceFolderProperties.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/ProjectGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/base/AbstractProjectGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/springboot/maven/SpringBootMavenJavaProjectGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/Dependency.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectMetadata.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPlugin.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPom.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/spring/SpringBootJavaProjectMetadata.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/templating/TemplateType.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/naming/NameConverter.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ApplicationYamlGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkProjectStarterClassGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkSpecificTestUnitGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/GitIgnoreGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectArchiver.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildWrapperGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDirectoryInitializer.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDocumentationGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectLayoutGenerator.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/TemplateEngine.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/ProjectGeneratorRegistry.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistry.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationService.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImpl.java delete mode 100644 src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/exception/ProjectGenerationException.java delete mode 100644 src/main/resources/templates/gitignore.ftl delete mode 100644 src/main/resources/templates/maven-wrapper.properties.ftl delete mode 100644 src/main/resources/templates/springBootApplication.yml.ftl delete mode 100644 src/main/resources/templates/springBootJavaPom.xml.ftl delete mode 100644 src/main/resources/templates/springBootMainClass.java.ftl delete mode 100644 src/main/resources/templates/springBootMavenJavaReadMe.ftl delete mode 100644 src/main/resources/templates/springBootTestClass.java.ftl delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializerTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGeneratorTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngineTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfigurationTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java delete mode 100644 src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java diff --git a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java b/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java deleted file mode 100644 index 1afb601..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/cli/CliRunner.java +++ /dev/null @@ -1,162 +0,0 @@ -package io.github.bsayli.codegen.initializr.cli; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGenerationService; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.*; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -@Component -@Profile("cli") -public class CliRunner implements ApplicationRunner { - - private static final Logger log = LoggerFactory.getLogger(CliRunner.class); - - private static final String ARG_GROUP_ID = "groupId"; - private static final String ARG_ARTIFACT_ID = "artifactId"; - private static final String ARG_NAME = "name"; - private static final String ARG_PACKAGE_NAME = "packageName"; - private static final String ARG_JAVA_VERSION = "javaVersion"; - private static final String ARG_BOOT_VERSION = "springBootVersion"; - private static final String ARG_OUTPUT_DIR = "outputDir"; - private static final String ARG_OVERWRITE = "overwrite"; - - private static final String DEF_GROUP_ID = "com.example"; - private static final String DEF_ARTIFACT_ID = "demo-app"; - private static final String DEF_PACKAGE_NAME = "com.example.demo"; - private static final String DEF_JAVA_VERSION = "21"; - private static final String DEF_BOOT_VERSION = "3.5.5"; - private static final String DEF_DESCRIPTION = "Generated by codegen-initializr-core"; - private static final boolean DEF_OVERWRITE = false; - - private static final Path DEFAULT_OUTPUT_ROOT = - Paths.get(System.getProperty("user.dir"), "target", "generated-projects"); - - private final ProjectGenerationService service; - - public CliRunner(ProjectGenerationService service) { - this.service = service; - } - - @Override - public void run(ApplicationArguments args) throws Exception { - String groupId = argOrDefault(args, ARG_GROUP_ID, DEF_GROUP_ID); - String artifact = argOrDefault(args, ARG_ARTIFACT_ID, DEF_ARTIFACT_ID); - String name = argOrDefault(args, ARG_NAME, artifact); - String pkg = argOrDefault(args, ARG_PACKAGE_NAME, DEF_PACKAGE_NAME); - String javaVer = argOrDefault(args, ARG_JAVA_VERSION, DEF_JAVA_VERSION); - String bootVer = argOrDefault(args, ARG_BOOT_VERSION, DEF_BOOT_VERSION); - Path outputRoot = argPath(args, ARG_OUTPUT_DIR).orElse(DEFAULT_OUTPUT_ROOT); - boolean overwrite = argBoolean(args, ARG_OVERWRITE, DEF_OVERWRITE); - - Path projectDir = outputRoot.resolve(artifact); - - if (Files.exists(projectDir)) { - if (overwrite) { - log.info( - "♻️ Existing directory found. Deleting before regeneration: {}", - projectDir.toAbsolutePath()); - deleteRecursively(projectDir); - } else { - throw new IllegalStateException( - "Target directory already exists: " - + projectDir.toAbsolutePath() - + " (use --overwrite=true to replace it)"); - } - } - - var depWeb = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-web") - .build(); - - var depTest = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-test") - .scope("test") - .build(); - - var metadata = - new SpringBootJavaProjectMetadataBuilder() - .springBootVersion(bootVer) - .javaVersion(javaVer) - .groupId(groupId) - .artifactId(artifact) - .name(name) - .description(DEF_DESCRIPTION) - .packageName(pkg) - .projectLocation(outputRoot) - .dependencies(List.of(depWeb, depTest)) - .build(); - - var type = new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); - - Path zip = service.generateProject(type, metadata); - - log.info("✅ Project archive generated at: {}", zip.toAbsolutePath()); - if (outputRoot.equals(DEFAULT_OUTPUT_ROOT)) { - log.info("ℹ️ Tip: Use --{}=/absolute/path to control the output location.", ARG_OUTPUT_DIR); - } - } - - private String argOrDefault(ApplicationArguments args, String name, String def) { - var values = args.getOptionValues(name); - return (values != null && !values.isEmpty()) ? values.getFirst() : def; - } - - private Optional argPath(ApplicationArguments args, String name) { - var values = args.getOptionValues(name); - if (values == null || values.isEmpty()) return Optional.empty(); - String raw = values.getFirst(); - if (raw == null || raw.isBlank()) return Optional.empty(); - return Optional.of(Paths.get(raw)); - } - - private boolean argBoolean(ApplicationArguments args, String name, boolean def) { - var values = args.getOptionValues(name); - if (values == null || values.isEmpty()) return def; - String raw = values.getFirst(); - if (raw == null) return def; - return switch (raw.trim().toLowerCase()) { - case "true", "1", "yes", "y" -> true; - case "false", "0", "no", "n" -> false; - default -> def; - }; - } - - private void deleteRecursively(Path dir) { - if (!Files.exists(dir)) return; - - try (Stream paths = Files.walk(dir)) { - paths - .sorted(Comparator.reverseOrder()) - .forEach( - p -> { - try { - Files.deleteIfExists(p); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } catch (IOException e) { - throw new UncheckedIOException("Failed to clean directory: " + dir.toAbsolutePath(), e); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGenerator.java deleted file mode 100644 index 6a8d26d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGenerator.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.GitIgnoreGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("gitIgnoreFileGenerator") -public class GitIgnoreFileGenerator implements GitIgnoreGenerator { - - private final TemplateEngine freeMarkerTemplateEngine; - - public GitIgnoreFileGenerator(TemplateEngine freeMarkerTemplateEngine) { - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - } - - @Override - public void generateGitIgnoreContent(File projectDestination, List ignoreList) - throws IOException { - - Map gitIgnoreData = new HashMap<>(); - gitIgnoreData.put("ignoreList", ignoreList != null ? ignoreList : Collections.emptyList()); - - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.GITIGNORE, gitIgnoreData, projectDestination); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializer.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializer.java deleted file mode 100644 index 05c5c20..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializer.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDirectoryInitializer; -import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.springframework.stereotype.Component; - -@Component("projectRootDirectoryInitializer") -public class ProjectRootDirectoryInitializer implements ProjectDirectoryInitializer { - - @Override - public Path initializeProjectDirectory(String projectName) throws IOException { - Path tempPath = Files.createTempDirectory(projectName); - Path projectPath = Paths.get(tempPath.toString(), projectName); - Files.createDirectories(projectPath); - return projectPath; - } - - @Override - public Path initializeProjectDirectory(String projectName, Path projectLocation) - throws IOException { - if (projectLocation != null) { - Path projectDir = projectLocation.resolve(projectName); - if (Files.exists(projectDir)) { - throw new FileAlreadyExistsException(projectDir.toString(), null, "File already exists!"); - } - Files.createDirectories(projectDir); // Create directories recursively - return projectDir; - } else { - return initializeProjectDirectory(projectName); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiver.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiver.java deleted file mode 100644 index be4c8f5..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiver.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectArchiver; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; -import org.springframework.stereotype.Component; - -@Component("zipProjectArchiver") -public class ZipProjectArchiver implements ProjectArchiver { - - @Override - public Path archiveProject(File projectDestination, String projectName) throws IOException { - projectName = sanitizeFilename(projectName); - String archiveFilename = projectName + ".zip"; - File archiveFile = new File(projectDestination.getParent(), archiveFilename); - - try (FileOutputStream fos = new FileOutputStream(archiveFile); - ZipOutputStream zipOut = new ZipOutputStream(fos)) { - - zipOut.setLevel(ZipOutputStream.DEFLATED); - - Path projectPath = Paths.get(projectDestination.getAbsolutePath()); - - addFilesToZip(projectPath, zipOut); - } - - return archiveFile.toPath(); - } - - private void addFilesToZip(Path projectPath, ZipOutputStream zipOut) throws IOException { - String rootFileName = sanitizeRootFileName(projectPath.getFileName().toString()); - List processingErrors = new ArrayList<>(); - try (Stream walkStream = Files.walk(projectPath)) { - walkStream.forEach( - filePath -> { - String entryName = getEntryName(projectPath, filePath, rootFileName); - try { - addFileToZip(zipOut, filePath, entryName); - } catch (IOException e) { - processingErrors.add( - String.format("Error processing file: %s. Reason: %s", filePath, e.getMessage())); - } - }); - } - - if (!processingErrors.isEmpty()) { - throw new IOException( - String.format( - "Error encountered during archive creation for %s: %s", - projectPath.getFileName(), String.join(", ", processingErrors))); - } - } - - private String getEntryName(Path projectPath, Path filePath, String rootFileName) { - if (projectPath.equals(filePath)) { - return rootFileName; - } else { - String entryName = sanitizeEntryName(projectPath.relativize(filePath).toString()); - return "/" + rootFileName + "/" + entryName; - } - } - - private void addFileToZip(ZipOutputStream zipOut, Path filePath, String entryName) - throws IOException { - if (Files.isDirectory(filePath)) { - zipOut.putNextEntry(new ZipEntry(entryName + "/")); - } else { - zipOut.putNextEntry(new ZipEntry(entryName)); - try (FileInputStream fis = new FileInputStream(filePath.toFile())) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = fis.read(buffer)) != -1) { - zipOut.write(buffer, 0, bytesRead); - } - } - } - } - - private String sanitizeFilename(String filename) { - return filename.replaceAll("[/:*?<>|\\\\.^]", "").replaceAll("--+", "-"); - } - - private String sanitizeEntryName(String filename) { - return filename.replaceAll("[:*?<>|\\\\^]", "").replaceAll("-+", "_"); - } - - private String sanitizeRootFileName(String filename) { - return filename.replaceAll("[:*?<>|\\\\.^]", "").replaceAll("--+", "-"); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGenerator.java deleted file mode 100644 index 0551f74..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGenerator.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ApplicationYamlGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("springBootApplicationYamlGenerator") -public class SpringBootApplicationYamlGenerator implements ApplicationYamlGenerator { - - private static final String SRC_MAIN_RESOURCES = "src/main/resources"; - private final TemplateEngine freeMarkerTemplateEngine; - - public SpringBootApplicationYamlGenerator(TemplateEngine freeMarkerTemplateEngine) { - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - } - - @Override - public void generateApplicationYaml(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - Map appPropertiesModel = new HashMap<>(); - appPropertiesModel.put("projectName", projectMetadata.getName()); - - File srcMainResourcesFile = new File(projectDestination, SRC_MAIN_RESOURCES); - - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.SPRING_BOOT_APPLICATION_YAML, appPropertiesModel, srcMainResourcesFile); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGenerator.java deleted file mode 100644 index 4341ee0..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGenerator.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants.SpringBootJavaMainClassGeneratorConstants.FILE_NAME_EXTENSION; -import static io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants.SpringBootJavaMainClassGeneratorConstants.FILE_NAME_POSTFIX; -import static io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants.SpringBootJavaMainClassGeneratorConstants.TEMPLATE_NAME; - -import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.MavenJavaSourceFolderProperties; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.naming.NameConverter; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkProjectStarterClassGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("springBootJavaMainClassGenerator") -public class SpringBootJavaMainClassGenerator implements FrameworkProjectStarterClassGenerator { - - private final TemplateEngine freeMarkerTemplateEngine; - private final MavenJavaSourceFolderProperties sourceFolder; - private final NameConverter nameConverter; - - public SpringBootJavaMainClassGenerator( - TemplateEngine freeMarkerTemplateEngine, - MavenJavaSourceFolderProperties sourceFolder, - NameConverter nameConverter) { - this.sourceFolder = sourceFolder; - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - this.nameConverter = nameConverter; - } - - @Override - public void generateProjectStarterClass(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - - Map mainClassModel = new HashMap<>(); - mainClassModel.put("projectPackageName", projectMetadata.getPackageName()); - - // e.g. "codegen-demo" -> "CodegenDemo" + "Application" - String classBase = nameConverter.toPascalCase(projectMetadata.getName()); - String className = classBase + FILE_NAME_POSTFIX; - mainClassModel.put("className", className); - - String basePackagePath = projectMetadata.getPackageName().replace(".", "/"); - File srcMainJavaFile = new File(projectDestination, sourceFolder.srcMainJava()); - - File mainClassFileDestination = new File(srcMainJavaFile, basePackagePath); - String fileName = className + FILE_NAME_EXTENSION; - - freeMarkerTemplateEngine.generateFileFromTemplate( - TEMPLATE_NAME, fileName, mainClassModel, mainClassFileDestination); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGenerator.java deleted file mode 100644 index 8008858..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGenerator.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants.SpringBootJavaTestClassGeneratorConstants.*; - -import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.MavenJavaSourceFolderProperties; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.naming.NameConverter; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkSpecificTestUnitGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("springBootJavaTestClassGenerator") -public class SpringBootJavaTestClassGenerator implements FrameworkSpecificTestUnitGenerator { - - private final TemplateEngine freeMarkerTemplateEngine; - private final MavenJavaSourceFolderProperties sourceFolder; - private final NameConverter nameConverter; - - public SpringBootJavaTestClassGenerator( - TemplateEngine freeMarkerTemplateEngine, - MavenJavaSourceFolderProperties sourceFolder, - NameConverter nameConverter) { - this.sourceFolder = sourceFolder; - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - this.nameConverter = nameConverter; - } - - @Override - public void generateTestClass(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - - Map testClassModel = new HashMap<>(); - testClassModel.put("projectPackageName", projectMetadata.getPackageName()); - - String classBase = nameConverter.toPascalCase(projectMetadata.getName()); - String className = classBase + FILE_NAME_POSTFIX; - testClassModel.put("className", className); - - String basePackagePath = projectMetadata.getPackageName().replace(".", "/"); - File srcTestJavaFile = new File(projectDestination, sourceFolder.srcTestJava()); - - File testClassFileDestination = new File(srcTestJavaFile, basePackagePath); - String fileName = className + FILE_NAME_EXTENSION; - - freeMarkerTemplateEngine.generateFileFromTemplate( - TEMPLATE_NAME, fileName, testClassModel, testClassFileDestination); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGenerator.java deleted file mode 100644 index 817a7fd..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGenerator.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.maven.MavenPlugin; -import io.github.bsayli.codegen.initializr.projectgeneration.model.maven.MavenPom; -import io.github.bsayli.codegen.initializr.projectgeneration.model.maven.MavenPom.MavenPomBuilder; -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectBuildGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectBuildWrapperGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("springBootMavenJavaProjectBuildGenerator") -public class SpringBootMavenJavaProjectBuildGenerator implements ProjectBuildGenerator { - - private static final String MAVEN_MODEL_VERSION = "4.0.0"; - private static final String MAVEN_PROJECT_VERSION = "0.0.1-SNAPSHOT"; - - private final TemplateEngine freeMarkerTemplateEngine; - private final ProjectBuildWrapperGenerator mavenBuildWrapperGenerator; - private final List springBootMavenJavaPlugins; - - public SpringBootMavenJavaProjectBuildGenerator( - TemplateEngine freeMarkerTemplateEngine, - ProjectBuildWrapperGenerator mavenBuildWrapperGenerator, - List springBootMavenJavaPlugins) { - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - this.springBootMavenJavaPlugins = springBootMavenJavaPlugins; - this.mavenBuildWrapperGenerator = mavenBuildWrapperGenerator; - } - - @Override - public void generateBuildConfiguration(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - MavenPomBuilder mavenPomBuilder = new MavenPom.MavenPomBuilder(); - MavenPom mavenPom = - mavenPomBuilder - .modelVersion(MAVEN_MODEL_VERSION) - .version(MAVEN_PROJECT_VERSION) - .projectMetadata(projectMetadata) - .addDependencies(projectMetadata.getDependencies()) - .addPlugins(springBootMavenJavaPlugins) - .build(); - - Map pomModel = new HashMap<>(); - pomModel.put("pom", mavenPom); - - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.SPRING_BOOT_JAVA_POM, pomModel, projectDestination); - - mavenBuildWrapperGenerator.generateBuildWrapper(projectDestination, projectMetadata); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGenerator.java deleted file mode 100644 index 6961c3f..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGenerator.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDocumentationGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("springBootMavenJavaReadMeGenerator") -public class SpringBootMavenJavaReadMeGenerator implements ProjectDocumentationGenerator { - - private final TemplateEngine freeMarkerTemplateEngine; - - public SpringBootMavenJavaReadMeGenerator(TemplateEngine freeMarkerTemplateEngine) { - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - } - - @Override - public void generateProjectDocument(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - Map readMeModel = new HashMap<>(); - readMeModel.put("projectName", projectMetadata.getName()); - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.SPRING_BOOT_MAVEN_JAVA_README, readMeModel, projectDestination); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaMainClassGeneratorConstants.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaMainClassGeneratorConstants.java deleted file mode 100644 index 610d1d5..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaMainClassGeneratorConstants.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants; - -public class SpringBootJavaMainClassGeneratorConstants { - - public static final String TEMPLATE_NAME = "springBootMainClass.java.ftl"; - public static final String FILE_NAME_POSTFIX = "Application"; - public static final String FILE_NAME_EXTENSION = ".java"; - - private SpringBootJavaMainClassGeneratorConstants() {} -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaTestClassGeneratorConstants.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaTestClassGeneratorConstants.java deleted file mode 100644 index c703031..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/constants/SpringBootJavaTestClassGeneratorConstants.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot.constants; - -public class SpringBootJavaTestClassGeneratorConstants { - - public static final String TEMPLATE_NAME = "springBootTestClass.java.ftl"; - public static final String FILE_NAME_POSTFIX = "ApplicationTests"; - public static final String FILE_NAME_EXTENSION = ".java"; - - private SpringBootJavaTestClassGeneratorConstants() {} -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGenerator.java deleted file mode 100644 index 493c3d3..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGenerator.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.maven; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectBuildWrapperGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("mavenBuildWrapperGenerator") -public class MavenBuildWrapperGenerator implements ProjectBuildWrapperGenerator { - - private static final String MAVEN_VERSION = "3.9.11"; - private static final String WRAPPER_VERSION = "3.3.3"; - private static final String WRAPPER_FILE_DIR = ".mvn/wrapper"; - - private final TemplateEngine freeMarkerTemplateEngine; - - public MavenBuildWrapperGenerator(TemplateEngine freeMarkerTemplateEngine) { - this.freeMarkerTemplateEngine = freeMarkerTemplateEngine; - } - - @Override - public void generateBuildWrapper(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - Map wrapperModel = new HashMap<>(); - wrapperModel.put("wrapperVersion", WRAPPER_VERSION); - wrapperModel.put("mavenVersion", MAVEN_VERSION); - File wrapperFileDestination = new File(projectDestination, WRAPPER_FILE_DIR); - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.MAVEN_WRAPPER, wrapperModel, wrapperFileDestination); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGenerator.java deleted file mode 100644 index 9098da3..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGenerator.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.maven; - -import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.MavenJavaSourceFolderProperties; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectLayoutGenerator; -import java.io.File; -import java.io.IOException; -import java.util.List; -import org.springframework.stereotype.Component; - -@Component("mavenJavaProjectLayoutGenerator") -public class MavenJavaProjectLayoutGenerator implements ProjectLayoutGenerator { - - private final MavenJavaSourceFolderProperties sourceFolder; - - public MavenJavaProjectLayoutGenerator(MavenJavaSourceFolderProperties sourceFolder) { - this.sourceFolder = sourceFolder; - } - - @Override - public void generateProjectLayout(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - generateSourceFolders(projectDestination); - generatePackages(projectDestination, projectMetadata.getPackageName()); - } - - private void generateSourceFolders(File projectDestination) { - sourceFolder - .getSourceFolders() - .forEach( - s -> { - File sourceDir = new File(projectDestination, s); - if (!sourceDir.exists()) { - sourceDir.mkdirs(); - } - }); - } - - private void generatePackages(File projectDestination, String packageName) { - String packageNamePath = packageName.replace(".", "/"); - packageNamePath = sanitizePackageName(packageNamePath); - - File sourceFolderMainJavaFile = new File(projectDestination, sourceFolder.srcMainJava()); - File packageJavaFile = new File(sourceFolderMainJavaFile, packageNamePath); - - File sourceFolderTestJavaFile = new File(projectDestination, sourceFolder.srcTestJava()); - File packageTestFile = new File(sourceFolderTestJavaFile, packageNamePath); - - String packageGenPath = packageNamePath + "/codegen"; - File sourceFolderGenJavaFile = new File(projectDestination, sourceFolder.srcGenJava()); - File packageGenFile = new File(sourceFolderGenJavaFile, packageGenPath); - - List projectPackages = List.of(packageJavaFile, packageTestFile, packageGenFile); - - projectPackages.forEach( - p -> { - if (!p.exists()) { - p.mkdirs(); - } - }); - } - - private String sanitizePackageName(String packageName) { - return packageName.replaceAll("[\\:*?<>|\\\\\\^]", "").replaceAll("-+", "_"); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngine.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngine.java deleted file mode 100644 index 55f2c1d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngine.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating; - -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.TemplateEngine; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.Map; -import org.springframework.stereotype.Component; - -@Component("freeMarkerTemplateEngine") -public class FreeMarkerTemplateEngine implements TemplateEngine { - - private final Configuration freemarkerTemplateConfiguration; - - public FreeMarkerTemplateEngine(Configuration freemarkerTemplateConfiguration) { - this.freemarkerTemplateConfiguration = freemarkerTemplateConfiguration; - } - - @Override - public void generateFileFromTemplate( - TemplateType templateType, Map data, File destination) throws IOException { - - String templateFileName = templateType.getTemplateFileName(); - String fileName = templateType.getFileName(); - - generateFileFromTemplate(templateFileName, fileName, data, destination); - } - - public void generateFileFromTemplate( - String templateFileName, String fileName, Map data, File destination) - throws IOException { - Template template = freemarkerTemplateConfiguration.getTemplate(templateFileName); - - if (!destination.exists()) { - destination.mkdirs(); - } - - try (Writer writer = new FileWriter(new File(destination, fileName))) { - template.process(data, writer); - } catch (TemplateException e) { - throw new IOException("Error processing template: " + e.getMessage(), e); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfiguration.java deleted file mode 100644 index fa79066..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfiguration.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.configuration; - -import freemarker.template.TemplateExceptionHandler; -import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.FreeMarkerTemplateProperties; -import java.io.Serial; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(FreeMarkerTemplateProperties.class) -public class FreeMarkerTemplateConfiguration { - - private final FreeMarkerTemplateProperties freeMarkerProperties; - - public FreeMarkerTemplateConfiguration(FreeMarkerTemplateProperties freeMarkerProperties) { - this.freeMarkerProperties = freeMarkerProperties; - } - - @Bean - freemarker.template.Configuration freemarkerTemplateConfiguration() - throws FreeMarkerConfigurationException { - return initializeFreeMarkerTemplateConfiguration(); - } - - public freemarker.template.Configuration initializeFreeMarkerTemplateConfiguration() { - freemarker.template.Configuration configuration = - new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_32); - configuration.setDefaultEncoding(freeMarkerProperties.encoding()); - setTemplateExceptionHandler(configuration); - configuration.setClassForTemplateLoading(this.getClass(), freeMarkerProperties.templatePath()); - return configuration; - } - - private void setTemplateExceptionHandler(freemarker.template.Configuration configuration) { - try { - TemplateExceptionHandler exceptionHandler = - switch (freeMarkerProperties.templateExceptionHandler()) { - case "RETHROW_HANDLER" -> TemplateExceptionHandler.RETHROW_HANDLER; - case "DEBUG_HANDLER" -> TemplateExceptionHandler.DEBUG_HANDLER; - case "HTML_DEBUG_HANDLER" -> TemplateExceptionHandler.HTML_DEBUG_HANDLER; - case "IGNORE_HANDLER" -> TemplateExceptionHandler.IGNORE_HANDLER; - default -> - throw new IllegalArgumentException( - "Invalid exception handler name: " - + freeMarkerProperties.templateExceptionHandler()); - }; - configuration.setTemplateExceptionHandler(exceptionHandler); - } catch (IllegalArgumentException e) { - throw new FreeMarkerConfigurationException( - "Invalid templateExceptionHandler value: " - + freeMarkerProperties.templateExceptionHandler(), - e); - } - } -} - -class FreeMarkerConfigurationException extends RuntimeException { - - @Serial private static final long serialVersionUID = 4482627787641879716L; - - public FreeMarkerConfigurationException(String message, Exception e) { - super(message, e); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/SpringBootMavenJavaProjectConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/SpringBootMavenJavaProjectConfiguration.java deleted file mode 100644 index 6c20a3a..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/SpringBootMavenJavaProjectConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.configuration; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.maven.MavenPlugin; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SpringBootMavenJavaProjectConfiguration { - - @Bean - List springBootMavenJavaPlugins() { - List springBootMavenPlugins = new ArrayList<>(); - - MavenPlugin springBootMavenPlugin = - new MavenPlugin.MavenPluginBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-maven-plugin") - .build(); - - MavenPlugin mavenCompilerPlugin = - new MavenPlugin.MavenPluginBuilder() - .groupId("org.apache.maven.plugins") - .artifactId("maven-compiler-plugin") - .addConfigurationElement("generatedSourcesDirectory", "src/gen/java") - .addConfigurationElement("compileSourceRoots", List.of("src/main/java", "src/gen/java")) - .build(); - - springBootMavenPlugins.add(springBootMavenPlugin); - springBootMavenPlugins.add(mavenCompilerPlugin); - - return Collections.unmodifiableList(springBootMavenPlugins); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/FreeMarkerTemplateProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/FreeMarkerTemplateProperties.java deleted file mode 100644 index 0e625d9..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/FreeMarkerTemplateProperties.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "freemarker") -public record FreeMarkerTemplateProperties( - String encoding, String templateExceptionHandler, String templatePath) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/MavenJavaSourceFolderProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/MavenJavaSourceFolderProperties.java deleted file mode 100644 index cccaffd..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/properties/MavenJavaSourceFolderProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties; - -import java.util.List; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "maven.source-folder") -public record MavenJavaSourceFolderProperties( - String srcMainJava, - String srcMainResources, - String srcTestJava, - String srcTestResources, - String srcGenJava) { - - public List getSourceFolders() { - return List.of(srcMainJava, srcMainResources, srcTestJava, srcTestResources, srcGenJava); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/ProjectGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/ProjectGenerator.java deleted file mode 100644 index c528b51..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/ProjectGenerator.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.generator; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.IOException; -import java.nio.file.Path; - -public interface ProjectGenerator { - - /** - * Generates a project based on the provided project metadata. This method delegates the specific - * generation tasks to appropriate collaborators (ports) based on the project type. - * - * @param projectMetadata The project metadata object containing information about the desired - * project type. - * @throws IOException If an error occurs during project generation. - */ - Path generateProject(ProjectMetadata projectMetadata) throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/base/AbstractProjectGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/base/AbstractProjectGenerator.java deleted file mode 100644 index 15e6fc3..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/base/AbstractProjectGenerator.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.generator.base; - -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ApplicationYamlGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkProjectStarterClassGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkSpecificTestUnitGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.GitIgnoreGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectArchiver; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectBuildGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDirectoryInitializer; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDocumentationGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectLayoutGenerator; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; - -public abstract class AbstractProjectGenerator implements ProjectGenerator { - - private final ProjectDirectoryInitializer projectDirectoryInitializer; - private final GitIgnoreGenerator gitIgnoreGenerator; - private final ProjectArchiver projectArchiver; - private final ProjectLayoutGenerator projectLayoutGenerator; - private final ProjectBuildGenerator projectBuildGenerator; - private final ApplicationYamlGenerator applicationYamlGenerator; - private final FrameworkProjectStarterClassGenerator frameworkProjectStarterClassGenerator; - private final FrameworkSpecificTestUnitGenerator frameworkSpecificTestUnitGenerator; - private final ProjectDocumentationGenerator projectDocumentationGenerator; - - protected AbstractProjectGenerator( - ProjectDirectoryInitializer projectDirectoryInitializer, - GitIgnoreGenerator gitIgnoreGenerator, - ProjectArchiver projectArchiver, - ProjectLayoutGenerator projectLayoutGenerator, - ProjectBuildGenerator projectBuildGenerator, - ApplicationYamlGenerator applicationYamlGenerator, - FrameworkProjectStarterClassGenerator frameworkProjectStarterClassGenerator, - FrameworkSpecificTestUnitGenerator frameworkSpecificTestUnitGenerator, - ProjectDocumentationGenerator projectDocumentationGenerator) { - this.projectDirectoryInitializer = projectDirectoryInitializer; - this.gitIgnoreGenerator = gitIgnoreGenerator; - this.projectArchiver = projectArchiver; - this.projectLayoutGenerator = projectLayoutGenerator; - this.projectBuildGenerator = projectBuildGenerator; - this.applicationYamlGenerator = applicationYamlGenerator; - this.frameworkProjectStarterClassGenerator = frameworkProjectStarterClassGenerator; - this.frameworkSpecificTestUnitGenerator = frameworkSpecificTestUnitGenerator; - this.projectDocumentationGenerator = projectDocumentationGenerator; - } - - @Override - public final Path generateProject(ProjectMetadata projectMetadata) throws IOException { - Path projectDestinationPath = initializeProjectDirectory(projectMetadata); - File projectDestination = projectDestinationPath.toFile(); - - generateGitIgnoreContent(projectDestination); - generateProjectLayout(projectDestination, projectMetadata); - generateBuildConfiguration(projectDestination, projectMetadata); - generateApplicationProperties(projectDestination, projectMetadata); - generateProjectStarterClass(projectDestination, projectMetadata); - generateTestClass(projectDestination, projectMetadata); - generateProjectDocument(projectDestination, projectMetadata); - return archiveProject(projectDestination, projectMetadata); - } - - protected Path initializeProjectDirectory(ProjectMetadata projectMetadata) throws IOException { - if (projectMetadata.getProjectLocation() != null) { - return projectDirectoryInitializer.initializeProjectDirectory( - projectMetadata.getArtifactId(), projectMetadata.getProjectLocation()); - } else { - return projectDirectoryInitializer.initializeProjectDirectory( - projectMetadata.getArtifactId()); - } - } - - protected void generateGitIgnoreContent(File projectDestination) throws IOException { - List ignoreList = Collections.emptyList(); - gitIgnoreGenerator.generateGitIgnoreContent(projectDestination, ignoreList); - } - - protected void generateProjectLayout(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - projectLayoutGenerator.generateProjectLayout(projectDestination, projectMetadata); - } - - protected void generateBuildConfiguration( - File projectDestination, ProjectMetadata projectMetadata) throws IOException { - projectBuildGenerator.generateBuildConfiguration(projectDestination, projectMetadata); - } - - protected void generateApplicationProperties( - File projectDestination, ProjectMetadata projectMetadata) throws IOException { - applicationYamlGenerator.generateApplicationYaml(projectDestination, projectMetadata); - } - - protected void generateProjectStarterClass( - File projectDestination, ProjectMetadata projectMetadata) throws IOException { - frameworkProjectStarterClassGenerator.generateProjectStarterClass( - projectDestination, projectMetadata); - } - - protected void generateTestClass(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - frameworkSpecificTestUnitGenerator.generateTestClass(projectDestination, projectMetadata); - } - - protected void generateProjectDocument(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - projectDocumentationGenerator.generateProjectDocument(projectDestination, projectMetadata); - } - - protected Path archiveProject(File projectDestination, ProjectMetadata projectMetadata) - throws IOException { - return projectArchiver.archiveProject(projectDestination, projectMetadata.getName()); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/springboot/maven/SpringBootMavenJavaProjectGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/springboot/maven/SpringBootMavenJavaProjectGenerator.java deleted file mode 100644 index 41a2022..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/generator/springboot/maven/SpringBootMavenJavaProjectGenerator.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.generator.springboot.maven; - -import io.github.bsayli.codegen.initializr.projectgeneration.generator.base.AbstractProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ApplicationYamlGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkProjectStarterClassGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.FrameworkSpecificTestUnitGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.GitIgnoreGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectArchiver; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectBuildGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDirectoryInitializer; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectDocumentationGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.ports.ProjectLayoutGenerator; -import org.springframework.stereotype.Component; - -@Component("springBootMavenJavaProjectGenerator") -public class SpringBootMavenJavaProjectGenerator extends AbstractProjectGenerator { - - public SpringBootMavenJavaProjectGenerator( - ProjectDirectoryInitializer projectRootDirectoryInitializer, - GitIgnoreGenerator gitIgnoreFileGenerator, - ProjectArchiver projectZipArchiver, - ProjectLayoutGenerator mavenJavaProjectLayoutGenerator, - ProjectBuildGenerator springBootMavenJavaProjectBuildGenerator, - ApplicationYamlGenerator springBootApplicationYamlGenerator, - FrameworkProjectStarterClassGenerator springBootJavaMainClassGenerator, - FrameworkSpecificTestUnitGenerator springBootJavaTestClassGenerator, - ProjectDocumentationGenerator springBootMavenJavaReadMeGenerator) { - super( - projectRootDirectoryInitializer, - gitIgnoreFileGenerator, - projectZipArchiver, - mavenJavaProjectLayoutGenerator, - springBootMavenJavaProjectBuildGenerator, - springBootApplicationYamlGenerator, - springBootJavaMainClassGenerator, - springBootJavaTestClassGenerator, - springBootMavenJavaReadMeGenerator); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/Dependency.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/Dependency.java deleted file mode 100644 index 2327d8d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/Dependency.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model; - -public class Dependency { - private final String groupId; - private final String artifactId; - private final String version; - private final String scope; - - private Dependency(DependencyBuilder builder) { - this.groupId = builder.groupId; - this.artifactId = builder.artifactId; - this.version = builder.version; - this.scope = builder.scope; - } - - public String getGroupId() { - return groupId; - } - - public String getArtifactId() { - return artifactId; - } - - public String getVersion() { - return version; - } - - public String getScope() { - return scope; - } - - @Override - public String toString() { - return toShortDefinition(); - } - - public String toShortDefinition() { - return "Dependency [artifactId=" + artifactId + "]"; - } - - public String toLongDefinition() { - return "Dependency [groupId=" - + groupId - + ", artifactId=" - + artifactId - + ", version=" - + version - + ", scope=" - + scope - + "]"; - } - - public static class DependencyBuilder { - - private String groupId; - private String artifactId; - private String version; - private String scope; - - public DependencyBuilder groupId(String groupId) { - this.groupId = groupId; - return this; - } - - public DependencyBuilder artifactId(String artifactId) { - this.artifactId = artifactId; - return this; - } - - public DependencyBuilder version(String version) { - this.version = version; - return this; - } - - public DependencyBuilder scope(String scope) { - this.scope = scope; - return this; - } - - public Dependency build() { - if (groupId == null || artifactId == null) { - throw new IllegalStateException("groupId and artifactId are required fields"); - } - return new Dependency(this); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectMetadata.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectMetadata.java deleted file mode 100644 index d09c2fa..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectMetadata.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model; - -import java.nio.file.Path; -import java.util.List; - -public class ProjectMetadata { - - private final String name; - private final String description; - private final String groupId; - private final String artifactId; - private final String packageName; - private final List dependencies; - private final Path projectLocation; - - protected ProjectMetadata(ProjectMetadataBuilder builder) { - this.name = builder.name; - this.description = builder.description; - this.groupId = builder.groupId; - this.artifactId = builder.artifactId; - this.packageName = builder.packageName; - this.dependencies = builder.dependencies; - this.projectLocation = builder.projectLocation; - } - - public String getName() { - return name; - } - - public String getGroupId() { - return groupId; - } - - public String getArtifactId() { - return artifactId; - } - - public String getPackageName() { - return packageName; - } - - public String getDescription() { - return description; - } - - public List getDependencies() { - return dependencies; - } - - public Path getProjectLocation() { - return projectLocation; - } - - public static class ProjectMetadataBuilder { - - private String name; - private String description; - private String groupId; - private String artifactId; - private String packageName; - private List dependencies; - private Path projectLocation; - - public ProjectMetadataBuilder name(String name) { - this.name = name; - return this; - } - - public ProjectMetadataBuilder description(String description) { - this.description = description; - return this; - } - - public ProjectMetadataBuilder groupId(String groupId) { - this.groupId = groupId; - return this; - } - - public ProjectMetadataBuilder artifactId(String artifactId) { - this.artifactId = artifactId; - return this; - } - - public ProjectMetadataBuilder packageName(String packageName) { - this.packageName = packageName; - return this; - } - - public ProjectMetadataBuilder dependencies(List dependencies) { - this.dependencies = dependencies; - return this; - } - - public ProjectMetadataBuilder projectLocation(Path projectLocation) { - this.projectLocation = projectLocation; - return this; - } - - public ProjectMetadata build() { - return new ProjectMetadata(this); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java deleted file mode 100644 index 3740414..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/ProjectType.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; - -public record ProjectType(Framework framework, BuildTool buildTool, Language language) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPlugin.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPlugin.java deleted file mode 100644 index 0299129..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPlugin.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.maven; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class MavenPlugin { - - private final String groupId; - private final String artifactId; - private final String version; - private final Map configuration; - - private MavenPlugin(MavenPluginBuilder builder) { - this.groupId = builder.groupId; - this.artifactId = builder.artifactId; - this.version = builder.version; - this.configuration = builder.configuration; - } - - public String getGroupId() { - return groupId; - } - - public String getArtifactId() { - return artifactId; - } - - public String getVersion() { - return version; - } - - public Map getConfiguration() { - return configuration; - } - - public static class MavenPluginBuilder { - private final Map configuration = new LinkedHashMap<>(); - private String groupId; - private String artifactId; - private String version; - - public MavenPluginBuilder groupId(String groupId) { - this.groupId = groupId; - return this; - } - - public MavenPluginBuilder artifactId(String artifactId) { - this.artifactId = artifactId; - return this; - } - - public MavenPluginBuilder version(String version) { - this.version = version; - return this; - } - - public MavenPluginBuilder addConfigurationElement(String key, Object value) { - configuration.put(key, value); - return this; - } - - public MavenPlugin build() { - if (groupId == null || artifactId == null) { - throw new IllegalStateException("groupId, artifactId are required fields"); - } - return new MavenPlugin(this); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPom.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPom.java deleted file mode 100644 index facc982..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/maven/MavenPom.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.maven; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.util.ArrayList; -import java.util.List; - -public class MavenPom { - - private final String modelVersion; - private final String version; - private final ProjectMetadata projectMetadata; - private final List dependencies; - private final List plugins; - - private MavenPom(MavenPomBuilder builder) { - this.modelVersion = builder.modelVersion; - this.version = builder.version; - this.projectMetadata = builder.projectMetadata; - this.dependencies = builder.dependencies; - this.plugins = builder.plugins; - } - - public String getModelVersion() { - return modelVersion; - } - - public String getVersion() { - return version; - } - - public ProjectMetadata getProjectMetadata() { - return projectMetadata; - } - - public List getDependencies() { - return dependencies; - } - - public List getPlugins() { - return plugins; - } - - public static class MavenPomBuilder { - private String modelVersion; - private String version; - private ProjectMetadata projectMetadata; - private List dependencies = new ArrayList<>(); - private List plugins = new ArrayList<>(); - - public MavenPomBuilder modelVersion(String modelVersion) { - this.modelVersion = modelVersion; - return this; - } - - public MavenPomBuilder version(String version) { - this.version = version; - return this; - } - - public MavenPomBuilder projectMetadata(ProjectMetadata projectMetadata) { - this.projectMetadata = projectMetadata; - return this; - } - - public MavenPomBuilder addDependencies(List dependencies) { - this.dependencies = new ArrayList<>(dependencies); - return this; - } - - public MavenPomBuilder addPlugins(List plugins) { - this.plugins = new ArrayList<>(plugins); - return this; - } - - public MavenPom build() { - return new MavenPom(this); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/spring/SpringBootJavaProjectMetadata.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/spring/SpringBootJavaProjectMetadata.java deleted file mode 100644 index 29f5cb4..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/spring/SpringBootJavaProjectMetadata.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.spring; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; - -public class SpringBootJavaProjectMetadata extends ProjectMetadata { - - private String springBootVersion; - private String javaVersion; - - protected SpringBootJavaProjectMetadata( - ProjectMetadataBuilder builder, String springBootVersion, String javaVersion) { - super(builder); - this.springBootVersion = springBootVersion; - this.javaVersion = javaVersion; - } - - public String getSpringBootVersion() { - return springBootVersion; - } - - public void setSpringBootVersion(String springBootVersion) { - this.springBootVersion = springBootVersion; - } - - public String getJavaVersion() { - return javaVersion; - } - - public void setJavaVersion(String javaVersion) { - this.javaVersion = javaVersion; - } - - public static class SpringBootJavaProjectMetadataBuilder extends ProjectMetadataBuilder { - private String springBootVersion; - private String javaVersion; - - public SpringBootJavaProjectMetadataBuilder springBootVersion(String springBootVersion) { - this.springBootVersion = springBootVersion; - return this; - } - - public SpringBootJavaProjectMetadataBuilder javaVersion(String javaVersion) { - this.javaVersion = javaVersion; - return this; - } - - @Override - public SpringBootJavaProjectMetadata build() { - return new SpringBootJavaProjectMetadata(this, springBootVersion, javaVersion); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/templating/TemplateType.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/templating/TemplateType.java deleted file mode 100644 index 54b973d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/model/templating/TemplateType.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.model.templating; - -public enum TemplateType { - GITIGNORE(".gitignore", "gitignore.ftl"), - SPRING_BOOT_JAVA_POM("pom.xml", "springBootJavaPom.xml.ftl"), - SPRING_BOOT_MAVEN_JAVA_README("README.md", "springBootMavenJavaReadMe.ftl"), - MAVEN_WRAPPER("maven-wrapper.properties", "maven-wrapper.properties.ftl"), - SPRING_BOOT_APPLICATION_YAML("application.yml", "springBootApplication.yml.ftl"); - - private final String fileName; - private final String templateFileName; - - TemplateType(String fileName, String templateFileName) { - this.fileName = fileName; - this.templateFileName = templateFileName; - } - - public String getFileName() { - return fileName; - } - - public String getTemplateFileName() { - return templateFileName; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/naming/NameConverter.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/naming/NameConverter.java deleted file mode 100644 index d70e1a2..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/naming/NameConverter.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.naming; - -import org.springframework.stereotype.Component; - -@Component -public class NameConverter { - - /** - * Converts a raw project name into Java-friendly PascalCase. Examples: "codegen-demo" -> - * "CodegenDemo" "my_service.core" -> "MyServiceCore" "123-metric" -> "App123Metric" - */ - public String toPascalCase(String raw) { - if (raw == null || raw.isBlank()) return ""; - String[] parts = raw.split("[^A-Za-z0-9]+"); - StringBuilder sb = new StringBuilder(); - for (String p : parts) { - if (p.isBlank()) continue; - String lower = p.toLowerCase(); - sb.append(Character.toUpperCase(lower.charAt(0))); - if (lower.length() > 1) { - sb.append(lower.substring(1)); - } - } - String result = sb.toString(); - if (!result.isEmpty() && Character.isDigit(result.charAt(0))) { - result = "App" + result; - } - return result; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ApplicationYamlGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ApplicationYamlGenerator.java deleted file mode 100644 index 5a0b9e9..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ApplicationYamlGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface ApplicationYamlGenerator { - - void generateApplicationYaml(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkProjectStarterClassGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkProjectStarterClassGenerator.java deleted file mode 100644 index 7494230..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkProjectStarterClassGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface FrameworkProjectStarterClassGenerator { - - void generateProjectStarterClass(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkSpecificTestUnitGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkSpecificTestUnitGenerator.java deleted file mode 100644 index d1fe0e2..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/FrameworkSpecificTestUnitGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface FrameworkSpecificTestUnitGenerator { - - void generateTestClass(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/GitIgnoreGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/GitIgnoreGenerator.java deleted file mode 100644 index 44a5906..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/GitIgnoreGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -public interface GitIgnoreGenerator { - - void generateGitIgnoreContent(File projectDestination, List ignoreList) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectArchiver.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectArchiver.java deleted file mode 100644 index 92c679d..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectArchiver.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; - -public interface ProjectArchiver { - - Path archiveProject(File projectDestination, String projectName) throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildGenerator.java deleted file mode 100644 index c9c5ada..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface ProjectBuildGenerator { - - void generateBuildConfiguration(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildWrapperGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildWrapperGenerator.java deleted file mode 100644 index 3e470c1..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectBuildWrapperGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface ProjectBuildWrapperGenerator { - - void generateBuildWrapper(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDirectoryInitializer.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDirectoryInitializer.java deleted file mode 100644 index bf3df58..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDirectoryInitializer.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import java.io.IOException; -import java.nio.file.Path; - -public interface ProjectDirectoryInitializer { - - Path initializeProjectDirectory(String projectName) throws IOException; - - Path initializeProjectDirectory(String projectName, Path projectLocation) throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDocumentationGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDocumentationGenerator.java deleted file mode 100644 index 3de2d4e..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectDocumentationGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface ProjectDocumentationGenerator { - - void generateProjectDocument(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectLayoutGenerator.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectLayoutGenerator.java deleted file mode 100644 index 5262192..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/ProjectLayoutGenerator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; - -public interface ProjectLayoutGenerator { - - void generateProjectLayout(File projectDestination, ProjectMetadata projectMetadata) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/TemplateEngine.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/TemplateEngine.java deleted file mode 100644 index 29b7b8b..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/ports/TemplateEngine.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.ports; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import java.io.File; -import java.io.IOException; -import java.util.Map; - -public interface TemplateEngine { - - void generateFileFromTemplate( - TemplateType templateType, Map data, File destination) throws IOException; - - void generateFileFromTemplate( - String templateFileName, String fileName, Map data, File destination) - throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/ProjectGeneratorRegistry.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/ProjectGeneratorRegistry.java deleted file mode 100644 index 8f57415..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/ProjectGeneratorRegistry.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.registry; - -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import java.util.Optional; - -public interface ProjectGeneratorRegistry { - - Optional getProjectGenerator(ProjectType projectType); -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistry.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistry.java deleted file mode 100644 index 3e8527f..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistry.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.registry; - -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import java.util.Map; -import java.util.Optional; -import org.springframework.stereotype.Component; - -@Component -public class SimpleProjectGeneratorRegistry implements ProjectGeneratorRegistry { - - private final Map registeredProjectGenerators; - - public SimpleProjectGeneratorRegistry( - Map registeredProjectGenerators) { - this.registeredProjectGenerators = registeredProjectGenerators; - } - - @Override - public Optional getProjectGenerator(ProjectType projectType) { - return Optional.ofNullable(registeredProjectGenerators.get(projectType)); - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java deleted file mode 100644 index 350153c..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/configuration/ProjectGeneratorRegistryConfiguration.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.registry.configuration; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import java.util.HashMap; -import java.util.Map; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class ProjectGeneratorRegistryConfiguration { - - private final ProjectGenerator springBootMavenJavaProjectGenerator; - - public ProjectGeneratorRegistryConfiguration( - ProjectGenerator springBootMavenJavaProjectGenerator) { - this.springBootMavenJavaProjectGenerator = springBootMavenJavaProjectGenerator; - } - - @Bean - Map registeredProjectGenerators() { - Map generatorFactories = new HashMap<>(); - - ProjectType springBootMavenJavaProjectType = - new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); - - generatorFactories.put(springBootMavenJavaProjectType, springBootMavenJavaProjectGenerator); - - return generatorFactories; - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationService.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationService.java deleted file mode 100644 index eee7d9a..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationService.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.service; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import java.io.IOException; -import java.nio.file.Path; - -public interface ProjectGenerationService { - - Path generateProject(ProjectType projectType, ProjectMetadata projectMetadata) throws IOException; -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImpl.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImpl.java deleted file mode 100644 index d9256ed..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.service; - -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.registry.ProjectGeneratorRegistry; -import io.github.bsayli.codegen.initializr.projectgeneration.service.exception.ProjectGenerationException; -import java.io.IOException; -import java.nio.file.Path; -import org.springframework.stereotype.Service; - -@Service -public class ProjectGenerationServiceImpl implements ProjectGenerationService { - - private final ProjectGeneratorRegistry registry; - - public ProjectGenerationServiceImpl(ProjectGeneratorRegistry registry) { - this.registry = registry; - } - - @Override - public Path generateProject(ProjectType projectType, ProjectMetadata projectMetadata) { - ProjectGenerator projectGenerator = - registry - .getProjectGenerator(projectType) - .orElseThrow( - () -> new IllegalArgumentException("Unsupported project type: " + projectType)); - try { - return projectGenerator.generateProject(projectMetadata); - } catch (IOException e) { - throw new ProjectGenerationException("Error generating project: " + e.getMessage(), e); - } - } -} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/exception/ProjectGenerationException.java b/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/exception/ProjectGenerationException.java deleted file mode 100644 index 4808e8a..0000000 --- a/src/main/java/io/github/bsayli/codegen/initializr/projectgeneration/service/exception/ProjectGenerationException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.service.exception; - -public class ProjectGenerationException extends RuntimeException { - - public ProjectGenerationException(String message) { - super(message); - } - - public ProjectGenerationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e44f8bd..c4adca3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -58,17 +58,3 @@ templating: template-path: /templates cache-enabled: true cache-update-delay-ms: 60000 - - -freemarker: - encoding: UTF-8 - template-exception-handler: RETHROW_HANDLER - template-path: /templates - -maven: - source-folder: - src-main-java: src/main/java - src-main-resources: src/main/resources - src-test-java: src/test/java - src-test-resources: src/test/resources - src-gen-java: src/gen/java \ No newline at end of file diff --git a/src/main/resources/templates/gitignore.ftl b/src/main/resources/templates/gitignore.ftl deleted file mode 100644 index 5d94f6b..0000000 --- a/src/main/resources/templates/gitignore.ftl +++ /dev/null @@ -1,76 +0,0 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Eclipse / IntelliJ / VSCode IDE # -################################## -.classpath -.project -.settings/ -.idea/ -*.iws -*.iml -*.ipr - -# Build directories # -##################### -target/ -build/ -bin/ - -# Maven specific # -################## -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -pom.xml.bak -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/ - -# Generated source folders # -############################ -generated-sources/ -generated-classes/ - -# Add placeholders for project-specific ignores (optional) -<#if ignoreList?has_content && ignoreList?size gt 0> - <#list ignoreList as pattern> - ${pattern} - - \ No newline at end of file diff --git a/src/main/resources/templates/maven-wrapper.properties.ftl b/src/main/resources/templates/maven-wrapper.properties.ftl deleted file mode 100644 index ad0b9fc..0000000 --- a/src/main/resources/templates/maven-wrapper.properties.ftl +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# Maven Wrapper core version (kept for visibility & tests) -wrapperVersion=${wrapperVersion} - -# Maven distribution to be used by the wrapper -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${mavenVersion}/apache-maven-${mavenVersion}-bin.zip -<#-- Optional checksum for Maven distribution --> -<#if distributionSha256Sum?? && distributionSha256Sum?has_content> - distributionSha256Sum=${distributionSha256Sum} - - -# Maven Wrapper JAR location (aligns with wrapperVersion above) -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/${wrapperVersion}/maven-wrapper-${wrapperVersion}.jar -<#-- Optional checksum for Wrapper jar --> -<#if wrapperSha256Sum?? && wrapperSha256Sum?has_content> - wrapperSha256Sum=${wrapperSha256Sum} - \ No newline at end of file diff --git a/src/main/resources/templates/springBootApplication.yml.ftl b/src/main/resources/templates/springBootApplication.yml.ftl deleted file mode 100644 index 3525bb9..0000000 --- a/src/main/resources/templates/springBootApplication.yml.ftl +++ /dev/null @@ -1,8 +0,0 @@ -spring: -application: -name: ${projectName} -# server: -# port: 8080 -# logging: -# level: -# root: INFO \ No newline at end of file diff --git a/src/main/resources/templates/springBootJavaPom.xml.ftl b/src/main/resources/templates/springBootJavaPom.xml.ftl deleted file mode 100644 index 4eef2c8..0000000 --- a/src/main/resources/templates/springBootJavaPom.xml.ftl +++ /dev/null @@ -1,67 +0,0 @@ - - - - ${pom.modelVersion} - - - org.springframework.boot - spring-boot-starter-parent - ${pom.projectMetadata.springBootVersion} - - - - ${pom.projectMetadata.groupId} - ${pom.projectMetadata.artifactId} - ${pom.version} - - - ${pom.projectMetadata.javaVersion} - - - - <#list pom.dependencies as dependency> - - ${dependency.groupId} - ${dependency.artifactId} - <#if dependency.version??> - ${dependency.version} - - <#if dependency.scope??> - ${dependency.scope} - - - - - - - - <#list pom.plugins as plugin> - - ${plugin.groupId} - ${plugin.artifactId} - <#if plugin.version??> - ${plugin.version} - - <#if plugin.configuration?has_content && plugin.configuration?size gt 0> - - <#list plugin.configuration! {} as key, value> - <#if key != 'compileSourceRoots'> - <${key}>${value} - - - <#if plugin.configuration.compileSourceRoots?has_content && plugin.configuration.compileSourceRoots?size gt 0> - - <#list plugin.configuration.compileSourceRoots! {} as sourceDirectory> - ${sourceDirectory} - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/springBootMainClass.java.ftl b/src/main/resources/templates/springBootMainClass.java.ftl deleted file mode 100644 index 4b7565b..0000000 --- a/src/main/resources/templates/springBootMainClass.java.ftl +++ /dev/null @@ -1,13 +0,0 @@ -package ${projectPackageName}; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class ${className} { - -public static void main(String[] args) { -SpringApplication.run(${className}.class, args); -} - -} diff --git a/src/main/resources/templates/springBootMavenJavaReadMe.ftl b/src/main/resources/templates/springBootMavenJavaReadMe.ftl deleted file mode 100644 index ff10dfb..0000000 --- a/src/main/resources/templates/springBootMavenJavaReadMe.ftl +++ /dev/null @@ -1,53 +0,0 @@ -Project Initialization - -Extract the downloaded archive: Use a tool like WinZip, 7-Zip, or the unzip command to extract the downloaded archive file (e.g., ${projectName}.zip). - -Navigate to the project directory: Open your terminal or command prompt and navigate to the extracted project directory using the cd command (e.g., cd ${projectName}). - -Running the project: - -Option 1: With Maven (Recommended): - -If you already have Maven installed, you can directly use standard Maven commands like mvn package or mvn test to build and potentially run the project (refer to the project documentation for specific commands on how to run the application). - -Option 2: Without Maven: - -Pre-built Version (if available): The project might offer pre-built versions that include the mvnw and mvnw.cmd scripts for running the project without requiring Maven installation. Check the project website or documentation for download instructions for a pre-built version (if available). - -Build Scripts and Run the Project (if source code available): - -1. Download a minimal Apache Maven version from the official website: https://maven.apache.org/download.cgi -2. Extract the downloaded Maven archive into a temporary directory. -3. Open a terminal window (command prompt on Windows). -4. Navigate to the extracted project's root directory using the cd command. -5. (Optional) Run mvn package to see the build process and downloaded dependencies. -6. Generate the wrapper scripts using a specific Maven command (check project documentation for the exact command, a common example might be: mvn wrapper:wrapper). -7. After running the script generation command, check the project's root directory for the newly generated scripts: mvnw (Linux/macOS) and mvnw.cmd (Windows). -8. Run the Project: -- Linux/macOS: With the mvnw script generated, execute the following command in the terminal (assuming the script is executable): ./mvnw - - - Windows: With the mvnw.cmd script generated, double-click the script or run it from the command prompt: mvnw.cmd - - - Replace - with the desired action you want to perform. Here are some common examples: - - ./mvnw package: Builds the project and creates a package (JAR file). - ./mvnw test: Runs unit tests for the project. - - Dependencies - - This project uses the following dependencies based on your selections during generation: - - (List generated dependencies here. You can access this information from the project's pom.xml file) - Project Structure - - pom.xml: This file defines the project's metadata (groupId, artifactId, version) and dependencies. - wrapper/: This directory stores the configuration for the Maven Wrapper. - wrapper.conf: Configuration file for the wrapper executable. - src/main/java/: Source code directory for your application's Java classes. - src/main/resources/: Configuration files and resources used by your application. - src/test/java/: Source code directory for your application's unit tests (optional). - src/gen/java/: Generated code directory for the Codegen's Java classes. - - Additional Notes \ No newline at end of file diff --git a/src/main/resources/templates/springBootTestClass.java.ftl b/src/main/resources/templates/springBootTestClass.java.ftl deleted file mode 100644 index dcc1b84..0000000 --- a/src/main/resources/templates/springBootTestClass.java.ftl +++ /dev/null @@ -1,14 +0,0 @@ -package ${projectPackageName}; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ${className} { - -@Test -void contextLoads() { -} - -} - diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGeneratorTest.java deleted file mode 100644 index e644e8d..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/GitIgnoreFileGeneratorTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; - -import io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating.FreeMarkerTemplateEngine; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class GitIgnoreFileGeneratorTest { - - private static final String GITIGNORE_FILE_NAME = ".gitignore"; - - @Autowired private GitIgnoreFileGenerator gitIgnoreFileGenerator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateGitIgnoreContent_CreatesCorrectFileWithEmptyIgnoreList() throws IOException { - gitIgnoreFileGenerator.generateGitIgnoreContent(tempFolder.toFile(), Collections.emptyList()); - - Path generatedFile = tempFolder.resolve(GITIGNORE_FILE_NAME); - assertTrue(Files.exists(generatedFile)); - String content = Files.readString(generatedFile); - assertTrue(content.length() > 10); - } - - @Test - void testGenerateGitIgnoreContent_CreatesCorrectFileWithSomeIgnoreList() throws IOException { - List ignoreList = Arrays.asList("*.pyc", "__pycache__"); - gitIgnoreFileGenerator.generateGitIgnoreContent(tempFolder.toFile(), ignoreList); - - Path generatedFile = tempFolder.resolve(GITIGNORE_FILE_NAME); - assertTrue(Files.exists(generatedFile)); - String content = Files.readString(generatedFile); - assertTrue(content.contains("*.py")); - assertTrue(content.contains("__pycache__")); - } - - @Test - void testGenerateGitIgnoreContent_CreatesFileAndVerifiesContent() throws IOException { - File projectDestination = tempFolder.toFile(); - - List additionalIgnorePatterns = List.of("*.md"); - gitIgnoreFileGenerator.generateGitIgnoreContent(projectDestination, additionalIgnorePatterns); - - File generatedFile = new File(projectDestination, GITIGNORE_FILE_NAME); - assertTrue(generatedFile.exists(), "Generated file should be created!"); - - List gitIgnoreList = - Files.readAllLines(generatedFile.toPath()).stream() - .map(String::trim) - .filter(s -> !s.isBlank()) - .toList(); - - assertThat(gitIgnoreList, hasItems("*.com", "*.md", ".idea/", "target/", "generated-sources/")); - } - - @Test - void testGenerateGitIgnoreContent_ThrowsExceptionOnTemplateEngineError() throws Exception { - FreeMarkerTemplateEngine mockEngine = Mockito.mock(FreeMarkerTemplateEngine.class); - GitIgnoreFileGenerator gitIgnoreFileGeneratorLocal = new GitIgnoreFileGenerator(mockEngine); - - Mockito.doThrow(new IOException("Template processing error")) - .when(mockEngine) - .generateFileFromTemplate(any(), any(), any()); - - assertThrows( - IOException.class, - () -> gitIgnoreFileGeneratorLocal.generateGitIgnoreContent(tempFolder.toFile(), null)); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializerTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializerTest.java deleted file mode 100644 index e13631e..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ProjectRootDirectoryInitializerTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ProjectRootDirectoryInitializerTest { - - @TempDir public Path tempFolder; - @Autowired private ProjectRootDirectoryInitializer initializer; - private Path projectDirectory; - - @Test - void testInitializeProjectDirectory_CreatesCorrectDirectoryWithProjectName() throws IOException { - String projectName = "springBootTest"; - - projectDirectory = initializer.initializeProjectDirectory(projectName); - - assertTrue(Files.isDirectory(projectDirectory), "Project directory should be created"); - - assertTrue( - projectDirectory.getFileName().toString().startsWith(projectName), - "Directory name should start with " + projectName); - } - - @Test - void testInitializeProjectDirectory_CreatesCorrectDirectoryWithProjectNameAndLocation() - throws IOException { - String projectName = "springBootTest"; - Path projectLocation = Path.of(tempFolder.toFile().toPath().toString() + "/resources/app"); - - Path projectFullPath = projectLocation.resolve(projectName); - - Path createdDir = initializer.initializeProjectDirectory(projectName, projectLocation); - - assertTrue(Files.isDirectory(createdDir), "Project directory should be created"); - - assertEquals(projectFullPath, createdDir, "Directory should be created at specified location"); - - String directoryName = createdDir.getFileName().toString(); - assertEquals(projectName, directoryName, "Directory name should include project name"); - } - - @Test - void testInitializeProjectDirectory_CreatesCorrectDirectoryWithNullProjectLocation() - throws IOException { - String projectName = "null-location-app"; - - projectDirectory = initializer.initializeProjectDirectory(projectName, null); - - assertTrue(Files.isDirectory(projectDirectory), "Project directory should be created"); - assertTrue( - projectDirectory.getFileName().toString().startsWith(projectName), - "Directory name should start with project name"); - } - - @Test - void testInitializeProjectDirectory_ShouldFailWhenDirectoryAlreadyExists() throws IOException { - String projectName = "codegen-demo"; - Path projectLocation = tempFolder.toFile().toPath(); - - Path projectDir = projectLocation.resolve(projectName); - Files.createDirectories(projectDir); // Create directories recursively - - try { - initializer.initializeProjectDirectory(projectName, projectLocation); - fail("Expected an exception for existing directory"); - } catch (FileAlreadyExistsException e) { - assertTrue(e.getMessage().contains("File already exists")); - } - } - - @AfterEach - void cleanup() throws IOException { - if (projectDirectory != null && Files.exists(projectDirectory)) { - FileUtils.deleteDirectory(projectDirectory.toFile()); - File parentFile = projectDirectory.getParent().toFile(); - if (parentFile.exists() && FileUtils.isEmptyDirectory(parentFile)) { - FileUtils.deleteDirectory(parentFile); - } - } - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java deleted file mode 100644 index 7dc1b27..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/ZipProjectArchiverTest.java +++ /dev/null @@ -1,196 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ZipProjectArchiverTest { - - @TempDir public Path tempFolder; - @Autowired private ZipProjectArchiver zipProjectArchiver; - - @Test - void testArchiveProject_CreatesArchiveFile() throws IOException { - String projectName = "codegen-demo"; - Path projectDir = tempFolder.resolve(projectName); - Files.createDirectories(projectDir); - - File testFile1 = tempFolder.resolve(projectDir.toString() + "/demo-01.txt").toFile(); - boolean created1 = testFile1.createNewFile(); - assertTrue(created1 || testFile1.exists(), "demo-01.txt could not be created"); - - File testFile2 = tempFolder.resolve(projectDir.toString() + "/folder-04/file-02.txt").toFile(); - File parent2 = testFile2.getParentFile(); - boolean mkParent2 = parent2.mkdirs(); - assertTrue(mkParent2 || parent2.exists(), "folder-04 could not be created"); - boolean created2 = testFile2.createNewFile(); - assertTrue(created2 || testFile2.exists(), "file-02.txt could not be created"); - - Path archivedProjectPath = zipProjectArchiver.archiveProject(projectDir.toFile(), projectName); - File archivedProjectFile = archivedProjectPath.toFile(); - assertTrue(archivedProjectFile.exists(), "Archive project file should be created"); - } - - @Test - void testArchiveProject_CreatesArchiveAndExtractsContent() throws IOException { - String projectName = "codegen-enterprise"; - Path projectDir = tempFolder.resolve(projectName); - Files.createDirectories(projectDir); - - String enterprise01FileName = "enterprise-01.txt"; - File testFile1 = - tempFolder.resolve(projectDir.toString() + "/" + enterprise01FileName).toFile(); - boolean created1 = testFile1.createNewFile(); - assertTrue(created1 || testFile1.exists(), enterprise01FileName + " could not be created"); - - File testFile2 = tempFolder.resolve(projectDir.toString() + "/folder-01/file-01.txt").toFile(); - File parent2 = testFile2.getParentFile(); - boolean mkParent2 = parent2.mkdirs(); - assertTrue(mkParent2 || parent2.exists(), "folder-01 could not be created"); - boolean created2 = testFile2.createNewFile(); - assertTrue(created2 || testFile2.exists(), "file-01.txt could not be created"); - - Path archivedProjectPath = zipProjectArchiver.archiveProject(projectDir.toFile(), projectName); - File archivedProjectFile = archivedProjectPath.toFile(); - assertTrue(archivedProjectFile.exists(), "Archive project file should be created"); - - File extractedDir = createExtractedProject(projectDir, archivedProjectFile); - assertTrue( - extractedDir.exists() && !FileUtils.isEmptyDirectory(extractedDir), - "Archived file was corrupted!"); - - String archivedProjectName = archivedProjectFile.getName().replace(".zip", ""); - File extractedProjectDir = new File(extractedDir, archivedProjectName); - String enterprise01FileNameFromArchived = "enterprise_01.txt"; - File enterprise01FileFromUnarchived = - new File(extractedProjectDir, enterprise01FileNameFromArchived); - assertTrue( - enterprise01FileFromUnarchived.exists(), - "Archived project file does not contain " + enterprise01FileNameFromArchived); - } - - @Test - void testArchiveProject_SuccessInEmptyDirectoryCreation() throws IOException { - String projectName = "codegen-demo-empty"; - Path projectDir = tempFolder.resolve(projectName); - Files.createDirectories(projectDir); - - Path archivedProjectPath = zipProjectArchiver.archiveProject(projectDir.toFile(), projectName); - File archivedProjectFile = archivedProjectPath.toFile(); - - assertTrue( - archivedProjectFile.exists(), - "Archived project file should be created even for empty directory"); - } - - @Test - void testArchiveProject_InvalidProjectFilePath_IOException() throws IOException { - String projectName = "codegen-demo"; - Path invalidProjectDir = Paths.get(tempFolder.toString(), "invalid/path"); - - try { - zipProjectArchiver.archiveProject(invalidProjectDir.toFile(), projectName); - fail("Expected an IOException for invalid project directory"); - } catch (IOException e) { - assertTrue( - e.getMessage() != null && e.getMessage().contains("No such file"), - "Unexpected exception message: " + e.getMessage()); - } - } - - @Test - void testArchiveProject_CorruptedFile_IOException() throws IOException { - String projectName = "codegen-demo"; - Path projectDir = tempFolder.resolve(projectName); - Files.createDirectories(projectDir); - - File testFile1 = tempFolder.resolve(projectDir.toString() + "/demo-01.txt").toFile(); - boolean created1 = testFile1.createNewFile(); - assertTrue(created1 || testFile1.exists(), "demo-01.txt could not be created"); - - File testFile2 = tempFolder.resolve(projectDir.toString() + "/folder-04/file-02.txt").toFile(); - File parent2 = testFile2.getParentFile(); - boolean mkParent2 = parent2.mkdirs(); - assertTrue(mkParent2 || parent2.exists(), "folder-04 could not be created"); - boolean created2 = testFile2.createNewFile(); - assertTrue(created2 || testFile2.exists(), "file-02.txt could not be created"); - - File corruptedFile = tempFolder.resolve(projectDir.toString() + "/corrupted-file.txt").toFile(); - boolean createdCorrupted = corruptedFile.createNewFile(); - assertTrue( - createdCorrupted || corruptedFile.exists(), "corrupted-file.txt could not be created"); - try (OutputStream corruptedOutputStream = new FileOutputStream(corruptedFile)) { - corruptedOutputStream.write("This is a corrupted file!".getBytes()); - corruptedOutputStream.write(new byte[] {1, 2, 3, -12}); - } - - // Try to remove permissions; accept success OR already-in-effect states. - boolean execUnset = corruptedFile.setExecutable(false, false); - assertTrue( - execUnset || !corruptedFile.canExecute(), - "Failed to unset execute permission on corrupted file"); - - boolean readUnset = corruptedFile.setReadable(false, false); - assertTrue( - readUnset || !corruptedFile.canRead(), "Failed to unset read permission on corrupted file"); - - boolean writeUnset = corruptedFile.setWritable(false, false); - assertTrue( - writeUnset || !corruptedFile.canWrite(), - "Failed to unset write permission on corrupted file"); - - try { - zipProjectArchiver.archiveProject(projectDir.toFile(), projectName); - fail("Expected an IOException for archiving corrupted file"); - } catch (IOException e) { - assertTrue( - e.getMessage() != null && e.getMessage().contains("Error processing file"), - "Unexpected exception message: " + e.getMessage()); - } - } - - private File createExtractedProject(Path projectDir, File archivedFile) throws IOException { - File extractedDir = tempFolder.resolve(projectDir.getParent() + "/unarchived").toFile(); - boolean mkExtracted = extractedDir.mkdirs(); - assertTrue(mkExtracted || extractedDir.exists(), "unarchived directory could not be created"); - - try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archivedFile))) { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - if (entry.isDirectory()) { - File directory = new File(extractedDir, entry.getName()); - boolean mk = directory.mkdirs(); - assertTrue(mk || directory.exists(), "Failed to create directory: " + directory); - } else { - File file = new File(extractedDir, entry.getName()); - File parent = file.getParentFile(); - boolean mk = parent.mkdirs(); - assertTrue(mk || parent.exists(), "Failed to create parent directory: " + parent); - - try (OutputStream outputStream = new FileOutputStream(file)) { - zis.transferTo(outputStream); - } - } - zis.closeEntry(); - } - } - - return extractedDir; - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGeneratorTest.java deleted file mode 100644 index a4c770b..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootApplicationYamlGeneratorTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; - -import io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating.FreeMarkerTemplateEngine; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBootApplicationYamlGeneratorTest { - - private static final String APPLICATION_YAML_FILE_NAME = "application.yml"; - private static final String EXPECTED_APPLICATION_NAME = "codegen-demo"; - private static final String SRC_MAIN_RESOURCES = "src/main/resources"; - - @Autowired private SpringBootApplicationYamlGenerator applicationYamlGenerator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateApplicationYaml_CreatesCorrectFileStructureAndFileName() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - applicationYamlGenerator.generateApplicationYaml(projectDestination, projectMetadata); - - File srcMainResourcesFileDestination = new File(projectDestination, SRC_MAIN_RESOURCES); - File generatedFile = new File(srcMainResourcesFileDestination, APPLICATION_YAML_FILE_NAME); - assertTrue(generatedFile.exists()); - - String content = Files.readString(generatedFile.toPath()); - assertTrue(content.length() > 10); - } - - @Test - void testGenerateApplicationYaml_CreatesFileAndVerifiesContent() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - applicationYamlGenerator.generateApplicationYaml(projectDestination, projectMetadata); - - File srcMainResourcesFileDestination = new File(projectDestination, SRC_MAIN_RESOURCES); - File generatedFile = new File(srcMainResourcesFileDestination, APPLICATION_YAML_FILE_NAME); - assertTrue(generatedFile.exists()); - - String yml = Files.readString(generatedFile.toPath()); - - assertTrue(yml.contains("spring:"), "YAML should contain 'spring:'"); - assertTrue(yml.contains("application:"), "YAML should contain 'application:'"); - assertTrue( - yml.contains("name: " + EXPECTED_APPLICATION_NAME), "YAML should set application name"); - } - - @Test - void testGenerateApplicationYaml_ThrowsExceptionOnTemplateEngineError() throws Exception { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - FreeMarkerTemplateEngine mockEngine = Mockito.mock(FreeMarkerTemplateEngine.class); - SpringBootApplicationYamlGenerator generatorLocal = - new SpringBootApplicationYamlGenerator(mockEngine); - - Mockito.doThrow(new IOException("Template processing error")) - .when(mockEngine) - .generateFileFromTemplate(any(), any(), any()); - - assertThrows( - IOException.class, - () -> generatorLocal.generateApplicationYaml(projectDestination, projectMetadata)); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGeneratorTest.java deleted file mode 100644 index af2622d..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaMainClassGeneratorTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBootJavaMainClassGeneratorTest { - - @Autowired private SpringBootJavaMainClassGenerator generator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateMainClass_CreatesMainClassFile() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - generator.generateProjectStarterClass(tempFolder.toFile(), projectMetadata); - - File expectedMainClassFile = - new File(tempFolder.toFile(), "src/main/java/com/codegen/core/CodegenDemoApplication.java"); - - assertTrue(expectedMainClassFile.exists(), "Main class file should be created"); - assertEquals( - "CodegenDemoApplication.java", - expectedMainClassFile.getName(), - "Main class file name should be CodegenDemoApplication.java"); - } - - @Test - void testGenerateMainClass_CreatesMainClassAndVerifiesContent() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - generator.generateProjectStarterClass(tempFolder.toFile(), projectMetadata); - - String generatedContent = - Files.readString( - new File( - tempFolder.toString(), - "src/main/java/com/codegen/core/CodegenDemoApplication.java") - .toPath()); - - assertTrue( - generatedContent.contains("@SpringBootApplication"), - "Generated content should contain @SpringBootApplication"); - - String expectedMainClassName = "CodegenDemoApplication"; - assertTrue( - generatedContent.contains(expectedMainClassName), - "Generated content should contain CodegenDemoApplication"); - - String expectedRunClassDefinition = "SpringApplication.run(CodegenDemoApplication.class, args)"; - assertTrue( - generatedContent.contains(expectedRunClassDefinition), - "Generated content should contain this line " + expectedRunClassDefinition); - } - - @Test - void testGenerateMainClass_CreatesMainClassWithSpecialCharsAndVerifiesContent() - throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo?*") - .packageName("com.codegen.core") - .build(); - - generator.generateProjectStarterClass(tempFolder.toFile(), projectMetadata); - - String generatedContent = - Files.readString( - new File( - tempFolder.toString(), - "src/main/java/com/codegen/core/CodegenDemoApplication.java") - .toPath()); - - assertTrue( - generatedContent.contains("@SpringBootApplication"), - "Generated content should contain @SpringBootApplication"); - - String expectedMainClassName = "CodegenDemoApplication"; - assertTrue( - generatedContent.contains(expectedMainClassName), - "Generated content should contain CodegenDemoApplication"); - - String expectedRunClassDefinition = "SpringApplication.run(CodegenDemoApplication.class, args)"; - assertTrue( - generatedContent.contains(expectedRunClassDefinition), - "Generated content should contain this line " + expectedRunClassDefinition); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGeneratorTest.java deleted file mode 100644 index 7ab42dc..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootJavaTestClassGeneratorTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBootJavaTestClassGeneratorTest { - - @Autowired private SpringBootJavaTestClassGenerator generator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateTestClass_CreatesTestClassFile() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - generator.generateTestClass(tempFolder.toFile(), projectMetadata); - - File expectedTestClassFile = - new File( - tempFolder.toFile(), "src/test/java/com/codegen/core/CodegenDemoApplicationTests.java"); - - assertTrue(expectedTestClassFile.exists(), "Test class file should be created"); - assertEquals( - "CodegenDemoApplicationTests.java", - expectedTestClassFile.getName(), - "Test class file name should be CodegenDemoApplicationTests.java"); - } - - @Test - void testGenerateTestClass_CreatesTestClassAndVerifiesContent() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - generator.generateTestClass(tempFolder.toFile(), projectMetadata); - - String generatedContent = - Files.readString( - new File( - tempFolder.toString(), - "src/test/java/com/codegen/core/CodegenDemoApplicationTests.java") - .toPath()); - - assertTrue( - generatedContent.contains("@SpringBootTest"), - "Generated content should contain @SpringBootTest"); - - String expectedTestClassName = "CodegenDemoApplicationTests"; - assertTrue( - generatedContent.contains(expectedTestClassName), - "Generated content should contain CodegenDemoApplicationTests"); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGeneratorTest.java deleted file mode 100644 index 6afbb32..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaProjectBuildGeneratorTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBootMavenJavaProjectBuildGeneratorTest { - - private static final String POM_FILE_NAME = "pom.xml"; - private static final String WRAPPER_FILE_DIR = ".mvn/wrapper"; - private static final String WRAPPER_FILE_NAME = "maven-wrapper.properties"; - private static final String WRAPPER_VERSION = "3.3.3"; - - @Autowired private SpringBootMavenJavaProjectBuildGenerator generator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateBuildConfiguration_CreatesPomFileAndWrapper() throws IOException { - Dependency dependencySpringBootStarterWeb = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-web") - .build(); - - Dependency dependencySpringBootStarterTest = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-test") - .scope("test") - .build(); - - SpringBootJavaProjectMetadataBuilder projectMetadataBuilder = - new SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder(); - projectMetadataBuilder - .groupId("com.codegen") - .artifactId("codegen-initialzr") - .name("codegen-initialzr") - .description("Codegen Initialzr") - .packageName("com.codegen.initialzr") - .dependencies(List.of(dependencySpringBootStarterWeb, dependencySpringBootStarterTest)); - - SpringBootJavaProjectMetadata springBootJavaProjectMetadata = - projectMetadataBuilder.springBootVersion("3.5.5").javaVersion("21").build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateBuildConfiguration(projectDestination, springBootJavaProjectMetadata); - - File pomFile = new File(projectDestination, POM_FILE_NAME); - assertTrue(pomFile.exists(), "pom.xml file should be created"); - - File wrapperFileDir = new File(projectDestination, WRAPPER_FILE_DIR); - assertTrue(wrapperFileDir.exists(), "Wrapper file directory should be created!"); - - File wrapperFile = new File(wrapperFileDir, WRAPPER_FILE_NAME); - assertTrue(wrapperFile.exists(), "Wrapper file maven-wrapper.properties should be created!"); - } - - @Test - void testGenerateBuildConfiguration_CreatesFileAndVerifiesContent() throws IOException { - Dependency dependencySpringBootStarterWeb = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-web") - .build(); - - Dependency dependencySpringBootStarterTest = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-test") - .scope("test") - .build(); - - SpringBootJavaProjectMetadataBuilder projectMetadataBuilder = - new SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder(); - projectMetadataBuilder - .groupId("com.codegen") - .artifactId("codegen-initialzr") - .name("codegen-initialzr") - .description("Codegen Initialzr") - .packageName("com.codegen.initialzr") - .dependencies(List.of(dependencySpringBootStarterWeb, dependencySpringBootStarterTest)); - - String javaVersion = "21"; - String springBootVersion = "3.5.5"; - SpringBootJavaProjectMetadata springBootJavaProjectMetadata = - projectMetadataBuilder - .springBootVersion(springBootVersion) - .javaVersion(javaVersion) - .build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateBuildConfiguration(projectDestination, springBootJavaProjectMetadata); - - File pomFile = new File(projectDestination, POM_FILE_NAME); - String pomContent = Files.readString(pomFile.toPath()); - pomContent = pomContent.trim().replaceAll("\\s*", ""); - - String expectedJavaVersionLine = "" + javaVersion + ""; - assertTrue( - pomContent.contains(expectedJavaVersionLine), - "Generated content should contain " + expectedJavaVersionLine); - - String parentContent = - """ - - org.springframework.boot - spring-boot-starter-parent - 3.5.5 - - - """; - - parentContent = parentContent.trim().replaceAll("\\s*", ""); - assertTrue( - pomContent.contains(parentContent), "Generated content should contain " + parentContent); - - File wrapperFileDir = new File(projectDestination, WRAPPER_FILE_DIR); - File wrapperFile = new File(wrapperFileDir, WRAPPER_FILE_NAME); - assertTrue(wrapperFile.exists(), "Wrapper file should be created!"); - - List wrapperFileList = Files.readAllLines(wrapperFile.toPath()); - assertThat(wrapperFileList, hasItem("wrapperVersion=" + WRAPPER_VERSION)); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGeneratorTest.java deleted file mode 100644 index 2ba0e51..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/framework/springboot/SpringBootMavenJavaReadMeGeneratorTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.framework.springboot; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; - -import io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating.FreeMarkerTemplateEngine; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SpringBootMavenJavaReadMeGeneratorTest { - - private static final String README_FILE_NAME = "README.md"; - - @Autowired private SpringBootMavenJavaReadMeGenerator readMeGenerator; - - @TempDir private Path tempFolder; - - @Test - void testGenerateReadMe_CreatesCorrectFileStructureAndFileName() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - readMeGenerator.generateProjectDocument(projectDestination, projectMetadata); - - File generatedFile = new File(projectDestination, README_FILE_NAME); - assertTrue(generatedFile.exists()); - - String content = Files.readString(generatedFile.toPath()); - assertTrue(content.length() > 10); - } - - @Test - void testGenerateReadMe_CreatesFileAndVerifiesContent() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - readMeGenerator.generateProjectDocument(projectDestination, projectMetadata); - - File generatedFile = new File(projectDestination, README_FILE_NAME); - assertTrue(generatedFile.exists()); - - String generatedContent = - Files.readString(new File(tempFolder.toString(), README_FILE_NAME).toPath()); - - String expectedLineProjectInitialization = "Project Initialization"; - assertTrue( - generatedContent.contains(expectedLineProjectInitialization), - "Generated content should contain" + expectedLineProjectInitialization); - - String expectedLineProjectName = "codegen-demo"; - assertTrue( - generatedContent.contains(expectedLineProjectName), - "Generated content should contain" + expectedLineProjectName); - - String expectedLineDependencies = "Dependencies"; - assertTrue( - generatedContent.contains(expectedLineDependencies), - "Generated content should contain" + expectedLineDependencies); - } - - @Test - void testGenerateReadMe_ThrowsExceptionOnTemplateEngineError() throws Exception { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").build(); - - File projectDestination = tempFolder.toFile(); - - FreeMarkerTemplateEngine mockEngine = Mockito.mock(FreeMarkerTemplateEngine.class); - SpringBootMavenJavaReadMeGenerator generatorLocal = - new SpringBootMavenJavaReadMeGenerator(mockEngine); - - Mockito.doThrow(new IOException("Template processing error")) - .when(mockEngine) - .generateFileFromTemplate(any(), any(), any()); - - assertThrows( - IOException.class, - () -> generatorLocal.generateProjectDocument(projectDestination, projectMetadata)); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGeneratorTest.java deleted file mode 100644 index 9d4d134..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenBuildWrapperGeneratorTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.maven; - -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class MavenBuildWrapperGeneratorTest { - - private static final String WRAPPER_FILE_DIR = ".mvn/wrapper"; - private static final String WRAPPER_FILE_NAME = "maven-wrapper.properties"; - private static final String WRAPPER_VERSION = "3.3.3"; - @TempDir public Path tempFolder; - @Autowired private MavenBuildWrapperGenerator generator; - - @Test - void testGenerateBuildWrapper_CreatesWrapperFile() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateBuildWrapper(projectDestination, projectMetadata); - - File wrapperFileDir = new File(projectDestination, WRAPPER_FILE_DIR); - assertTrue(wrapperFileDir.exists(), "Wrapper file directory should be created!"); - - File wrapperFile = new File(wrapperFileDir, WRAPPER_FILE_NAME); - assertTrue(wrapperFile.exists(), "Wrapper file should be created!"); - } - - @Test - void testGenerateBuildWrapper_CreatesFileAndVerifiesContent() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.codegen.core") - .build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateBuildWrapper(projectDestination, projectMetadata); - - File wrapperFileDir = new File(projectDestination, WRAPPER_FILE_DIR); - File wrapperFile = new File(wrapperFileDir, WRAPPER_FILE_NAME); - assertTrue(wrapperFile.exists(), "Wrapper file should be created!"); - - List wrapperFileList = Files.readAllLines(wrapperFile.toPath()); - assertThat(wrapperFileList, hasItem("wrapperVersion=" + WRAPPER_VERSION)); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGeneratorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGeneratorTest.java deleted file mode 100644 index 8dded14..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/maven/MavenJavaProjectLayoutGeneratorTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.maven; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectMetadata; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class MavenJavaProjectLayoutGeneratorTest { - - @TempDir public Path tempFolder; - @Autowired private MavenJavaProjectLayoutGenerator generator; - - @Test - void testGenerateProjectLayout_CreatesCorrectDirectoryAndPackageNames() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.example.demo") - .build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateProjectLayout(projectDestination, projectMetadata); - - List expectedDirectories = - List.of( - "src/main/java/com/example/demo", - "src/main/resources", - "src/test/java/com/example/demo", - "src/test/resources", - "src/gen/java/com/example/demo/codegen"); - - expectedDirectories.forEach( - dir -> { - File expectedDir = new File(projectDestination, dir); - assertTrue(expectedDir.exists(), "Directory " + dir + " was not created!"); - }); - } - - @Test - void testGenerateProjectLayout_CreatesProjectLayoutWithEmptyPackageName() throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder().name("codegen-demo").packageName("").build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateProjectLayout(projectDestination, projectMetadata); - - File expectedDir = new File(projectDestination, "src/main/java"); - assertTrue(expectedDir.exists(), "Main Java source directory was not created!"); - } - - @Test - void testGenerateProjectLayout_CreatesProjectLayoutWithSpecialCharInPackageName() - throws IOException { - ProjectMetadata projectMetadata = - new ProjectMetadata.ProjectMetadataBuilder() - .name("codegen-demo") - .packageName("com.example-demo") - .build(); - - File projectDestination = tempFolder.toFile(); - - generator.generateProjectLayout(projectDestination, projectMetadata); - - String expectedDirName = "src/main/java/com/example_demo"; - File expectedDir = new File(projectDestination, expectedDirName); - assertTrue(expectedDir.exists(), "Directory with special character not created!"); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngineTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngineTest.java deleted file mode 100644 index 4a04d0f..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/adapters/templating/FreeMarkerTemplateEngineTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.adapters.templating; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.templating.TemplateType; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class FreeMarkerTemplateEngineTest { - - private static final String GITIGNORE_FILE_NAME = ".gitignore"; - - @Autowired FreeMarkerTemplateEngine freeMarkerTemplateEngine; - - @TempDir private Path tempFolder; - - @Test - void testGenerateFileFromTemplateWithTemplateType() throws IOException { - List emptyList = Collections.emptyList(); - Map gitIgnoreData = new HashMap<>(); - gitIgnoreData.put("ignoreList", emptyList); - - freeMarkerTemplateEngine.generateFileFromTemplate( - TemplateType.GITIGNORE, gitIgnoreData, tempFolder.toFile()); - Path generatedFile = tempFolder.resolve(GITIGNORE_FILE_NAME); - assertTrue(Files.exists(generatedFile)); - String content = Files.readString(generatedFile); - assertTrue(content.length() > 10); - } - - @Test - void testGenerateFileFromTemplate() throws IOException { - List emptyList = Collections.emptyList(); - Map gitIgnoreData = new HashMap<>(); - gitIgnoreData.put("ignoreList", emptyList); - - String templateFileName = TemplateType.GITIGNORE.getTemplateFileName(); - String fileName = TemplateType.GITIGNORE.getFileName(); - freeMarkerTemplateEngine.generateFileFromTemplate( - templateFileName, fileName, gitIgnoreData, tempFolder.toFile()); - Path generatedFile = tempFolder.resolve(GITIGNORE_FILE_NAME); - assertTrue(Files.exists(generatedFile)); - String content = Files.readString(generatedFile); - assertTrue(content.length() > 10); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfigurationTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfigurationTest.java deleted file mode 100644 index fb71f2c..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/configuration/FreeMarkerTemplateConfigurationTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.configuration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import freemarker.template.Configuration; -import freemarker.template.TemplateExceptionHandler; -import io.github.bsayli.codegen.initializr.projectgeneration.configuration.properties.FreeMarkerTemplateProperties; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class FreeMarkerTemplateConfigurationTest { - - @Autowired private FreeMarkerTemplateConfiguration configuration; - - @Test - void testFreemarkerTemplateConfiguration_withValidProperties() - throws FreeMarkerConfigurationException { - Configuration freeMarkerConfiguration = - configuration.initializeFreeMarkerTemplateConfiguration(); - - assertThat(freeMarkerConfiguration.getTemplateExceptionHandler()) - .isEqualTo(TemplateExceptionHandler.RETHROW_HANDLER); - - assertThat(freeMarkerConfiguration.getDefaultEncoding()).isEqualTo("UTF-8"); - } - - @Test - void testFreemarkerTemplateConfiguration_withInvalidExceptionHandler_throwsException() { - FreeMarkerTemplateProperties testMockProperties = - new FreeMarkerTemplateProperties("UTF-8", "INVALID_HANDLER", "/templates"); - - FreeMarkerTemplateConfiguration freeMarkerTemplateConfigurationLocal = - new FreeMarkerTemplateConfiguration(testMockProperties); - - assertThrows( - FreeMarkerConfigurationException.class, - freeMarkerTemplateConfigurationLocal::initializeFreeMarkerTemplateConfiguration); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java deleted file mode 100644 index 61d4b90..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/registry/SimpleProjectGeneratorRegistryTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.registry; - -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.projectgeneration.generator.ProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.generator.springboot.maven.SpringBootMavenJavaProjectGenerator; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import java.util.Optional; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SimpleProjectGeneratorRegistryTest { - - @Autowired private SimpleProjectGeneratorRegistry registry; - - @Test - void testGetProjectGenerator_ExistingProjectType_ReturnsGenerator() { - ProjectType expectedProjectType = - new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); - - Optional generatorOptional = - registry.getProjectGenerator(expectedProjectType); - - assertTrue(generatorOptional.isPresent()); - - ProjectGenerator projectGenerator = generatorOptional.get(); - - assertSame(SpringBootMavenJavaProjectGenerator.class, projectGenerator.getClass()); - } -} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java b/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java deleted file mode 100644 index 93f249c..0000000 --- a/src/test/java/io/github/bsayli/codegen/initializr/projectgeneration/service/ProjectGenerationServiceImplTest.java +++ /dev/null @@ -1,181 +0,0 @@ -package io.github.bsayli.codegen.initializr.projectgeneration.service; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.util.CollectionUtils; - -@SpringBootTest -class ProjectGenerationServiceImplTest { - - private static final String DIRECTORY_NAME_UNARCHIVED = "unarchived"; - - @Autowired private ProjectGenerationService projectGenerationService; - - private Path archivedProjectPath; - - @Test - void testGenerateProject_SupportedProjectType_GeneratesProjectAndVerifiesContent() - throws IOException { - ProjectType springBootMavenJavaProjectType = - new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); - - Dependency dependencySpringBootStarterWeb = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-web") - .build(); - - Dependency dependencySpringBootStarterTest = - new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-test") - .scope("test") - .build(); - - SpringBootJavaProjectMetadataBuilder projectMetadataBuilder = - new SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder(); - projectMetadataBuilder - .groupId("com.codegen") - .artifactId("codegen-demo") - .name("codegen-demo") - .description("Codegen Demo Project") - .packageName("com.codegen.demo") - .dependencies(List.of(dependencySpringBootStarterWeb, dependencySpringBootStarterTest)); - - SpringBootJavaProjectMetadata springBootJavaProjectMetadata = - projectMetadataBuilder.springBootVersion("3.5.5").javaVersion("21").build(); - - archivedProjectPath = - projectGenerationService.generateProject( - springBootMavenJavaProjectType, springBootJavaProjectMetadata); - File archivedProjectFile = archivedProjectPath.toFile(); - - assertTrue(archivedProjectFile.exists(), "Archive project file should be created"); - - File extractedDir = createExtractedProject(archivedProjectFile); - assertTrue( - extractedDir.exists() && !FileUtils.isEmptyDirectory(extractedDir), - "Archived file was corrupted!"); - - String archived = archivedProjectFile.getName().replace(".zip", ""); - File extractedProjectDir = new File(extractedDir, archived); - - String gitIgnoreFileName = ".gitignore"; - File gitIgnoreFileFromUnarchived = new File(extractedProjectDir, gitIgnoreFileName); - assertTrue( - gitIgnoreFileFromUnarchived.exists(), - "Archived project file does not contain " + gitIgnoreFileName + " file"); - - String pomFileName = "pom.xml"; - File pomFileFromUnarchived = new File(extractedProjectDir, pomFileName); - assertTrue( - pomFileFromUnarchived.exists(), - "Archived project file does not contain " + pomFileName + " file"); - } - - @AfterEach - void cleanup() throws IOException { - if (archivedProjectPath != null && archivedProjectPath.toFile().exists()) { - Path parentPath = archivedProjectPath.getParent(); - if (parentPath != null && Files.exists(parentPath)) { - Collection files = FileUtils.listFiles(parentPath.toFile(), null, false); - List subfolderNames = getSubfolderNames(parentPath); - if (!CollectionUtils.isEmpty(files) && !CollectionUtils.isEmpty(subfolderNames)) { - boolean countSizeOk = files.size() == 1 && subfolderNames.size() == 2; - if (countSizeOk && subfolderNames.contains(DIRECTORY_NAME_UNARCHIVED)) { - FileUtils.deleteDirectory(parentPath.toFile()); - } - } - } - } - } - - private File createExtractedProject(File archivedProjectFile) throws IOException { - File extractedDir = new File(archivedProjectFile.getParentFile(), DIRECTORY_NAME_UNARCHIVED); - ensureDir(extractedDir, "Failed to create extracted dir: " + extractedDir); - - try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archivedProjectFile))) { - ZipEntry entry; - byte[] buffer = new byte[8192]; - - while ((entry = zis.getNextEntry()) != null) { - if (entry.isDirectory()) { - File directory = new File(extractedDir, entry.getName()); - ensureDir(directory, "Failed to create directory: " + directory); - } else { - File file = new File(extractedDir, entry.getName()); - File parent = file.getParentFile(); - ensureDir(parent, "Failed to create parent directory: " + parent); - - try (OutputStream outputStream = new FileOutputStream(file)) { - int len; - while ((len = zis.read(buffer)) != -1) { - outputStream.write(buffer, 0, len); - } - } - } - zis.closeEntry(); - } - } - - return extractedDir; - } - - private void ensureDir(File dir, String errorMessage) throws IOException { - if (dir == null) { - throw new IOException(errorMessage + " (null)"); - } - if (dir.exists()) { - if (!dir.isDirectory()) { - throw new IOException(errorMessage + " (exists but not a directory)"); - } - return; - } - boolean created = dir.mkdirs(); - if (!created && !dir.exists()) { - throw new IOException(errorMessage); - } - } - - private List getSubfolderNames(Path folderPath) { - List subfolderNames = new ArrayList<>(); - File folder = new File(folderPath.toString()); - if (!folder.isDirectory()) { - return subfolderNames; - } - File[] files = folder.listFiles(); - if (files == null) { - return subfolderNames; - } - for (File file : files) { - if (file.isDirectory()) { - subfolderNames.add(file.getName()); - } - } - return subfolderNames; - } -} From 52f9d47320711f2a2966ba451a487b23a5df43b3 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:28:47 +0300 Subject: [PATCH 28/74] style: apply Google Java Format across the codebase - Reformatted all source files using Google Java Format - No functional changes; formatting-only cleanup --- .../AbstractJavaClassScaffolderAdapter.java | 13 +- ...AbstractSingleTemplateArtifactAdapter.java | 4 +- .../bootstrap/config/ArtifactDefinition.java | 4 +- .../config/CodegenProfilesProperties.java | 6 +- .../config/CodegenProfilesPropertiesTest.java | 185 +++++++++--------- 5 files changed, 106 insertions(+), 106 deletions(-) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java index cff3468..12bedb5 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/shared/AbstractJavaClassScaffolderAdapter.java @@ -28,9 +28,9 @@ public abstract class AbstractJavaClassScaffolderAdapter implements ArtifactPort private final StringCaseFormatter stringCaseFormatter; protected AbstractJavaClassScaffolderAdapter( - TemplateRenderer renderer, - ArtifactDefinition artifactDefinition, - StringCaseFormatter stringCaseFormatter) { + TemplateRenderer renderer, + ArtifactDefinition artifactDefinition, + StringCaseFormatter stringCaseFormatter) { this.renderer = renderer; this.artifactDefinition = artifactDefinition; this.stringCaseFormatter = stringCaseFormatter; @@ -42,9 +42,8 @@ public final Iterable generate(ProjectBlueprint bluepri PackageName packageName = blueprint.getPackageName(); Map model = - Map.ofEntries( - entry(KEY_PROJECT_PACKAGE, packageName.value()), - entry(KEY_CLASS_NAME, className)); + Map.ofEntries( + entry(KEY_PROJECT_PACKAGE, packageName.value()), entry(KEY_CLASS_NAME, className)); TemplateDefinition templateDefinition = artifactDefinition.templates().getFirst(); Path baseDir = Path.of(templateDefinition.outputPath()); @@ -62,4 +61,4 @@ protected String pascal(String value) { } protected abstract String buildClassName(ProjectBlueprint blueprint); -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java index ca91d13..88d7a2b 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/adapter/out/shared/artifact/AbstractSingleTemplateArtifactAdapter.java @@ -16,7 +16,7 @@ public abstract class AbstractSingleTemplateArtifactAdapter implements ArtifactP private final ArtifactDefinition artifactDefinition; protected AbstractSingleTemplateArtifactAdapter( - TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { + TemplateRenderer renderer, ArtifactDefinition artifactDefinition) { this.renderer = renderer; this.artifactDefinition = artifactDefinition; } @@ -35,4 +35,4 @@ public final Iterable generate(ProjectBlueprint bluepri } protected abstract Map buildModel(ProjectBlueprint blueprint); -} \ No newline at end of file +} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java index 53e912d..ead5b81 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/ArtifactDefinition.java @@ -5,6 +5,4 @@ import java.util.List; public record ArtifactDefinition( - String basePath, - @Valid @NotNull List templates -) {} \ No newline at end of file + String basePath, @Valid @NotNull List templates) {} diff --git a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java index 652ce22..f8977d9 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesProperties.java @@ -29,19 +29,19 @@ public ProfileProperties requireProfile(ProfileType profile) { } ArtifactDefinition requireArtifact( - ProfileType profile, ProfileProperties profileProps, ArtifactKey artifactKey) { + ProfileType profile, ProfileProperties profileProps, ArtifactKey artifactKey) { ArtifactDefinition artifact = profileProps.artifacts().get(artifactKey.key()); if (artifact == null) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); + ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND, artifactKey.key(), profile.key()); } String basePath = profileProps.templateBasePath(); if (basePath == null || basePath.isBlank()) { throw new ProfileConfigurationException( - ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING, profile.key()); + ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING, profile.key()); } return new ArtifactDefinition(basePath, artifact.templates()); diff --git a/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java b/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java index b2621e6..aa38f76 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/bootstrap/config/CodegenProfilesPropertiesTest.java @@ -17,94 +17,97 @@ @DisplayName("Unit Test: CodegenProfilesProperties") class CodegenProfilesPropertiesTest { - private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; - private static final ArtifactKey ARTIFACT_KEY = ArtifactKey.POM; - private static final String PROFILE_KEY = PROFILE.key(); - private static final String ARTIFACT_MAP_KEY = ARTIFACT_KEY.key(); - private static final String TEMPLATE_BASE_PATH = "springboot/maven/java/"; - - @Test - @DisplayName("artifact() should return ArtifactDefinition with profile basePath and artifact templates") - void artifact_shouldReturnDefinitionWithProfileBasePathAndArtifactTemplates() { - TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); - - ArtifactDefinition artifactDefinition = - new ArtifactDefinition("artifact-specific/", List.of(templateDefinition)); - - ProfileProperties profileProperties = - new ProfileProperties( - TEMPLATE_BASE_PATH, - List.of(ARTIFACT_KEY), - Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); - - CodegenProfilesProperties properties = - new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); - - ArtifactDefinition result = properties.artifact(PROFILE, ARTIFACT_KEY); - - assertThat(result.basePath()).isEqualTo(TEMPLATE_BASE_PATH); - assertThat(result.templates()).isSameAs(artifactDefinition.templates()); - } - - @Test - @DisplayName("requireProfile() should throw ProfileConfigurationException when profile is missing") - void requireProfile_shouldThrowWhenProfileMissing() { - CodegenProfilesProperties properties = new CodegenProfilesProperties(Map.of()); - - assertThatThrownBy(() -> properties.requireProfile(PROFILE)) - .isInstanceOfSatisfying( - ProfileConfigurationException.class, - ex -> { - assertThat(ex.getMessageKey()) - .isEqualTo(ProfileConfigurationException.KEY_PROFILE_NOT_FOUND); - assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); - }); - } - - @Test - @DisplayName("artifact() should throw ProfileConfigurationException when artifact is missing") - void artifact_shouldThrowWhenArtifactMissing() { - ProfileProperties profileProperties = - new ProfileProperties(TEMPLATE_BASE_PATH, List.of(ARTIFACT_KEY), Map.of()); - - CodegenProfilesProperties properties = - new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); - - assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) - .isInstanceOfSatisfying( - ProfileConfigurationException.class, - ex -> { - assertThat(ex.getMessageKey()) - .isEqualTo(ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND); - assertThat(ex.getArgs()).containsExactly(ARTIFACT_MAP_KEY, PROFILE_KEY); - }); - } - - @Test - @DisplayName("artifact() should throw ProfileConfigurationException when templateBasePath is blank") - void artifact_shouldThrowWhenTemplateBasePathBlank() { - CodegenProfilesProperties properties = getCodegenProfilesProperties(); - - assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) - .isInstanceOfSatisfying( - ProfileConfigurationException.class, - ex -> { - assertThat(ex.getMessageKey()) - .isEqualTo(ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING); - assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); - }); - } - - private static CodegenProfilesProperties getCodegenProfilesProperties() { - TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); - - ArtifactDefinition artifactDefinition = - new ArtifactDefinition(null, List.of(templateDefinition)); - - ProfileProperties profileProperties = - new ProfileProperties( - " ", List.of(ARTIFACT_KEY), Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); - - return new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); - } -} \ No newline at end of file + private static final ProfileType PROFILE = ProfileType.SPRINGBOOT_MAVEN_JAVA; + private static final ArtifactKey ARTIFACT_KEY = ArtifactKey.POM; + private static final String PROFILE_KEY = PROFILE.key(); + private static final String ARTIFACT_MAP_KEY = ARTIFACT_KEY.key(); + private static final String TEMPLATE_BASE_PATH = "springboot/maven/java/"; + + private static CodegenProfilesProperties getCodegenProfilesProperties() { + TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); + + ArtifactDefinition artifactDefinition = + new ArtifactDefinition(null, List.of(templateDefinition)); + + ProfileProperties profileProperties = + new ProfileProperties( + " ", List.of(ARTIFACT_KEY), Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); + + return new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + } + + @Test + @DisplayName( + "artifact() should return ArtifactDefinition with profile basePath and artifact templates") + void artifact_shouldReturnDefinitionWithProfileBasePathAndArtifactTemplates() { + TemplateDefinition templateDefinition = new TemplateDefinition("pom.ftl", "pom.xml"); + + ArtifactDefinition artifactDefinition = + new ArtifactDefinition("artifact-specific/", List.of(templateDefinition)); + + ProfileProperties profileProperties = + new ProfileProperties( + TEMPLATE_BASE_PATH, + List.of(ARTIFACT_KEY), + Map.of(ARTIFACT_MAP_KEY, artifactDefinition)); + + CodegenProfilesProperties properties = + new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + + ArtifactDefinition result = properties.artifact(PROFILE, ARTIFACT_KEY); + + assertThat(result.basePath()).isEqualTo(TEMPLATE_BASE_PATH); + assertThat(result.templates()).isSameAs(artifactDefinition.templates()); + } + + @Test + @DisplayName( + "requireProfile() should throw ProfileConfigurationException when profile is missing") + void requireProfile_shouldThrowWhenProfileMissing() { + CodegenProfilesProperties properties = new CodegenProfilesProperties(Map.of()); + + assertThatThrownBy(() -> properties.requireProfile(PROFILE)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_PROFILE_NOT_FOUND); + assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); + }); + } + + @Test + @DisplayName("artifact() should throw ProfileConfigurationException when artifact is missing") + void artifact_shouldThrowWhenArtifactMissing() { + ProfileProperties profileProperties = + new ProfileProperties(TEMPLATE_BASE_PATH, List.of(ARTIFACT_KEY), Map.of()); + + CodegenProfilesProperties properties = + new CodegenProfilesProperties(Map.of(PROFILE_KEY, profileProperties)); + + assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_ARTIFACT_NOT_FOUND); + assertThat(ex.getArgs()).containsExactly(ARTIFACT_MAP_KEY, PROFILE_KEY); + }); + } + + @Test + @DisplayName( + "artifact() should throw ProfileConfigurationException when templateBasePath is blank") + void artifact_shouldThrowWhenTemplateBasePathBlank() { + CodegenProfilesProperties properties = getCodegenProfilesProperties(); + + assertThatThrownBy(() -> properties.artifact(PROFILE, ARTIFACT_KEY)) + .isInstanceOfSatisfying( + ProfileConfigurationException.class, + ex -> { + assertThat(ex.getMessageKey()) + .isEqualTo(ProfileConfigurationException.KEY_TEMPLATE_BASE_MISSING); + assertThat(ex.getArgs()).containsExactly(PROFILE_KEY); + }); + } +} From 14ff343c386d423b968eaf9579b8a1321a2a8393 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:32:23 +0300 Subject: [PATCH 29/74] chore(version): bump project version to 1.0.0 for upcoming stable release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This branch introduces the full hexagonal architecture refactor, new configuration model, improved templating pipeline, test infrastructure, CI (Build + CodeQL + Coverage), and extensive cleanups — representing a major, stable evolution of the project. Updating the version early in the branch ensures: - CI and coverage run against the upcoming stable baseline - README and artifacts reflect the new release direction - The final merge to main can directly produce 1.0.0 No breaking change for users yet, since this is not merged into main. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 61eb99c..c78f2b2 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ io.github.bsayli codegen-springboot-initializr - 0.2.0 + 1.0.0 codegen-springboot-initializr Spring Boot project generator (CLI & programmatic) https://github.com/bsayli/codegen-springboot-initializr From c136c792c9a049f1ad8720eafa40da78edf70b1f Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 16:51:28 +0300 Subject: [PATCH 30/74] docs(readme): show Codecov badge for refactor/hexagonal-architecture branch Switched the Codecov badge to track the 'refactor/hexagonal-architecture' branch so that coverage is visible during PR review. This will be reverted to the main-branch badge after merging into main. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ca9f20..1b535be 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml) [![Release](https://img.shields.io/github/v/release/bsayli/codegen-springboot-initializr?logo=github&label=release)](https://github.com/bsayli/codegen-springboot-initializr/releases/latest) [![CodeQL](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml) -[![codecov](https://codecov.io/gh/bsayli/codegen-springboot-initializr/branch/main/graph/badge.svg)](https://codecov.io/gh/bsayli/codegen-springboot-initializr) +[![codecov](https://codecov.io/gh/bsayli/codegen-springboot-initializr/branch/refactor/hexagonal-architecture/graph/badge.svg)](https://codecov.io/gh/bsayli/codegen-springboot-initializr/tree/refactor/hexagonal-architecture) [![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.7-green?logo=springboot)](https://spring.io/projects/spring-boot) [![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) From aa306e660deba2ae11d71340b7f6c4f5608e1b70 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 17:22:43 +0300 Subject: [PATCH 31/74] test(it): add initial integration tests and fix README template rendering - Added SpringBootMavenJavaArtifactsAdapterIT as the first integration test - Added CodegenSpringbootInitializrApplicationIT to verify full context bootstrapping - Fixed README.md.ftl rendering condition (`dependencies?has_content`) - Corrected `.gitignore.ftl` template path usage for FreeMarker loading - Ensured template-base-path resolution works consistently across adapters --- src/main/resources/application.yml | 2 +- .../springboot/maven/java/README.md.ftl | 3 +- ...egenSpringbootInitializrApplicationIT.java | 18 +++++ ...SpringBootMavenJavaArtifactsAdapterIT.java | 78 +++++++++++++++++++ 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/CodegenSpringbootInitializrApplicationIT.java create mode 100644 src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterIT.java diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c4adca3..2f4f750 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -29,7 +29,7 @@ codegen: gitignore: templates: - - template: .gitignore.ftl + - template: gitignore.ftl output-path: .gitignore application-yaml: diff --git a/src/main/resources/templates/springboot/maven/java/README.md.ftl b/src/main/resources/templates/springboot/maven/java/README.md.ftl index 8f7eca3..9f2d70a 100644 --- a/src/main/resources/templates/springboot/maven/java/README.md.ftl +++ b/src/main/resources/templates/springboot/maven/java/README.md.ftl @@ -65,9 +65,8 @@ src ## Dependencies (selected) -<#if dependencies?? && dependencies?size > 0> +<#if dependencies?has_content> <#list dependencies as d> - * ${d.groupId}:${d.artifactId}<#if d.version?? && d.version?has_content>:${d.version}<#if d.scope?? && d.scope?has_content> (${d.scope}) <#else> diff --git a/src/test/java/io/github/bsayli/codegen/initializr/CodegenSpringbootInitializrApplicationIT.java b/src/test/java/io/github/bsayli/codegen/initializr/CodegenSpringbootInitializrApplicationIT.java new file mode 100644 index 0000000..0ebfb72 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/CodegenSpringbootInitializrApplicationIT.java @@ -0,0 +1,18 @@ +package io.github.bsayli.codegen.initializr; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@Tag("integration") +@DisplayName("Integration Test: Spring Context Bootstrapping") +class CodegenSpringbootInitializrApplicationIT { + + @Test + @DisplayName("Spring context should load successfully") + void contextLoads() { + // If context fails to load, the test will fail automatically. + } +} diff --git a/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterIT.java b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterIT.java new file mode 100644 index 0000000..0aa5f89 --- /dev/null +++ b/src/test/java/io/github/bsayli/codegen/initializr/adapter/out/profile/springboot/maven/java/SpringBootMavenJavaArtifactsAdapterIT.java @@ -0,0 +1,78 @@ +package io.github.bsayli.codegen.initializr.adapter.out.profile.springboot.maven.java; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.github.bsayli.codegen.initializr.domain.factory.ProjectBlueprintFactory; +import io.github.bsayli.codegen.initializr.domain.model.ProjectBlueprint; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependencies; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.Dependency; +import io.github.bsayli.codegen.initializr.domain.model.value.dependency.DependencyCoordinates; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ArtifactId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.GroupId; +import io.github.bsayli.codegen.initializr.domain.model.value.identity.ProjectIdentity; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectDescription; +import io.github.bsayli.codegen.initializr.domain.model.value.naming.ProjectName; +import io.github.bsayli.codegen.initializr.domain.model.value.pkg.PackageName; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.JavaVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.PlatformTarget; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.platform.SpringBootVersion; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildOptions; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; +import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; +import io.github.bsayli.codegen.initializr.domain.port.out.artifact.GeneratedFile; +import java.util.List; +import java.util.stream.StreamSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@Tag("integration") +@DisplayName("Integration Test: SpringBootMavenJavaArtifactsAdapter") +class SpringBootMavenJavaArtifactsAdapterIT { + + @Autowired private SpringBootMavenJavaArtifactsAdapter adapter; + + @Test + @DisplayName( + "generate() should produce artifacts for a valid Spring Boot + Maven + Java blueprint") + void generate_shouldProduceArtifactsForValidBlueprint() { + ProjectBlueprint blueprint = blueprint(); + + Iterable files = adapter.generate(blueprint); + + var list = StreamSupport.stream(files.spliterator(), false).toList(); + + assertThat(list).isNotEmpty(); + } + + private ProjectBlueprint blueprint() { + ProjectIdentity identity = + new ProjectIdentity(new GroupId("com.example"), new ArtifactId("demo-app")); + + ProjectName name = new ProjectName("demo-app"); + ProjectDescription description = new ProjectDescription("Integration test blueprint"); + PackageName packageName = new PackageName("com.example.demo"); + + BuildOptions buildOptions = + new BuildOptions(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); + + PlatformTarget platformTarget = + new PlatformTarget(JavaVersion.JAVA_21, SpringBootVersion.V3_5_6); + + Dependency webStarter = + new Dependency( + new DependencyCoordinates( + new GroupId("org.springframework.boot"), new ArtifactId("spring-boot-starter")), + null, + null); + + Dependencies dependencies = Dependencies.of(List.of(webStarter)); + + return ProjectBlueprintFactory.of( + identity, name, description, packageName, buildOptions, platformTarget, dependencies); + } +} From 4ba203b991f6a90cb892b280d50936153c34b925 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 17:38:39 +0300 Subject: [PATCH 32/74] refactor(domain): harden immutability and clean API based on CodeQL recommendations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dependencies.asList now returns an unmodifiable defensive copy - GeneratedFile.Binary made fully immutable • defensive copy in constructor and getter • simplified equals/hashCode for safe comparison • removed IntelliJ nullability noise - PlatformTargetSelector API simplified • removed unused parameter from supportedTargetsFor() • eliminated @SuppressWarnings("unused") • updated corresponding unit test All changes follow CodeQL warnings and reinforce domain immutability and API clarity. --- .../domain/model/value/dependency/Dependencies.java | 2 +- .../domain/policy/tech/PlatformTargetSelector.java | 3 +-- .../domain/port/out/artifact/GeneratedFile.java | 8 ++++++-- .../domain/policy/tech/PlatformTargetSelectorTest.java | 3 +-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java index 71d6829..2324c59 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/model/value/dependency/Dependencies.java @@ -15,7 +15,7 @@ public static Dependencies of(List raw) { } public List asList() { - return items; + return List.copyOf(items); } public boolean isEmpty() { diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java index 4ce4773..fc432bd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelector.java @@ -17,8 +17,7 @@ public static PlatformTarget select( return requested; } - @SuppressWarnings("unused") - public static List supportedTargetsFor(BuildOptions options) { + public static List supportedTargetsFor() { return CompatibilityPolicy.allSupportedTargets(); } } diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java index 801df73..3634578 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java @@ -31,8 +31,12 @@ public byte[] bytes() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Binary(Path path, byte[] bytes1))) return false; + if (this == o) { + return true; + } + if (!(o instanceof Binary(Path path, byte[] bytes1))) { + return false; + } return relativePath.equals(path) && Arrays.equals(bytes, bytes1); } diff --git a/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java index f3ee2f3..f5a4f31 100644 --- a/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java +++ b/src/test/java/io/github/bsayli/codegen/initializr/domain/policy/tech/PlatformTargetSelectorTest.java @@ -54,9 +54,8 @@ void select_incompatibleTarget_shouldFailCompatibility() { @Test @DisplayName("supportedTargetsFor should return all supported targets from CompatibilityPolicy") void supportedTargetsFor_shouldReturnAllSupportedTargets() { - BuildOptions options = buildOptions(); - List targets = PlatformTargetSelector.supportedTargetsFor(options); + List targets = PlatformTargetSelector.supportedTargetsFor(); assertThat(targets) .isNotEmpty() From 8fe118aaec942bad3e3ce9d37487e0460a399929 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 17:45:09 +0300 Subject: [PATCH 33/74] fix(domain): enforce defensive copy in GeneratedFile.Binary to prevent internal representation exposure - Replace compact constructor with full canonical constructor - Ensure `bytes` field is assigned using a defensive copy - Keep getter returning a copied array for full immutability - Resolves CodeQL "Exposing internal representation" warning --- .../initializr/domain/port/out/artifact/GeneratedFile.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java index 3634578..4c25e44 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java @@ -18,10 +18,12 @@ record Text(Path relativePath, String content, Charset charset) implements Gener } record Binary(Path relativePath, byte[] bytes) implements GeneratedFile { - public Binary { + + public Binary(Path relativePath, byte[] bytes) { requireRelativePath(relativePath); requireBinaryContent(bytes); - bytes = Arrays.copyOf(bytes, bytes.length); + this.relativePath = relativePath; + this.bytes = Arrays.copyOf(bytes, bytes.length); } @Override From 8bc8b7e5fda36c8a1ef7fa81219575a56789cf02 Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 17:50:43 +0300 Subject: [PATCH 34/74] chore: suppress false-positive warning (java:S2384) in Binary record --- .../initializr/domain/port/out/artifact/GeneratedFile.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java index 4c25e44..be54fbd 100644 --- a/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java +++ b/src/main/java/io/github/bsayli/codegen/initializr/domain/port/out/artifact/GeneratedFile.java @@ -17,6 +17,7 @@ record Text(Path relativePath, String content, Charset charset) implements Gener } } + @SuppressWarnings("java:S2384") // Representation exposure false-positive; defensive copy applied record Binary(Path relativePath, byte[] bytes) implements GeneratedFile { public Binary(Path relativePath, byte[] bytes) { From f4b15decd9cec9f6b31e2d5effd9a2130efde81f Mon Sep 17 00:00:00 2001 From: bsayli Date: Tue, 25 Nov 2025 18:06:29 +0300 Subject: [PATCH 35/74] docs: refresh README for 1.0.0 hexagonal refactor, update project status, and remove old social-preview image (new one will be added later) --- README.md | 248 +++++++++++++++------------------ docs/images/social-preview.png | Bin 1149746 -> 0 bytes 2 files changed, 111 insertions(+), 137 deletions(-) delete mode 100644 docs/images/social-preview.png diff --git a/README.md b/README.md index 1b535be..f4724d9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Codegen Spring Boot Initializr — Hexagonal, Templated, Zero-Boilerplate Project Generator +# Codegen Spring Boot Initializr — Hexagonal, Template‑Driven, Zero‑Boilerplate Project Generator [![Build](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/build.yml) -[![Release](https://img.shields.io/github/v/release/bsayli/codegen-springboot-initializr?logo=github&label=release)](https://github.com/bsayli/codegen-springboot-initializr/releases/latest) +[![Release](https://img.shields.io/github/v/release/bsayli/codegen-springboot-initializr?logo=github\&label=release)](https://github.com/bsayli/codegen-springboot-initializr/releases/latest) [![CodeQL](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml/badge.svg)](https://github.com/bsayli/codegen-springboot-initializr/actions/workflows/codeql.yml) [![codecov](https://codecov.io/gh/bsayli/codegen-springboot-initializr/branch/refactor/hexagonal-architecture/graph/badge.svg)](https://codecov.io/gh/bsayli/codegen-springboot-initializr/tree/refactor/hexagonal-architecture) [![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) @@ -9,199 +9,173 @@ [![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -

- Social preview -
- Social preview banner for GitHub and sharing -

- -**A customizable project generator for Spring Boot.** -Quickly scaffold a new Java application with predefined structure, configuration, and tests — no repetitive setup -required. - --- -## 🚀 Problem Statement +## ⚠ Project Status (1.0.0 Refactor Branch) -Bootstrapping a new Spring Boot project often involves: +This README reflects the ongoing **hexagonal architecture rewrite** for version **1.0.0**. -* Manually creating Maven folders -* Writing boilerplate `pom.xml` -* Copying `.gitignore`, `application.yml`, and test classes -* Setting up wrapper scripts +The core domain, application layer, artifact pipeline, FreeMarker templating, CI/CD, and test suite are complete. -❌ Time wasted on repetitive setup -❌ Risk of inconsistencies between projects -❌ Slower onboarding for new developers +🔄 **Inbound adapters (CLI & REST)** are under active development and will land before the **1.0.0 GA release**. --- -## 💡 Solution +## 🚀 Overview -This project automates all of that: +**Codegen Spring Boot Initializr** is a **hexagonal, template‑driven generator** that produces production‑ready Spring Boot project skeletons with: -* Generates a **ready-to-run Spring Boot project** with Maven -* Adds `.gitignore`, `application.yml`, starter class, and test class -* Supports **custom package and project naming** -* Ships with **CLI runner** for one-liner project generation -* Produces a **zip archive** you can immediately extract and use +* Strongly validated domain blueprint +* Profile‑based artifact pipelines (e.g., `springboot-maven-java`) +* FreeMarker template rendering +* Fully isolated and tested ports/adapters +* Zero boilerplate, consistent project layouts + +It aims to eliminate repetitive setup steps (pom.xml, `.gitignore`, `application.yml`, test scaffolding, package structure) by generating them automatically. --- -## ⚡ Quick Start +## 💡 Problem Statement -### 1. Clone the Repository +Bootstrapping a new Spring Boot project often means: -```bash -git clone https://github.com/bsayli/codegen-springboot-initializr.git -cd codegen-springboot-initializr -``` +* Creating folder structures by hand +* Copy‑pasting `pom.xml`, `.gitignore`, config files +* Writing the same starter and test classes repeatedly +* Maintaining consistency across multiple services -### 2. Build the Project +This leads to **time loss**, **inconsistencies**, and **onboarding friction**. -```bash -mvn clean install -``` +--- -### 3. Run in CLI Mode +## 💡 Solution -```bash -mvn spring-boot:run -Dspring-boot.run.profiles=cli \ - -Dspring-boot.run.arguments="--groupId=com.example --artifactId=demo-app --packageName=com.example.demo --outputDir=./target/generated-projects --overwrite=true" -``` +This project provides: -✅ This generates a new project as a zip archive under the specified output directory (default: -`./target/generated-projects`): +* **Hexagonal core** — domain‑first, framework‑agnostic +* **Template‑driven artifact generation** via FreeMarker +* **Strictly validated domain blueprint** (name, groupId, artifactId, package, dependencies) +* **Profile‑based pipelines** — e.g. Spring Boot + Maven + Java 21 +* **Full test coverage:** unit + integration +* **GitHub Actions** with CodeQL, JaCoCo, Codecov -``` -[OK] Project archive generated at: /.../target/generated-projects/demo-app/demo-app.zip -``` +Planned for 1.0.0: -ℹ️ Tips: +* **CLI inbound adapter** — generate projects via command line +* **REST inbound adapter** — generate via HTTP POST -* If you don’t provide `--outputDir`, the project will be created under the default path `target/generated-projects`. -* If the target directory already exists: +--- - * By default, the generator will **fail-fast** with a clear error. - * Add `--overwrite=true` to **delete and regenerate** the project in the same directory. +## 🧩 Current Architecture (Hexagonal) ---- +This generator follows a clean **ports & adapters** architecture. -## 🧑‍💻 Programmatic Usage - -```java -import java.nio.file.Path; -import java.util.List; - -import io.github.bsayli.codegen.initializr.projectgeneration.model.Dependency; -import io.github.bsayli.codegen.initializr.projectgeneration.model.ProjectType; -import io.github.bsayli.codegen.initializr.projectgeneration.model.spring.SpringBootJavaProjectMetadata.SpringBootJavaProjectMetadataBuilder; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.BuildTool; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Framework; -import io.github.bsayli.codegen.initializr.domain.model.value.tech.stack.Language; -import io.github.bsayli.codegen.initializr.projectgeneration.service.ProjectGenerationService; - -// Assume ProjectGenerationService is injected or obtained from Spring context -ProjectGenerationService service = /* @Autowired or ApplicationContext.getBean(...) */; - - var depWeb = new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-web") - .build(); - - var depTest = new Dependency.DependencyBuilder() - .groupId("org.springframework.boot") - .artifactId("spring-boot-starter-test") - .scope("test") - .build(); - - var metadata = new SpringBootJavaProjectMetadataBuilder() - .springBootVersion("3.5.5") - .javaVersion("21") - .groupId("com.example") - .artifactId("demo-app") - .name("demo-app") - .description("Generated by codegen-initializr-core") - .packageName("com.example.demo") - .dependencies(List.of(depWeb, depTest)) - .build(); - - var type = new ProjectType(Framework.SPRING_BOOT, BuildTool.MAVEN, Language.JAVA); - - Path zip = service.generateProject(type, metadata); -System.out. - - println("Archive generated at: "+zip.toAbsolutePath()); -``` +**Domain Layer** ---- +* `ProjectBlueprint` (aggregate root) +* Value Objects (`ProjectName`, `PackageName`, `GroupId`, etc.) +* Policies (naming, reserved words, dependency rules) +* Errors with i18n (`DomainViolationException`, etc.) -## 🖼 Demo Output +**Application Layer** -Example of the generated project structure: +* Ports for artifact generation (`ProjectArtifactsPort`, `ArtifactPort`) +* Application services orchestrating project generation -```text -demo-app/ - ├── pom.xml - ├── .gitignore - ├── src/ - │ ├── main/java/com/example/demo/DemoAppApplication.java - │ ├── main/resources/application.yml - │ ├── test/java/com/example/demo/DemoAppApplicationTests.java - │ └── gen/java/... (for codegen output) -``` +**Adapter Layer** ---- +* **Outbound:** -## 🛠 Tech Stack & Features + * FreeMarker templating + * Artifact adapters (`pom`, `.gitignore`, `application.yml`, scaffolder, README) + * Profile selection: `springboot-maven-java` +* **Inbound:** -* 🚀 **Java 21** — modern baseline -* 🍃 **Spring Boot 3.5** -* 📦 **Maven 3.9+** — build and dependency management -* 🧩 **FreeMarker templates** — for generator extensibility -* 📂 **Automatic directory structure** — `src/main/java`, `src/test/java`, etc. -* 🧪 **JUnit 5** — generated test classes + * CLI (coming soon) + * REST (coming soon) + +**Bootstrap Layer** + +* Spring Boot configuration +* Template loader +* Profile bindings --- -## 🧩 Architecture +## 📦 Features (1.0.0 Core) + +### ✅ Completed -This project follows a **hexagonal (ports & adapters) architecture**: +* Hexagonal refactor +* Domain-driven blueprint & policies +* FreeMarker template rendering +* Profile-based artifact pipeline +* Integration test suite (`SpringBootTest` + Failsafe) +* Codecov integration +* CodeQL + Security scanning +* GitHub Actions (build + test) -* **Ports** — abstract interfaces like `ProjectBuildGenerator`, `ApplicationYamlGenerator`, `ProjectArchiver` -* **Adapters** — framework-specific implementations (Spring Boot, Maven, FreeMarker) -* **Core** — generation service depends only on ports, making it extensible and testable +### 🔄 In Progress -This design allows the generator to evolve independently of specific tools while staying highly testable. +* CLI adapter (inbound) +* REST adapter (inbound) +* Additional profiles --- ## 🧪 Testing -Run the test suite: +Run all tests: ```bash -mvn test +mvn verify ``` -The generator components (`pom.xml`, `.gitignore`, `application.yml`, layout, archiver) are fully covered with unit & -integration tests. +Test suite includes: + +* Domain unit tests (policies, value objects, selectors) +* Adapter unit tests +* Integration tests verifying `springboot-maven-java` pipeline end‑to‑end +* JaCoCo + Codecov reporting + +--- + +## 📂 Project Structure (Generated Output Example) + +```text +my-app/ + ├── pom.xml + ├── .gitignore + ├── src/ + │ ├── main/java/.../MyAppApplication.java + │ ├── main/resources/application.yml + │ ├── test/java/.../MyAppApplicationTests.java + │ └── gen/java/... (reserved for future generators) +``` + +--- + +## 🛣 Roadmap (Post‑1.0.0) + +* Additional generation profiles (Kotlin, Gradle, multi‑module) +* Dockerfile & CI/CD template artifacts +* Pluggable dependency catalogs --- -## 📖 Related Work +## 📘 Contributing -This tool is inspired by the need to automate repetitive **Spring Boot project initialization** tasks. -It works well alongside other repositories -like [spring-boot-openapi-generics-clients](https://github.com/bsayli/spring-boot-openapi-generics-clients). +Contributions and discussions are welcome. +Open issues or PRs at: +[https://github.com/bsayli/codegen-springboot-initializr](https://github.com/bsayli/codegen-springboot-initializr) --- ## 🛡 License -MIT +Licensed under **MIT** — see [LICENSE](LICENSE). --- **Author:** Barış Saylı -**GitHub:** [bsayli](https://github.com/bsayli) +GitHub: [https://github.com/bsayli](https://github.com/bsayli) diff --git a/docs/images/social-preview.png b/docs/images/social-preview.png deleted file mode 100644 index c4e56f8e56dd63b75f26bfc47e12999a8036f5f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1149746 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≫%)@qJ+Pns$?X(x&^`XC6E*a?k23iZss|BQolzA; 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

V{rDN(Sm-K0=ieE6xLd=w8d65o`qedN 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

8&qcufB31XXRx`Q# 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≫C6gX~UOe1(yo2tQ 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

#t*ro;SwlLc77 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
P@-8-Sj#EmE9Dp&?Ftp9w@^Qdr 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%0D(LXV!}4Tac^$?uIPf28qMfEIF=obw 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

)XdIi6s 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ϛl1Jnn4qQ=%)hgudos} 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®l=46p5z93;h$o4c@J%p*B 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�!QKe4xz{;2Qbq?4MjCfH0(# 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≤E3b-=9e(+0~wP znS1$)j|S@^-5=sn4|ieH%wo8B4FS-&6&WIQ!I91DZM%~3P5RZgi`j{%c7p)G)UwgD z2F!LJB)7l%-UEr+HV0`TM}lu~kP5l!s;z6J*=AG>OYTs4p5?>2!B$U% z6@GU!7g+&8W^@ledTl@->k=>$;&?tthwnYBtG^R|Ey#D5;;hFTYM>^0Vx+iTo4HEN zUh?x6mgOZa+MFWavpNzo@@V^b|8BbMm{>aoi@r5>xJVnsf3WUuIkE1(TDt4)rdxgh zNkpux|42t|@sZ_SEW53ZiT^+xiJzrl1=~d>`%!r~ZZGlBnji2hc&H(M&C9)WTR0z0wF|@Se+w9Q<2s(ascTKuoG4bKOt|`!Nl(K^YLt+neF*2ieo2^1LhWV`j(Y2Nup!Nf`U~eBG&0TsnBc!fF)%orP_HjwlFT(5uqSV< z)Pja3eSaM264FY9e%wjsGGcH=HP6nqtqI>Ug?hgIf-yPQ8DE)jB!6+97U#02lGy}Z z(okONZR%1cg3G<4P)jcw8)S^LVXjyI$mS{6?I_ww#knkCCGJ2hS6kcy;aKWSVq>k@ zq;G{5f5LkF(*kKfuuJOJXYjC0M4sN0JQ!kfMEYY+sVXqm9&lkLkjqxrOt{27iu3ey zGKbH|AX%;JPD@Q8-!F0u%_!wv4`BmTVD~Q4LK<)OmbO{Y(l~usC$0Ik(drpmJcNWj zY^xB_Jwg|wdH|W~yjkg{$a%O&e|%vFH8KwKrx(q}cy9#KRz-As!xLG@s{Mn`#!pXP zLS7p^TuZ#WdFOz`Io1Gojk1J4#rN=^Wcnk2bLG2ZGHtRg?B-1ri^3as<v!MHpA_T?5&%iQMZ@(f8ppVj+v^Q?i&HlUb~RKbf>TY7`v@HShE7lE1v zd*vKWLmK0l{KRi34w=ZpmV6i|KZg6Poo71TC{Ct(@t9g|znSQLlIbrubX8^IO$NF} zhMr%6_sz##*h0n2S3(kIOo}f(oOW6wt#1UTL}tp=~zXx-M+C*Mlpe&*d~dNDCQb+`Y7iRm|*m~ z-p1pzD75a`FY*iyG=Uj`;V!q2GL43kae%lmzA%4{3=2+}AAofvDNCMcT*1_u0*zj9 zMRKOA?k9W41t(hc8K08o-BF6nqXy&%uEjQi33IB`K`v2L3w#Hc1NwSbEZNSyi5mZas@}`RNFR+=`Il+zE#~S`Pt)!X)2TAdH(kR!?Xyh?5M^u7sv`eW;>|(P1{UTi$ z($@DQlTvP%K0-`A|7J{!;#(c^m)qaFjPCc4WFAJTaX*}8nY!(!c2^BcAJ+O4Ru6HB zgyy8W(o`;&Pps}BO0AtVrH0vNpOTmIVJF$<NTJOROcF+3?;iuv9DfATR!nll*mUHpU(N=;FepOJN6*2yFA^Hp^}1=830f26SgrPN_8egKUWiXbwIxscW#9||fHx<0lUKJP{kS0vAiBMgwy~4dXkR0!@B+9aRh@qVY(BuD zF6bu>$k4t*Izox%G=Zk}+iNa37-L=uf z1V}$IZ(Fhn6(%?>_+zKq(tHYeAUJh(#bs`v6JM%;vDZWE_l`}eL|Y5_6F3ZW6!P3a z$^v-49>@M#%l~E5l_)lHsy?>d&5YAQL~L+5<6WlLxAf#XF)x+K4GVSGGfqS|F7Bpu zS}IMR!iM9=pWt64o<9w1mrHSNLzT>TiW$9E;tZ9ro-l6)-Ttso$EKF;shDQJz3`?q zg4z7xi?>`hi`KvHlo%I0by~j2Rx?V!KV>7Oxu&t{c42H$)~E8kH~S_&mo(J$Vp*%r z&K7$OB309Vc+qnk;2>N_h)}rVZq2ivEWcItUP#Q?VlNpvf#AGDzm;@n?#ZBVi<~Tn zd*}rT;f;zRscoA6Kt0jY>mi@6zymz$6PC@*3Xezqft=GbCUkdSXa=Omh+KD)YF+BQ zac!EV*$Td}uOQj4QSt+60uj8z{76rSeQ{A-Zx{Yb^w0>g^vwUoKD^cHXX5b}=o#{DwyC~ESxe|IeKUSeE2R~X zoipF`aplxWEU?GA#QSOeSM9769M{I$Zt4oc9C{`LOL5Sa?`Btx%w$m7c&K3Tqr^%% z^O2BN=rR72>ThNuOT#mG+DYcCx79Wi58ZxNHj|}a zwMN*7h!*;=sl_tSXasn6_jv~(J4IZ6nfQX>Oa;IbA)y2>o*Ug)BM6u_z->hyUR7>$ zSB|0mO~^@rsbr}KP+w7h12N~@Q)2@%$4nHZ`azuL`9}w&pNmba~fdq9vkZ*fob~^wTPN>H=r&J~{!LFKTRzm8C_G zTqJVJ+dna_8HPPAJe1g2dsJ2|w^cf3I^_D~733nB@5of>!CiMdPcV(d6uaCGSbGr! z;;2dC3_Pctk*-@Ys@dKrDTc{$q<1jCo}oLe3*W!9#8|Xq4|E{$39f#lziRzfDwOSa zZ|n}_U;v#c0Oe+Tev-ie6P0 zDmmTEmU=0eYIu#vBhurpVRGJ%+ZwE7??%m0S1;sXEaJ$U;E2RL^{lX z*#gd0gGjYn=l+ojMZ_cS-Ny8xQmf%Nv0`XtqvOXp`}Ivs;|M(M)qAf4iaV8fmCpG0 zFAiWA1+lC>2wXp+8qfq@$1@x$Zuvn7P#VC9@UkvrBOu7UD&7CvLx@0%fIRa2&4RR+ zQNMH>7|LR`>!hyqV%}jwBoIqi1CHo()W%`qt2|Zb1-P+N;$z>~>@{tIl~;1!(XZ~{ z^SnObU9XFlnXj??blTnq(|LFsthIeiZg$&2U%;$NCz*YG|4KXwg( zgSdgSubuotpm&i545mO?s8gLRyAz4)@qVq#`rb zu3(_&S|daNpwYtPJFW`brghr7ooO|C08^#g|4x;r^MEq(UqS>y!nk8e7*-3WCwQ$g zJmM(<(fmUrPnY|1H}?^Jg_vR{gx&#}>fy_P4~4($3AA2+L0elax`pi+Z9DgLcQ@f) z!fd=fT-ZO%AHFF&s=50$gUZf%u5K+iV1(K_CKvJXXSDQtN?wN*^|dpOk?KN`D)p~m z`vFW?Vv4qVlFaPUp#7sY%hF|bY=@FNn(1}GdnL?6$y--BlkveN9n)Xk!3iwzXVJz> zV+#qqYOSe@fvawr(=n2u_IS6t%BSB@<6YI8kHF>v*5)_hAKOeFAt{XS+%FAS7sEI9o+1u~~wZ-!Y(&ge~svpMcx$>JeAkVJIdYH@R ztLRK3Ph?Azk=@k$`y88|HfelSW5b0z%{y-+YmCEFV{`NSZ!m2P5Pguq`%Y_KOS!3t zXg2$>OYcD+e4Spq zSA5EhQye}kd?r9+N_?P$-;UYfq;4b!CEvhYKvA{nG8ZoPW8Wo2M`YIt>K8;*glUFl zSkOfQz=Nyt%l-`lmmB`+{^kGJtNt&N9s7Ssb{@^gh7jG|2}+h5&e+Q*&JT{;%av(} zF$A${lsa_d>3`ZMM2JRO=C)e(B5*k!x7<4ihw%5^@7STun~}#Id=mY6v7T9PD;e&L z#d!1;qf$_V^{>=LPF)F8sstzw>&+Msh32SS;j0hwU`gSTE|jR zH1-OHRhBaPAe>X+%iqJVI}~d(-EcC*PKMUAC#j2c^T{<8o0fhjVssQ0GV|89{+g+_ ze%zw6J+LMF+c}pbO!C#*GBX!i<(|6}nsGnp(HmOkZAj?h@SQ{oOv`0=jUw%z)220+ zvFy)_8aB*k9KMjpvK5CkM{7EIHiEoe=ADC>ijREV2+v5ot8{eKOFBAvZ_ubC2DBt}L)S4}BRqa45*?XUU+ zL0iBRjv(6DZhUL}H!SaY!2;~yMy?2IgOk>g9q@TOB$a*VM^&R`#t^)DXZ2bu@>MO& zhAFS2Td>pe?X6f*H~+51=iu{zrYEE|R1pM&6rgy$SYQOk+R;F80*=c;)GoV+N$bO# zg367YT2L0}NA8z`^4UUw-?BuPNJ)o6bN9)-hs;4yy-ItQ_!G`+)QlH(VUQKlw$qL` zc5)YFHNtZ4UA(Uui=R^G{ht_ND32>q5iE(KF!=_Hy;T|OQre=ve@AB6mb%rAE_7bN z*0^FyOtMQGmD4C)nr%-COpJ3X5wMgGtFQEa9=+^!KIM3A*a|nBU2bon(VTXI4UH-&bWm20pKwr=CZu=`!8%B>7IK5pu|ct@vL77Vk5xWyZm|qf#3aC zSM(o>Lv~Ibgwulf%=`K>@fJ8mCtY~t0ky?1N2d3xo;bS{gtV4%z)ju@UwcuZo>*mb zp{X7`p}Wg*YO1=5KA7d{Yt{?b9^%@%Jblb|9v|e$RPg9XefgJeq-GNCk!pi_Q#(+R zDv_o%5>+XAN=2^zRTq)y0;pfnh#C5UCDJVkIPvRmL7qu70{Kjm1V1AX-oeXrT(Yb# zRrv7*^|$3;K#v5xXwuRbUoBhfIe1JE*A`(UQtuZP*8>6jQWF_5i5c%tO}Y={U5T8h zxb@vJkraL*#uI%s#b%-49tdg2c&|{e>})(jkh!yTb40*tN9lx5@xmSLcOG_7#wr!& zxWHwd@X4e3+#_{SRqQ)^K4TI{!l0SgUVq^&nW>opLav%7SwuTz&?SltrTnyblaj1A zPAj>OY6Gqeb7C0Z9UKT8`=WHblQE8Iz3!n?)qwSld)B|Gdht}b@ldaV@z7FQs-Zlq z#FORs-XjxGtX_20s}Xv9#tna$nu0nSb-sHG246t2?`bkZpB$u{kxbaA;~jh_?o7ac zGttp&w>v9#q;G$C(Y1MfyRbyv*;T~GLac<^pSJX}ws+Pg^XW71S$Rvnga;!Y|3`CX7S^ph-WvcVYFqe!xv(EZvY5U+KkW~1h zTW>L^z9RiY#CyTlq3G~bC`Q4<7wNJ$wGfd@zlL4n`#@3nV~2BzM5O87okEAKZ?LsU z6j#H#WK8PiL_dpbom6N%c5Cz-GNcYV)}d4Ok(|R9G3kLj3ctX@j(`!<3_*N?2jI*B}ps0=yYFUZT(XeLP^J`#`doSZ79F4JaO7I^F({8UhE;0 zH7-#=X0w5Za5jE2OWiez7t3=9&))$W?0aUUFR|gBw)i%Z?V$Y!H(`qLKO8Q}?)q_yo?zb^ zB5D5fs+Ih6QKZG-z$&{#c)W%Sn6C!-CenxG0N)`&8Axb^q1!CEPx9Mt@E^g+ zpVa(~bTT}6HJW7mphi_U=!ZRE1`6H{j;`NAKGY@p2v>S zti~`4Uf+>=tl*Mj+~UeUkth?!cdA(G_&4)*qZsFUjfmdC{nmBBLkOdHld?r8#p^VRb& zdv#9{fVLAZ`b`oDOGV53ruM4G7Fp*!fW}COZ}-|8o&Kd~c#WSv&4iK$Fx$$b0*nrJ z0f|wd?!S=me<=;%|56&7vq3*)i8?_)AF;i&*bQ{G@mwKMrosS8CM9(UpQ%`YdoScF zEme7S0@#g2SgZm#R!w3iZ_U{!BU+k1m0_QC*G?ZWOLHd88+9hEdZc)!BKPD{&cc)X zgAU#!#$^hCKsj4~+|)lrR?l{d-KGDdZ_OfXr~nvtgFU6p3;)BgTl))^i*Gk?z&j|< zb{`jU99P2%_1(PBDc}*uHrSBQ}h5^5OA>LTAg$m>)TBS zD#-rV2dQC8Ny$;eWGG~RoTx8+VF1fB$}&`EaC53%-C586BUt_y?11YIX^4RJ&86ss z4}6IIs~^Sn>zi&S?g9*lLhRS@{poi5>q`^w?(EX2G?>I`Kj6$k;cFpJ?G`ifcJyp| zsl!F>ZSl=(+i!GVYEH0q61%NO*EBj=P_PX6_`;78aHV2fJY6T^kR;tFWAzv^@9x&* z2RBCHcS{z)o~c68@&U=mc{o3LT)qHq;}JfTp{Ca(t3cS4x`Msavod)`5v~%YW+mP5 z=J)F>ktIUwlfI+^nm5@KO)Qf$C58u5ERCYhI^?qyDpOnF+v5U!R^F@V zF63Dk9X+?6Qa7~k+SSv=wyuS_WLRP<4OMXgsGZJ3&mWyh@BMWdw%!B&;;47Nx$0^xE26=tAKIgkQdkvz;P?Ec0l8lOkZIl!1d3?RwkmA>EbxhLh!mVhNmi9(SF_$_k71b+RxAVOQCUKB_!0o*^pNDJah zDn9pR1&FfjJZj78+#2M%^m`$x^^?qw$0a?9Gr%vde~U%1hySsxeDm@Wf~zO~;HZ(V z-a5zWVo&fFcGdq@NjJfhu3xGOu^4?LVvLBVboF zsk%SpNbVsf+i3rl;Ef7%7B;W+U2ts&N1vqkld9j(XYd7oLuCF>h{UkNIzlhbrcNT9 z`$T_MK5PsPr4(mBoqR9p`C+WE=+VWO*azMTDF_x|g;6Y9ZD%>H*i~RQXcI#vb|{g` zWwm5%canI1YgOWvQ}V7OUDX9$qKy>vFbDR*q4AJY5*B*uC}6ZcXZT488Moj%gC_ui|9RJYFy z;HN<^8-0rgjK&5nR*%%_8$sHFTSL4bXoNFD_q!Wd?YA<%1gEOK^L@Ba6+Zbr5!W9! zeUR?qFi~tU>vXlgQ1}DSuW5fHBc5HIne2wmc^Z)hkZYdI%`!D1w*fQI#niWL5{_dw zv+HM}MI+lsAz=#Mb$Wx}(qZs;v6sUw(6fvNXxO=IadH$dL#JlrI8ldud!b#{c@uIZ zg!gb+fcb0+$$eGHRalvnLL+GNw{Gk5+>k+YNsjF6ybNBq_cuNCZl|=Az5>ZJu%jwn z>CukKG{&e!=~!v0Ju>ggHK6ja8g?OiXNz?ssE>H|)ofpLlah4a*BZ07bbUK9zJ zn{6LGB0y;NX3H2|M^%kwY#b5m(EDk%QdH?8D;53PiW^yH(f)W-H$YMsNha(rx@^es zXO9*EiO*40UFw1oi%xQz z4%rb1M`BS@5D5GCj18kPLlv#i#Kj2--n}{MFN|JA)!TQJi5JJcH)UKo|1@V@7y26;tngfU@ANF zhYgLnjt%Ly^w)%g=Zl`dk0y>-PUqYGdh19V{#D`h2zpzTGCVL(jIe2~3N zf_sy=%A!TpBaSx3JMQ$CXu67R(N7j~`5=|BGCQ17+A9<-1V=}Ve0vO1blk7`!r@m< z3EO@AP`ZI)Z2tjFV`CWRkZ*N9NN&M#uL2S%`qEg{@*I<97VCtC2z~gz!HZ*nEI1aD zXym$N3Ao3RhCWU#jt?$!m=U2im&(x0zb{-8U=j(?WH_r$Cdn-{TIrs}_&(jg?}K-& z=~;=$zY(u8Y5v zqP%I9eN33lx5nD|k)_yAwk>5GLmYQl?p4A2f0NvR-xeQ(?suQ^U&d*pH}!?I1rC1V z3*fdbSTbHWFzv2%PDTY5$1LbtZSwubQ~HC6A8Qer(6jIejj=^dMCH-QvYOt@8!ueX z+~T3**^iGM3tct5&Yy`@lOER)p*DD(O6)z9_$dd>15RnigXGqbV@^6(KQrO9W}rq#mest=;CXZw0MkD8YwqX- znO^pH$*=$YdRj$VhS)>TD5IQimR-TfanpwdALQ+q;r9$pyLN5+J_69BpCM|3cBn9kXz$cJK(OJ!rL+u{YIzw;JpaSz*!F(^ z?68`b=g+4=#JPU#N}8wv4*Tij*_&|SXQLOdiuo^CQfrjhj;_Ez8h8tJNRN_=eYe~n z?CE_h~pi!OruyT6Buojf;XU@!*nr z0a#Z;C((n4`>R=Pv>BdEl(1mhFped|UMrB%eDxPM7G5~bw@$0@g4(^4r;{~KDVhi2 z4ZX-;nT$$@-xcdHrl&uZI152f)*86A!WfT);B5gM6sUw$wWi;hCtG2)Sw`Xn@@{;5cN1ovpogH%#>GyH>> zdzD`Pz}As1Z>mA_m84xMef^GVTbo^;kxn-7$?$%ofOe7`dhpi$OVK7VG}&g~Q*FNo zIOatJO@$i><@>jz9=LMzW=_sI+r=vx>v$P9zs_W3 zVXb^{Q_!c7tBy-Y5C*S>ShbzA=7rcBB=wv#z({mww@KQgf@j{%t^9MY`^x!w&Fnw+ ztU(X=RBe~i-zj19)G+<-|}v4nA^>CkEKSZU?FQ$FZ-y8_mf`EBl|{L_07Jh{0Nit zlBxQ!qy6Ls`^I_NEA^jp_Wyx&pzI z1IeC_Nzv_Dyy4#2xf^9SAu(_kdLgbj-5G6%DO%^BTwc^<7H%^143EX$sqdBD5~~F#q{936z_R9AY5_-OAgHo@6ko0A8mgXn1A! ztofBpFfuPCkM)<{bn2M?f`{kVf=1DU!E>%S$d@J1`2%huOG~Jx-)`k1Cs^z3q7}`h zm?ZhKZ$|m?g>R^MfEd&2KII}O?M%sm3tS$d{@tiXGsLa}dV>RW*VR`Eqe#6+okS1z zWABsq)RUb~%=$Gs6h&=eOZdv!HJS!Qp>~AB0iAL^oZRi-cQ?x0 z%w6esW9VOG_jgwR&heoslWAL5d!)|F4DZGa8&%&QF}z&)<9CUZ8H62bb= za`Wa@G{bLydCv2X*S=3?mdX*II33yPNXL!h_Bz||dUTY}y{(}GL5oV-#*&SZk4FXh z2FY=3O_NhZHb%p-Ovq(^p8g+;>je+TFa_px{eukbNeN<#vGly|LL{M&hdNJg@1oUYg!ED^;M63(<9bT4d2U zg8yY0j8|(lhOWF|A>+OgWU#)~u2~+8JwqSW@L$l0b-s{18LQe7b={zHzqd*y#XMF= zZ`@+)l$@ryU1C-h$F6x@@gFz+;X z|B9`LW_VpOvwU>{8^4_hiFRIJ>#2dsfB3ChpmUUQkvKivV+}T{)(v z5ut&9x4Mt5_T1-PoOB_cGBSEho{PEd9$N6=BsK-ld-O$0-vdW7&7rv9J2Ubq1!FGd z@$`2Oh}2)_-eRCO$=0bqlCnvsWUpj@uKI_Aw;|8Df-f#Su5kp?guoslnt;kvzxvbG zmC5E2_<3u#j*aD4q&623sqR7&`Z7DVt?x*Q_G^HSv>``l?gDi%+0Ukj>5BM0H4G^A3g1GqV2w0&Q` z6p0z`a;Ci9o2yX2sQcp;S<*4PK%U|_c)V5MLih~&qv(TR@RmE@a#;>I)UEzt==?)m z!YP|J=|ulz@wE9Eylisez!V(Q@S)Y6j>WDaIUTwz z^3F^0c5R=&OE~bisCi=D-j@6&igW|`QvJZ)QWR@&$XWb?f51T0vtOe4H*wnAmJp$E zDgM(~mfM+gyV0Ub9rdp_PV^rBeh52?&Eejc%hxu~KZJ?fgV}=wJU&_PCrvj#8g?y} z{PC=GXRxA3gk<8uHQSl0k))OVl7o^j>|Y%f37u{H@k4- zN^XdKk@|y6H0!)kh_ik7rU}EV4NUHID+)$#@N?B$y}OB+S^D)wZe)7|lZTE(acV18 z9Mki;JGeoUDO~<;a_oJV?M>Cr>oIRU;R(@ld6;{sD-?n~u2Fmt;^3UIfE>)}O)Xh? z*oOI4A*T)Xz8p&96D2ESrRvcG^@nulL9vY?p_VG;59Yq#DY;W}s`Sm_8uyz-`?-}` zzd^)ToQ}0BoUJ74YnpZ29-~bn7jyC#>Gfv@KHjxuc^Zi;w_VqpN|S$(8+cDqee7#x z`Hm68--Czn-qD1yKAZg>8+ZL%2j0WO*rc}dC`HQ7zr2dCk6X3Q(1nrKlO$A!Vbo2* z?R!3du6NqOp#&y(6R z8#`-L4{0*tPftyLiT|{P9AbxzqZ)v>y?!^=?;W9BGJMfnBiyU+0%EIueD~jQRPwwW z-)8gs zgG3lKttq)KZp2*q-e^G$B*${zWr)D_rPW6DB<~q8GD^CP(PmmcP$?UD>h)f&Vx&bq zW}SME&pQ^QjEPywuY6as1V_y9QLu=S%=QBnZ2iml@>jCQCCfmUwEbLIlhGOkB=r<@ zVY3yn(TRxn9MUIBq6}FC9K7Aq8((*))>GRZi#3sUW$WB8uAAvcr9M@BE( zKh|Et@m(xgCAaTY-J`cZX9l9vgYQ{o9U6}`KtF#lS;oXxiKbq0C1K zD-Ew(M;wS3c?YS1^1XYB?GIgl?nV~*8}RiSR>lX6&1`uN>1n&TiB}ifztbcw`hcZ2 zce$W?FnmHBF20ASEIULNmLQr|Fo73-r(Gxp`OeIBoLdT(tkcAkf7{)+M!=|#_6o(& zHL_&$fbr0Kqc#Pz@OzMYIc2p(COXVWh%9!=DzBMaa6Hv zcRsjy`h?`nbX$vG^IyKVz_q2}{N*3A^V6VThS`tBAL&GxUCgFbC^q_UyJoE6KEk(y zACwjC-)qw-@8&xdvS-TvP;SfkMDlqV?MtW1T&gw}@diAPu<(0*a2EC5B!^k8f0pIF zkCKrmpr#-;oe)`mzK_`@OM{)pG9xutSwTX|wUa|(9Y^v=BgY`csxf`s7oY=^$UC^G zk#kfTJ@}{!-8TFksC;x*XC@YmCao`2!lpdjV$hqon{oS_r6$`rusFfPlgTMaa9UMq zethv$J8FS3y{(;P=t^Lw6c@|&>yis{+9RI5B?=GFMk#yuVv;k7-CO=2TW=W_)gN|^ zA_ypmC`b&_5|RUmbSNsRAV}BHT?5js(k0!YfQWQA1JW(sIdl&L!vF)EjsBnWKJRtT zHyC2ihJDvs>t4U(MD#Jmi8OFrmv?oqok8ZeNoCRzzjOOP_heTK8zl|uO2dB7i8H`= z6nP}seqFB6A+kyMDBIk-j(@~c(5~KAkTN{g1eKlRI`wFt;Sh2|+hZ$PyFFy&sE|KEK%A60* zC%cLY>MD>5%)T4pTZcP&uQ(Nn zMd=lOWHY@wj{4N|>W4KKQej}iCkWb0k&o4vv_%a75Uy-@KMF1ZzvR>E{V@qZ25LzR zKV6Oq>rHngZxdb9@)y;3QJrZ^I-x zw!Xgab4DLT_LTQl1E`U2e0#-+%J0!CI2N-EY2CBeA&EvFJe9d%Pq+xaZhcXhqounq zS#V{RsK#Mv<{cR&cKK6G`^e1q@H)96|uV)JDUVC`0_EIXeKY>p< z6s#O=&rZ?p-URs+=qHZ}gLzS9SBBMevQcqjZ0+bt1#vkO{bz`zXFe60PDts5CFMw$ z(l=izu0JD=%Y&jxX38KHNcWxsv=Az-!L6gj<&qaC3XAT7T-x53IqP1$=8z}?9rbjU zR}NcS`a3d8&|{w+MdN*u+t3@E#N{*Eg(k9(ctzWtG1(a#xyCkaoz7&FSc7~G?vrzW z^l4|wtIS#=$#%g?xxPI%M<3vb~W2gnyVTMJ2ZOz|5}4eH~J($PpoEiS8=6(fZ? zi*Fx2PMYkokL3x|9+l{Gj>X0IpFHQ*&Old5xlZa} z8R414DOWO|$hdfXmvJs=^K#v#2vE5YcAu_3Fr5@->Wg}9_OCTf&K;0rX~75lB$CnJ=#muXTolc&X}IcswgV*~1ER85lpKzdXnJ?EXlqw5Nf^TFYr>MH4~66ko+jiK12So!4>3jP&0G2g7GZbg3Mi!r2hRPKdn zS7N4MJqoYYd0O3a!UT^R(o`{ab)`<#CX_WP^)IVo*_oNz`$3b(7EPvkX#VUikDOoM zql+0xTGgL|gyOB6R^5lzlMpajkh5;WL3ke5CEGQQ@R(wXn4y%mX!c)-2X#KGF7nAN<*(rvGjb? zZ6fCZ{IQ=%lDKR&B(6~N`a^?4!#W&GKA?tik4EWwdxP9Hoi019Oz}mfE%v5NKEfjU z;A_wZ5&kuh78T6ab;5^+cN!&rJWSr^3(Z`AQAhCSqu1T<=rBEqxdB~niGMJwwUTd9 zY=Ja)_%dHr{Nzd~-w8tKJ7rnH!wX7^CON3V#Zwg4LojtLorm%!iWl}3U3D9;0r|}C zyzk(^QtYe?###&w0Jr}tLC9szm}-vk`O-vj{DQ6~-q8_TVH_`sWQFPeyh34lq8|>q zInHw(@MTR^jYFOEQ)Dnw-;RAQivg!uv_KniOiGhLC$uu^@1&K7`9e_of^r)eb7T_w z-F1OS@TQbSeY*k8fUXb4yUyc{af9F4;gVaxNJbr$gZ({1QF!fujsy zw&PrrFoJySldf_?aSG^r6-Qt0Kypr#E;ArG#;(bzuHUO2DYW)V*_T#tmu_Iz+SurA z&cM_D7-3|mc-S%xJsaKs)c1+LXXm7+5ArFN%2H_4E6QH{0UCks6a0?V^mo`Fmikl{ z{bPMS8~32j#VHgKP;;o8PB4`W=J}Wvhu99vnB00}Z+Wi1V~d*HV7{;N=)0r1>FH?I z_k(NxF1rtURRz^XxHQ}@<2x;Bsj@kURPj#1SDp5-2Ty^6MIa{#>;P1pB+Pnn{%y4P9P+fHHc=yy#+;B#6ef$u}- z+5o*v$J6f5TD_%0+j;KRuUAAAefkR!O9esv;Bc2!$)SFDhfq3X;0b)SktuXc3jSb- zV7P+@Lk_oFwLXbDp+{gS9)R8;CqSM^Oh|TplJ%5K#E?jY`oX#iT*!Pik5zn#^!4+5 z3c6k5GPdn|=-Za%%w{Qp+N{A;IfI^+_(aR=G$gAmo<31=s{FaLa~h(fuTRzM2cCbj zH9SsLx%cRbTLrHQ`kZ*HQjD;gjeHJ~`s#qrlk1Nz-K{mxYLco7E~j3F#xex+(*i3+JSt z+Mt0`8qK+5!rXQHBq2r|9S2iGy4)$w-k-i^)M}Rab=f>necj&U!m@g4;hDuC;Xljb zVWx)Xq4AT5%~A7ubJjsbjBxZ2@VG0{*S*aV30rO|&tU;KiPieObpcQc0HoGG$L8yq zjN=5G3Eq6QGNt?W*$ekak*9m`Mf>R>8M(pXcH?L!RG%*w%Y8&t7rfl$i?Z*EXo*xE zmJL{4nN{Y3Gq8qZt&t0ur+&9Lzazca_<1wo3G)z(pWDqQ10es+Z_#*0Rg%ziu9>)_!`CkBEQ;3`_du^yiefeE%~` zN_6q`boa?MUSY9m>)P6qz&SUYvW(>V7ax2b{a#+5S0IY(RMU{lK}~W72w&*>!gJ^s$xFJv}c2u*zs#e)|?jR(}b6?}9jy<$c%XJW^2#Kff|B+~0T7GY<&l^XMWor-7M zf4oZkQ*wC?Ym7d2UZVBV4>Co($OX#RZNKdLX}ZF-#6dIO%aMHI+9l3$v;Gc>dZ=I> z{$bl8i>cPEFRG$)zdWaOzVw=JGwdxl4VW@K#A}v(4}$Y@^Z7taA`0qctzi%Dl*D2m zFWONvzb^3T8$C-&{v3_(3S1f32AWG^TIm)qe1in$9RvIGj-~_pOLfR&Sp;* zyh;qe>Cka+9k=gvdZ|WAUQ=ya;V|0(l*jzN-9i&YCw@wShz65!RVFXB_2EkoKW#(v zriC64O|wGwl4q&t_7Qwt;u5Ib;3~#Mw}T4HkK0oUCCAlG=({~`<(=rfOEWB< zHyu0)ftB1(OHT>|eX;amtj;;-c-QF)LcD@pdM{Cy9zJ0hV(mn5I(AqNoyMD$a2?r) zxYLU0vlxZoDppO0P+TZI@rlOX{*3$`cP;PbqpY;UjH(|UD?xM^i7F8eKaa{2GZli| z-DD^^yIUaspcdu#V+K*;s0R9y*1F=~0>ec4CMtJgMjTPJ&DhT0EX)564%~yaey2P> z__Dn+KYUoDq0 zS>OvK`>8?6+g?RpZDwlqrZIV!L=D29QbS*BT0?QC-5BP3{U@XOge3H<&6zgnf)toJ zM|rO-d?n2WRV{AjCxAyX?E~7y#-PNQB~Sa7x#)h_+hg^&bHs_dV@|#wLOw`n;ZC{a zG_{n$QpI^1k2>P*1$V*prh@_lE|Q^)kHltZf{c-L4qucFF~lwHZ}@Jb;|?Upp*6ll z$s5p2B_qhQ5KqtIECj}jYy$kVe=U7aeX4#>vhPq*Kg_Bir%!FY!>XGirP!PGO_M7!*5+|6CUw|S50rbvj!o5 zg?_4KOP?(LXrs=a`>JtW_H9MYXgua94f*Ed72&<1LE{*uHr@=Q5Z-OlojSWd?z4M{ zN7gNh;MlE01EniGCFE%7y2dsIv_0p^G4g%$)`ywTmMRA0ql|h{;9hc>^Q{ut6TytY zvc6i9oKA-73+vamDG&)ejibRmfDw`b>JZjrzdM-CBr z8N#omCzQ+8nl0-7MEfJf6wlqHLzC6_wDiek?x5bw9gX|pStII>^NG;TeO<*_ zdTc6nU+B|X&b&Vq4R}lvBY0Y#%D-Pf8EnWEnjs1)TL@FYp0ltvBqs%9Ub*uTH_;Z2 zL*KjHR(8CVTW}osSz~j7%PZHl=&NYcXXl;zYGmyTxeqAyzOCt$xi=IzM|sbxu23`UVwQToIozv&&_vkQzkl-A4f)K7&0C)$NpfqUPhN zBlZ9t2D!35lBg6Ux4(h8aICDTS&X08Mri@fv#`31¨z3&(vYQ*61RJt>ynrdq`czk;N?4JX}J?= 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