All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.koder95.eme.Version Maven / Gradle / Ivy

There is a newer version: 0.4.4
Show newest version
/*
 * Copyright (C) 2018 Kamil Jan Mularski [@koder95]
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */
package pl.koder95.eme;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Klasa umożliwia zarządzanie numerami wersji oprogramowania.
 * Klasa projektowana na podstawie
 * Wersjonowania semantycznego 2.0.0.
 *
 * @author Kamil Jan Mularski [@koder95]
 * @version 0.4.1, 2021-11-05
 * @since 0.2.0
 */
public class Version implements Comparable {

    private final int major, minor, patch;
    private final boolean preRelease;
    private final Collection identifiers, buildMetadata;

    /**
     * Tworzy nowy nr wersji z podanymi wartościami.
     *
     * @param major główna liczba, określa kompatybilność
     * @param minor liczba podporządkowana, określa rozszerzenie
     * @param patch liczba łatkowa, określa numer poprawki
     * @param preRelease określa, czy wersja jest przedpremierowa
     * @param identifiers identyfikatory wersji przedpremierowej
     * @param buildMetadata metadane build'u
     */
    public Version(int major, int minor, int patch, boolean preRelease,
            Collection identifiers,
            Collection buildMetadata) {
        if (major < 0 || minor < 0 || patch < 0)
            throw new IllegalArgumentException();
        else {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
            this.preRelease = preRelease;
            this.identifiers = identifiers;
            this.buildMetadata = buildMetadata;
        }
    }

    /**
     * Tworzy nowy nr wersji stabilnej z podanymi wartościami.
     *
     * @param major główna liczba, określa kompatybilność
     * @param minor liczba podporządkowana, określa rozszerzenie
     * @param patch liczba łatkowa, określa numer poprawki
     */
    public Version(int major, int minor, int patch) {
        this(major, minor, patch, false, Collections.emptyList(),
                Collections.emptyList());
    }

    @Override
    public final int compareTo(Version o) {
        /*
        Pierwszeństwo odnosi się do sposobu porównywania wersji między sobą
        podczas ich porządkowania.
        
        Pierwszeństwo MUSI być ustalane w rozdzieleniu wersji na identyfikatory
        major, minor, patch oraz identyfikator przedpremierowy w podanej
        kolejności (meta-dane buildu nie decydują o pierwszeństwie).
        */
        
        /*
        Pierwszeństwo jest ustalane przez pierwszą różnicę wykrytą podczas
        porównania każdego z identyfikatorów od lewej do prawej:
        wersje major, minor, patch są zawsze porównywane numerycznie.
        Przykład: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1.
        */
        if (major > o.major) return 1;
        if (major < o.major) return -1;
        // major'y są równe, więc:
        if (minor > o.minor) return 1;
        if (minor < o.minor) return -1;
        // major'y i minor'y są równe, więc:
        if (patch > o.patch) return 1;
        if (patch < o.patch) return -1;
        
        if (!preRelease && !o.preRelease) return 0;
        /*
        Gdy numery wersji major, minor i patch są równe, wydanie przedpremierowe
        poprzedza wersję standardową. Przykładowo: 1.0.0-alpha < 1.0.0.
        */
        if (preRelease && !o.preRelease) return -1;
        if (!preRelease && o.preRelease) return 1;
        /*
        Pierwszeństwo dwóch wydań przedpremierowych z takimi samymi numerami
        wersji major, minor i patch MUSI być ustalane przez porównywanie każdego
        z identyfikatorów rozdzielonych kropkami w kierunku od lewej do prawej,
        póki nie zostanie wykryta różnica w taki sposób:
        identyfikatory złożone z samych cyfr porównywane są numerycznie,
        a identyfikatory z literami lub dywizami porównywane są leksykalnie
        w kolejności ASCII.
        */
        if (identifiers == o.identifiers) return 0;
        
        Iterator i0 = identifiers.iterator(),
                i1 = o.identifiers.iterator();
        while (i0.hasNext() || i1.hasNext()) {
            if (i0.hasNext() && i1.hasNext()) {
                String firstS = i0.next(), secondS = i1.next();
                int firstI = -1, secondI = -1;
                try {
                    firstI = Integer.parseInt(firstS);
                    secondI = Integer.parseInt(secondS);
                } catch (NumberFormatException ex) {
                    // do nothing
                }
                /*
                Identyfikatory numeryczne zawsze poprzedzają
                identyfikatory nienumeryczne.
                */
                if (firstI >= 0) {
                    if (secondI >= 0) {
                        // porównywanie numeryczne:
                        if (firstI < secondI) return -1;
                        if (firstI > secondI) return 1;
                    } else return -1;
                } else {
                    if (secondI >= 0) return 1;
                    else {
                        // porównywanie alfabetyczne:
                        int compareTo = firstS.compareTo(secondS);
                        if (compareTo != 0)
                            return compareTo > 0? 1 : -1;
                    }
                }
            }
            /*
            Większy zbiór przedpremierowych pól poprzedza mniejszy zbiór,
            o ile wszystkie poprzedzające identyfikatory są sobie równe.
            Przykład:
            1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta
            < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
            */
            else if (!i0.hasNext() && i1.hasNext()) return -1;
            return 1;
        }
        return 0;
    }

