blob: 3090f46ac2ac8f69a0f39589d454b32bee8248db [file] [log] [blame]
/*
* 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
*
* 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 org.netbeans.modules.javascript.cdnjs;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Representation of a version suitable for sorting/comparison. There are
* no restrictions on the format of the version. Dot-separated versions
* of arbitrary length (4.1.3.2), textual versions (alpha, beta) and their
* mixtures (1.3.2-beta-b20) are all supported.
*
* @author Jan Stola
*/
public final class Version {
/**
* Set of suffixes that when appended to a version represent
* an earlier version than the original one. For example, {@code alpha}
* is a member of this set because {@code 1.0.3-alpha} is an earlier version
* than {@code 1.0.3}.
*/
private static final Set<String> PRE_VERSIONS = new HashSet<>();
static {
PRE_VERSIONS.add("pre"); // NOI18N
PRE_VERSIONS.add("dev"); // NOI18N
PRE_VERSIONS.add("alpha"); // NOI18N
PRE_VERSIONS.add("beta"); // NOI18N
PRE_VERSIONS.add("rc"); // NOI18N
}
/**
* Parses a textual representation of a version.
*
* @param versionName textual representation of a version.
* @return {@code Version} object that corresponds to the given
* textual representation.
*/
public static Version parse(String versionName) {
List<Fragment> list = new ArrayList<>();
list.add(new Fragment(0, "")); // NOI18N
boolean isNumber = false; // Are we parsing number fragment?
boolean isText = false; // Are we parsing text fragment?
long number = 0;
StringBuilder textBuilder = new StringBuilder();
// The trailing dot ensures that the last fragment
// is ended and added into the list
String version = versionName + "."; // NOI18N
for (int i=0; i<version.length(); i++) {
char c = version.charAt(i);
boolean digit = ('0' <= c && c <= '9');
boolean letter = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
if (isNumber && !digit) {
// End of number fragment
isNumber = false;
list.add(new Fragment(number, "")); // NOI18N
number = 0;
} else if (isText && !letter) {
// End of text fragment
isText = false;
String text = textBuilder.toString();
if (PRE_VERSIONS.contains(text.toLowerCase())) {
Fragment previous = list.get(list.size()-1);
list.set(list.size()-1, new Fragment(previous.getNumber()-1, previous.getText()));
list.add(new Fragment(Long.MAX_VALUE, text));
} else {
list.add(new Fragment(0, text));
}
textBuilder.setLength(0);
}
if (digit) {
isNumber = true;
number *= 10;
number += (c - '0');
} else if (letter) {
isText = true;
textBuilder.append(c);
}
}
return new Version(list);
}
/** Fragments of this version. */
private final Fragment[] fragments;
/**
* Creates a new {@code Version} from the given list of version fragments.
*
* @param fragmentList list of fragments that represent the version.
*/
private Version(List<Fragment> fragmentList) {
fragments = fragmentList.toArray(new Fragment[fragmentList.size()]);
}
/**
* Returns the length of this version (i.e. number of fragments this
* version consists of).
*
* @return number of fragments this version consists of.
*/
private int getFragmentCount() {
return fragments.length;
}
/**
* Returns the version fragment on the specified position.
*
* @param index index of the fragment to return.
* @return fragment on the specified position.
*/
private Fragment getFragment(int index) {
return fragments[index];
}
/**
* Version fragment. Version fragment is a part of a version. It represents
* either a list of consecutive digits or a list of consecutive letters
* in the textual representation of the version. Note that neither
* the {@code number} property nor {@code text} property of this objects
* matches necessarily to the represented section of the version.
*/
private static final class Fragment {
/** Numerical value of this fragment. */
private final long number;
/** Textual value of this fragment. */
private final String text;
/**
* Creates a new version fragment.
*
* @param number numerical value of the fragment.
* @param text textual value of the fragment.
*/
Fragment(long number, String text) {
assert text != null;
this.number = number;
this.text = text;
}
/**
* Returns the numerical value of the fragment.
*
* @return numerical value of the fragment.
*/
long getNumber() {
return number;
}
/**
* Returns the textual value of the fragment.
*
* @return textual value of the fragment.
*/
String getText() {
return text;
}
@Override
public String toString() {
return (text == null) ? Long.toString(number) : "(" + number + "," + text + ")"; // NOI18N
}
}
/**
* Comparator of {@code Version} objects.
*/
public static final class Comparator implements java.util.Comparator<Version> {
/** Instance of the comparator that sorts the versions in an ascending order. */
private static final java.util.Comparator<Version> ASCENDING_INSTANCE = new Comparator();
/** Instance of the comparator that sorts the version in a descending order. */
private static final java.util.Comparator<Version> DESCENDING_INSTANCE = new java.util.Comparator<Version>() {
@Override
public int compare(Version version1, Version version2) {
return ASCENDING_INSTANCE.compare(version2, version1);
}
};
/**
* Returns a shared instance of the comparator.
*
* @param ascending if {@code true} then the returned comparator
* sorts the versions in an ascending order, it sorts the versions
* in a descending order otherwise.
* @return shared instance of the comparator.
*/
public static java.util.Comparator<Version> getInstance(boolean ascending) {
return ascending ? ASCENDING_INSTANCE : DESCENDING_INSTANCE;
}
@Override
public int compare(Version version1, Version version2) {
int minLength = Math.min(version1.getFragmentCount(), version2.getFragmentCount());
for (int i=0; i<minLength; i++) {
Fragment fragment1 = version1.getFragment(i);
Fragment fragment2 = version2.getFragment(i);
long number1 = fragment1.getNumber();
long number2 = fragment2.getNumber();
if (number1 < number2) {
return -1;
} else if (number1 > number2) {
return 1;
} else {
// Numerical values are equal => compare the textual values
int textDiff = fragment1.getText().compareTo(fragment2.getText());
if (textDiff != 0) {
return textDiff;
}
}
}
return version1.getFragmentCount() - version2.getFragmentCount();
}
}
}