VersionSupport.java

package network.ike.workspace;

/**
 * Version manipulation for IKE workspace conventions.
 *
 * <p>Handles SNAPSHOT suffixes, branch-qualified versions, checkpoint
 * versions, and release version derivation. Extracted from
 * {@code ReleaseSupport} in ike-maven-plugin so these operations are
 * testable without Maven dependencies.
 */
public final class VersionSupport {

    private VersionSupport() {}

    /**
     * Strip {@code -SNAPSHOT} from a version string.
     *
     * @param version e.g. "1.1.0-SNAPSHOT" or "1.1.0-my-feature-SNAPSHOT"
     * @return e.g. "1.1.0" or "1.1.0-my-feature"
     */
    public static String stripSnapshot(String version) {
        return version.endsWith("-SNAPSHOT")
                ? version.substring(0, version.length() - "-SNAPSHOT".length())
                : version;
    }

    /**
     * Derive the release version from a SNAPSHOT version.
     * Equivalent to {@link #stripSnapshot(String)}.
     *
     * @param snapshotVersion version to strip
     * @return release version without -SNAPSHOT suffix
     */
    public static String deriveReleaseVersion(String snapshotVersion) {
        return stripSnapshot(snapshotVersion);
    }

    /**
     * Derive the next SNAPSHOT version by incrementing the last numeric
     * segment. {@code "2"} becomes {@code "3-SNAPSHOT"};
     * {@code "1.1.0"} becomes {@code "1.1.1-SNAPSHOT"}.
     *
     * @param releaseVersion a version without -SNAPSHOT
     * @return the next SNAPSHOT version
     */
    public static String deriveNextSnapshot(String releaseVersion) {
        String base = stripSnapshot(releaseVersion);
        int lastDot = base.lastIndexOf('.');
        if (lastDot >= 0) {
            String prefix = base.substring(0, lastDot + 1);
            String last = base.substring(lastDot + 1);
            return prefix + (Integer.parseInt(last) + 1) + "-SNAPSHOT";
        }
        // Simple integer version (e.g., "2" -> "3-SNAPSHOT")
        return (Integer.parseInt(base) + 1) + "-SNAPSHOT";
    }

    /**
     * Transform a branch name into a safe directory/version qualifier.
     * Replaces {@code /} with {@code -}.
     *
     * @param branch e.g. "feature/shield-terminology"
     * @return e.g. "feature-shield-terminology"
     */
    public static String safeBranchName(String branch) {
        String stripped = branch;
        for (String prefix : new String[]{"feature/", "hotfix/", "bugfix/", "release/"}) {
            if (stripped.startsWith(prefix)) {
                stripped = stripped.substring(prefix.length());
                break;
            }
        }
        return stripped.replace('/', '-');
    }

    /**
     * Derive a branch-qualified SNAPSHOT version.
     *
     * <p>Given base version {@code "1.2.0-SNAPSHOT"} and branch
     * {@code "feature/my-work"}, returns {@code "1.2.0-my-work-SNAPSHOT"}.
     *
     * <p>If the branch is "main", returns the base version unchanged.
     *
     * @param baseVersion the unqualified version (with or without -SNAPSHOT)
     * @param branch      the git branch name
     * @return the branch-qualified SNAPSHOT version
     */
    public static String branchQualifiedVersion(String baseVersion, String branch) {
        if ("main".equals(branch)) {
            return baseVersion.endsWith("-SNAPSHOT")
                    ? baseVersion
                    : baseVersion + "-SNAPSHOT";
        }
        String base = stripSnapshot(baseVersion);
        // Strip any existing branch qualifier: take only the numeric prefix
        String numericBase = extractNumericBase(base);
        return numericBase + "-" + safeBranchName(branch) + "-SNAPSHOT";
    }

    /**
     * Extract the numeric base version, stripping any branch qualifier.
     * {@code "1.2.0-my-feature"} becomes {@code "1.2.0"};
     * {@code "1.2.0"} is unchanged.
     *
     * @param version version string possibly containing a branch qualifier
     * @return numeric portion only
     */
    public static String extractNumericBase(String version) {
        // Find the first '-' that follows a digit and precedes a letter
        for (int i = 1; i < version.length(); i++) {
            if (version.charAt(i) == '-' && Character.isDigit(version.charAt(i - 1))
                    && i + 1 < version.length()
                    && Character.isLetter(version.charAt(i + 1))) {
                return version.substring(0, i);
            }
        }
        return version;
    }

    /**
     * Check whether a version string is a SNAPSHOT.
     *
     * @param version version to check
     * @return true if version ends with -SNAPSHOT
     */
    public static boolean isSnapshot(String version) {
        return version != null && version.endsWith("-SNAPSHOT");
    }

    /**
     * Check whether a version string is branch-qualified
     * (has a non-numeric qualifier before -SNAPSHOT).
     *
     * @param version version to check
     * @return true if version contains a branch qualifier
     */
    public static boolean isBranchQualified(String version) {
        if (!isSnapshot(version)) return false;
        String base = stripSnapshot(version);
        return !base.equals(extractNumericBase(base));
    }
}