001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.util.version;
020
021import java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Optional;
026
027/**
028 * The recognized generic version qualifiers. Qualifiers may apply a "shift" to versions (ie when sorting), if present.
029 *
030 * @since 2.0.17
031 */
032public final class GenericQualifiers {
033    private GenericQualifiers() {}
034
035    public static final String LABEL_ALPHA = "alpha";
036
037    public static final String LABEL_BETA = "beta";
038
039    public static final String LABEL_MILESTONE = "milestone";
040
041    public static final Integer QUALIFIER_ALPHA = -5;
042
043    public static final Integer QUALIFIER_BETA = -4;
044
045    public static final Integer QUALIFIER_MILESTONE = -3;
046
047    public static final Integer QUALIFIER_RC = -2;
048
049    public static final Integer QUALIFIER_SNAPSHOT = -1;
050
051    public static final Integer QUALIFIER_ZERO = 0;
052
053    public static final Integer QUALIFIER_SP = 1;
054
055    private static final Map<String, Integer> QUALIFIERS;
056
057    static {
058        Map<String, Integer> qualifiers = new LinkedHashMap<>();
059        qualifiers.put(LABEL_ALPHA, QUALIFIER_ALPHA);
060        qualifiers.put(LABEL_BETA, QUALIFIER_BETA);
061        qualifiers.put(LABEL_MILESTONE, QUALIFIER_MILESTONE);
062        qualifiers.put("rc", QUALIFIER_RC);
063        qualifiers.put("cr", QUALIFIER_RC);
064        qualifiers.put("snapshot", QUALIFIER_SNAPSHOT);
065        qualifiers.put("ga", QUALIFIER_ZERO);
066        qualifiers.put("final", QUALIFIER_ZERO);
067        qualifiers.put("release", QUALIFIER_ZERO);
068        qualifiers.put("sp", QUALIFIER_SP);
069        QUALIFIERS = Collections.unmodifiableMap(qualifiers);
070    }
071
072    /**
073     * Returns qualifier (an {@link Integer} for given token, if detected. This method is used in {@link GenericVersion}
074     * that tokenizes version, and uses string token in call. The input must have {@link String#toLowerCase(Locale)}
075     * applied.
076     */
077    static Optional<Integer> tokenQualifier(String token) {
078        return Optional.ofNullable(QUALIFIERS.get(token));
079    }
080
081    /**
082     * Returns qualifier (an {@link Integer} for given string, if detected. Qualifier, if present, defines "shift",
083     * if negative, it is a "preview version" (e.g. alpha, beta, milestone, release candidate or snapshot), if zero,
084     * it is a "final version" (e.g. ga, final, release), if positive, it is a "service pack" version (e.g. sp).
085     * If no qualifier is detected, an empty optional is returned.
086     *
087     * @param token the string to analyze for qualifier, must not be {@code null}
088     */
089    public static Optional<Integer> qualifier(String token) {
090        // most trivial preview version is "a1"
091        if (token.length() > 1) {
092            String v = token.toLowerCase(Locale.ENGLISH);
093            // simple case: full qualifier label is present (assuming once)
094            for (Map.Entry<String, Integer> entry : QUALIFIERS.entrySet()) {
095                String label = entry.getKey();
096                int pos = v.indexOf(label);
097                if (pos > -1
098                        && (pos == 0 || !Character.isLetter(v.charAt(pos - 1)))
099                        && (pos >= v.length() - label.length()
100                                || !Character.isLetter(v.charAt(pos + label.length())))) {
101                    // it must be surrounded by "transition" (non-char; to avoid "rc" detection in "1.0-arc")
102                    return Optional.of(entry.getValue());
103                }
104            }
105            // complex case: contains 'a', 'b' or 'm' followed immediately by number
106            for (char ch : new char[] {'a', 'b', 'm'}) {
107                int idx = v.lastIndexOf(ch);
108                if (idx > -1 && v.length() > idx + 1) {
109                    if (Character.isDigit(v.charAt(idx + 1))) {
110                        if (ch == 'a') {
111                            return Optional.of(QUALIFIER_ALPHA);
112                        } else if (ch == 'b') {
113                            return Optional.of(QUALIFIER_BETA);
114                        } else {
115                            return Optional.of(QUALIFIER_MILESTONE);
116                        }
117                    }
118                }
119            }
120        }
121        return Optional.empty();
122    }
123}