    /**
     * @return metadane build'u
     */
    public Collection getBuildMetadata() {
        return buildMetadata;
    }

    /**
     * @return identyfikatory przedpremierowe - gdy jest to wersja
     * przedpremierowa, {@code null} - gdy wersja stabilna
     */
    public Collection getIdentifiers() {
        return isPreRelease()? identifiers : null;
    }

    /**
     * @return główna liczba, wskazuje na ciągłość idei i podejścia, które
     * gwarantuje kompatybilność rozszerzeń
     */
    public int getMajor() {
        return major;
    }

    /**
     * @return liczba podporządkowana, która definiuje ilość kompatybilnych
     * rozszerzeń
     */
    public int getMinor() {
        return minor;
    }

    /**
     * @return liczba łatkowa, ile było poprawek rozszerzenia
     */
    public int getPatch() {
        return patch;
    }

    /**
     * @return czy jest to wersja niestabilna, czyli przedpremierowa
     */
    public boolean isPreRelease() {
        return preRelease;
    }
    
    @Override
    public String toString() {
        String stable = major + "." + minor + "." + patch;
        if (preRelease) {
            StringBuilder builder = new StringBuilder(stable + "-");
            identifiers.forEach((i) -> builder.append(i).append('.'));
            builder.deleteCharAt(builder.length()-1);
            if (!buildMetadata.isEmpty()) {
                builder.append('+');
                buildMetadata.forEach((bMd) -> builder.append(bMd).append('.'));
                builder.deleteCharAt(builder.length()-1);
            }
            return builder.toString();
        } else return stable;
    }

    private static Version parse0(String str) {
        boolean isPreRelease = str.contains("-"),
                hasBuildMetadata = str.contains("+");
        if (hasBuildMetadata) {
            String[] mainParts = str.split(Pattern.quote("+"));
            Version r = parse0(mainParts[0]);
            if (r == null) return null;
            return new Version(r.major, r.minor, r.patch, r.preRelease, r.identifiers,
                    Arrays.asList(mainParts[1].split(Pattern.quote("."))));
        } else {
            if (isPreRelease) {
                // 0 - stable, 1 - pre-release:
                String[] nonBuildParts = str.split(Pattern.quote("-"));
                String[] preReleaseParts = nonBuildParts[1]
                        .split(Pattern.quote("."));
                Version r = parse0(nonBuildParts[0]);
                if (r == null) return null;
                return new Version(r.major, r.minor, r.patch, true, Arrays.asList(preReleaseParts),
                        Collections.emptyList());
            } else {
                String[] stableParts = str.split(Pattern.quote("."));
                for (String part : stableParts) {
                    if (!part.matches("(\\d)+")) {
                        return null;
                    }
                }
                return new Version(Integer.parseInt(stableParts[0]), Integer.parseInt(stableParts[1]), Integer.parseInt(stableParts[2]));
            }
        }
    }

    /**
     * Odczytuje wersję z ciągu znaków.
     *
     * @param str wersja zapisana w ciągu znaków
     * @return wersja
     */
    public static Version parse(String str) {
        return parse0(str == null? "0.0.0" : str.startsWith("v")? str.substring(1) : str);
    }

    /**
     * Odczytuje wersję z klasy, która zawiera statyczną metodę {@code get()}
     * zwracającą instancję klasy. Wartości odczytywane są za pomocą wywołania
     * metody {@link Object#toString()}.
     *
     * @param klasa klasa, która ma zostać odczytana
     * @return instancja klasy {@link Version} utworzona na podstawie podanej klasy,
     * {@code null} — gdy nie można odczytać wersji z podanej klasy
     */
    public static Version parse(Class klasa) {
        try {
            Method get = Arrays.stream(klasa.getMethods())
                    .reduce(null, (r, c) -> c.getName().equals("get")? c : r);

            if (get != null) return parse(get.invoke(null).toString());
            return null;
        } catch (IllegalAccessException | IllegalArgumentException
                | InvocationTargetException ex) {
            return null;
        }
    }

    /**
     * @return wersja tego programu
     */
    public static Version get() {
        return parse(Main.class.getPackage().getSpecificationVersion());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy