| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed 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 |
| * |
| * http://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. |
| */ |
| |
| package com.android.sdklib.repository; |
| |
| import com.android.annotations.NonNull; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| |
| /** |
| * Package multi-part revision number composed of a tuple |
| * (major.minor.micro) and an optional preview revision |
| * (the lack of a preview number indicates it's not a preview |
| * but a final package.) |
| * |
| * @see MajorRevision |
| */ |
| public class FullRevision implements Comparable<FullRevision> { |
| |
| public static final int MISSING_MAJOR_REV = 0; |
| public static final int IMPLICIT_MINOR_REV = 0; |
| public static final int IMPLICIT_MICRO_REV = 0; |
| public static final int NOT_A_PREVIEW = 0; |
| |
| /** Only major revision specified: 1 term */ |
| protected static final int PRECISION_MAJOR = 1; |
| /** Only major and minor revisions specified: 2 terms (x.y) */ |
| protected static final int PRECISION_MINOR = 2; |
| /** Major, minor and micro revisions specified: 3 terms (x.y.z) */ |
| protected static final int PRECISION_MICRO = 3; |
| /** Major, minor, micro and preview revisions specified: 4 terms (x.y.z-rcN) */ |
| protected static final int PRECISION_PREVIEW = 4; |
| |
| public static final FullRevision NOT_SPECIFIED = new FullRevision(MISSING_MAJOR_REV); |
| |
| private static final Pattern FULL_REVISION_PATTERN = |
| // 1=major 2=minor 3=micro 4=separator 5=previewType 6=preview |
| Pattern.compile("\\s*([0-9]+)(?:\\.([0-9]+)(?:\\.([0-9]+))?)?([\\s-]*)?(?:(rc|alpha|beta)([0-9]+))?\\s*"); |
| |
| protected static final String DEFAULT_SEPARATOR = " "; |
| |
| private final int mMajor; |
| private final int mMinor; |
| private final int mMicro; |
| private final int mPreview; |
| private final String mPreviewSeparator; |
| private final PreviewType mPreviewType; |
| |
| public enum PreviewType { |
| ALPHA("alpha"), |
| BETA("beta"), |
| RC("rc") |
| ; |
| |
| final String name; |
| |
| PreviewType(String name) { |
| this.name = name; |
| } |
| } |
| |
| public FullRevision(int major) { |
| this(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV); |
| } |
| |
| public FullRevision(int major, int minor, int micro) { |
| this(major, minor, micro, NOT_A_PREVIEW); |
| } |
| |
| public FullRevision(int major, int minor, int micro, int preview) { |
| this(major, minor, micro, PreviewType.RC, preview, DEFAULT_SEPARATOR); |
| } |
| |
| public FullRevision(int major, int minor, int micro, @NonNull PreviewType previewType, |
| int preview, @NonNull String previewSeparator) { |
| mMajor = major; |
| mMinor = minor; |
| mMicro = micro; |
| mPreview = preview; |
| mPreviewSeparator = previewSeparator; |
| mPreviewType = previewType; |
| } |
| |
| public int getMajor() { |
| return mMajor; |
| } |
| |
| public int getMinor() { |
| return mMinor; |
| } |
| |
| public int getMicro() { |
| return mMicro; |
| } |
| |
| @NonNull |
| protected String getSeparator() { |
| return mPreviewSeparator; |
| } |
| |
| public boolean isPreview() { |
| return mPreview > NOT_A_PREVIEW; |
| } |
| |
| public int getPreview() { |
| return mPreview; |
| } |
| |
| /** |
| * Parses a string of format "major.minor.micro rcPreview" and returns |
| * a new {@link FullRevision} for it. All the fields except major are |
| * optional. |
| * <p/> |
| * The parsing is equivalent to the pseudo-BNF/regexp: |
| * <pre> |
| * Major/Minor/Micro/Preview := [0-9]+ |
| * Revision := Major ('.' Minor ('.' Micro)? )? \s* ('rc'Preview)? |
| * </pre> |
| * |
| * @param revision A non-null revision to parse. |
| * @return A new non-null {@link FullRevision}. |
| * @throws NumberFormatException if the parsing failed. |
| */ |
| @NonNull |
| public static FullRevision parseRevision(@NonNull String revision) |
| throws NumberFormatException { |
| return parseRevisionImpl(revision, true /*supportMinorMicro*/, true /*supportPreview*/, |
| false /*keepPrevision*/); |
| } |
| |
| @NonNull |
| protected static FullRevision parseRevisionImpl(@NonNull String revision, |
| boolean supportMinorMicro, |
| boolean supportPreview, |
| boolean keepPrecision) |
| throws NumberFormatException { |
| if (revision == null) { |
| throw new NumberFormatException("revision is <null>"); //$NON-NLS-1$ |
| } |
| |
| Throwable cause = null; |
| String error = null; |
| try { |
| Matcher m = FULL_REVISION_PATTERN.matcher(revision); |
| if (m != null && m.matches()) { |
| int major = Integer.parseInt(m.group(1)); |
| |
| int minor = IMPLICIT_MINOR_REV; |
| int micro = IMPLICIT_MICRO_REV; |
| int preview = NOT_A_PREVIEW; |
| int precision = PRECISION_MAJOR; |
| String previewSeparator = " "; |
| PreviewType previewType = PreviewType.RC; |
| |
| String s = m.group(2); |
| if (s != null) { |
| if (!supportMinorMicro) { |
| error = " -- Minor number not supported"; //$NON-NLS-1$ |
| } else { |
| minor = Integer.parseInt(s); |
| precision = PRECISION_MINOR; |
| } |
| } |
| |
| s = m.group(3); |
| if (s != null) { |
| if (!supportMinorMicro) { |
| error = " -- Micro number not supported"; //$NON-NLS-1$ |
| } else { |
| micro = Integer.parseInt(s); |
| precision = PRECISION_MICRO; |
| } |
| } |
| |
| s = m.group(6); |
| if (s != null) { |
| if (!supportPreview) { |
| error = " -- Preview number not supported"; //$NON-NLS-1$ |
| } else { |
| preview = Integer.parseInt(s); |
| previewSeparator = m.group(4); |
| precision = PRECISION_PREVIEW; |
| |
| String previewTypeName = m.group(5); |
| for (PreviewType pt : PreviewType.values()) { |
| if (pt.name.equals(previewTypeName)) { |
| previewType = pt; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (error == null) { |
| if (keepPrecision) { |
| return new PreciseRevision(major, minor, micro, preview, precision, |
| previewSeparator); |
| } else { |
| return new FullRevision(major, minor, micro, previewType, preview, previewSeparator); |
| } |
| } |
| } |
| } catch (Throwable t) { |
| cause = t; |
| } |
| |
| NumberFormatException n = new NumberFormatException( |
| "Invalid revision: " //$NON-NLS-1$ |
| + revision |
| + (error == null ? "" : error)); |
| if (cause != null) { |
| n.initCause(cause); |
| } |
| throw n; |
| } |
| |
| /** |
| * Returns the version in a fixed format major.minor.micro |
| * with an optional "rc preview#". For example it would |
| * return "18.0.0", "18.1.0" or "18.1.2 rc5". |
| */ |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(mMajor) |
| .append('.').append(mMinor) |
| .append('.').append(mMicro); |
| |
| if (mPreview != NOT_A_PREVIEW) { |
| sb.append(mPreviewSeparator).append(mPreviewType.name).append(mPreview); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns the version in a dynamic format "major.minor.micro rc#". |
| * This is similar to {@link #toString()} except it omits minor, micro |
| * or preview versions when they are zero. |
| * For example it would return "18 rc1" instead of "18.0.0 rc1", |
| * or "18.1 rc2" instead of "18.1.0 rc2". |
| */ |
| public String toShortString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(mMajor); |
| if (mMinor > 0 || mMicro > 0) { |
| sb.append('.').append(mMinor); |
| } |
| if (mMicro > 0) { |
| sb.append('.').append(mMicro); |
| } |
| if (mPreview != NOT_A_PREVIEW) { |
| sb.append(mPreviewSeparator).append(mPreviewType.name).append(mPreview); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns the version number as an integer array, in the form |
| * [major, minor, micro] or [major, minor, micro, preview]. |
| * |
| * This is useful to initialize an instance of |
| * {@code org.apache.tools.ant.util.DeweyDecimal} using a |
| * {@link FullRevision}. |
| * |
| * @param includePreview If true the output will contain 4 fields |
| * to include the preview number (even if 0.) If false the output |
| * will contain only 3 fields (major, minor and micro.) |
| * @return A new int array, never null, with either 3 or 4 fields. |
| */ |
| public int[] toIntArray(boolean includePreview) { |
| int size = includePreview ? 4 : 3; |
| int[] result = new int[size]; |
| result[0] = mMajor; |
| result[1] = mMinor; |
| result[2] = mMicro; |
| if (result.length > 3) { |
| result[3] = mPreview; |
| } |
| return result; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + mMajor; |
| result = prime * result + mMinor; |
| result = prime * result + mMicro; |
| result = prime * result + mPreview; |
| result = prime * result + mPreviewType.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object rhs) { |
| if (this == rhs) { |
| return true; |
| } |
| if (rhs == null) { |
| return false; |
| } |
| if (!(rhs instanceof FullRevision)) { |
| return false; |
| } |
| FullRevision other = (FullRevision) rhs; |
| if (mMajor != other.mMajor) { |
| return false; |
| } |
| if (mMinor != other.mMinor) { |
| return false; |
| } |
| if (mMicro != other.mMicro) { |
| return false; |
| } |
| if (mPreview != other.mPreview) { |
| return false; |
| } |
| if (mPreviewType != other.mPreviewType) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Trivial comparison of a version, e.g 17.1.2 < 18.0.0. |
| * |
| * Note that preview/release candidate are released before their final version, |
| * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the |
| * lack of preview number was "+inf": |
| * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0" |
| * and more than "18.1.2.4" |
| * |
| * @param rhs The right-hand side {@link FullRevision} to compare with. |
| * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs. |
| */ |
| @Override |
| public int compareTo(FullRevision rhs) { |
| return compareTo(rhs, PreviewComparison.COMPARE_NUMBER); |
| } |
| |
| /** |
| * Trivial comparison of a version, e.g 17.1.2 < 18.0.0. |
| * |
| * Note that preview/release candidate are released before their final version, |
| * so "18.0.0 rc1" comes below "18.0.0". The best way to think of it as if the |
| * lack of preview number was "+inf": |
| * "18.1.2 rc5" => "18.1.2.5" so its less than "18.1.2.+INF" but more than "18.1.1.0" |
| * and more than "18.1.2.4" |
| * |
| * @param rhs The right-hand side {@link FullRevision} to compare with. |
| * @param comparePreview How to compare the preview value. |
| * @return <0 if lhs < rhs; 0 if lhs==rhs; >0 if lhs > rhs. |
| */ |
| public int compareTo(FullRevision rhs, PreviewComparison comparePreview) { |
| int delta = mMajor - rhs.mMajor; |
| if (delta != 0) { |
| return delta; |
| } |
| |
| delta = mMinor - rhs.mMinor; |
| if (delta != 0) { |
| return delta; |
| } |
| |
| delta = mMicro - rhs.mMicro; |
| if (delta != 0) { |
| return delta; |
| } |
| |
| int p1, p2; |
| switch (comparePreview) { |
| case IGNORE: |
| // Nothing to compare. |
| break; |
| |
| case COMPARE_NUMBER: |
| if (!mPreviewType.equals(rhs.mPreviewType)) { |
| return mPreviewType.compareTo(rhs.mPreviewType); |
| } |
| |
| p1 = mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : mPreview; |
| p2 = rhs.mPreview == NOT_A_PREVIEW ? Integer.MAX_VALUE : rhs.mPreview; |
| delta = p1 - p2; |
| break; |
| |
| case COMPARE_TYPE: |
| p1 = mPreview == NOT_A_PREVIEW ? 1 : 0; |
| p2 = rhs.mPreview == NOT_A_PREVIEW ? 1 : 0; |
| delta = p1 - p2; |
| break; |
| } |
| return delta; |
| } |
| |
| /** Indicates how to compare the preview field in |
| * {@link FullRevision#compareTo(FullRevision, PreviewComparison)} */ |
| public enum PreviewComparison { |
| /** Both revisions must have exactly the same preview number. */ |
| COMPARE_NUMBER, |
| /** Both revisions must have the same preview type (both must be previews |
| * or both must not be previews, but the actual number is irrelevant.) |
| * This is the most typical choice used to find updates of the same type. */ |
| COMPARE_TYPE, |
| /** The preview field is ignored and not used in the comparison. */ |
| IGNORE |
| } |
| |
| |
| } |