| /* |
| * 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(); |
| } |
| |
| } |
| |
| } |