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

org.apache.brooklyn.util.text.VersionComparator Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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.apache.brooklyn.util.text;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;

import org.apache.brooklyn.util.text.NaturalOrderComparator;
import org.apache.brooklyn.util.text.Strings;

import com.google.common.annotations.VisibleForTesting;

/**
 * {@link Comparator} for version strings.
 * 

* SNAPSHOT items always lowest rated, * then splitting on dots, * using natural order comparator (so "9" < "10" and "4u8" < "4u20"), * and preferring segments without qualifiers ("4" > "4beta"). *

* Impossible to follow semantics for all versioning schemes but * does the obvious right thing for normal schemes * and pretty well in fringe cases. *

* See test case for lots of examples. */ public class VersionComparator implements Comparator { private static final String SNAPSHOT = "SNAPSHOT"; public static final VersionComparator INSTANCE = new VersionComparator(); public static VersionComparator getInstance() { return INSTANCE; } @Override public int compare(String v1, String v2) { if (v1==null && v2==null) return 0; if (v1==null) return -1; if (v2==null) return 1; boolean isV1Snapshot = v1.toUpperCase().contains(SNAPSHOT); boolean isV2Snapshot = v2.toUpperCase().contains(SNAPSHOT); if (isV1Snapshot == isV2Snapshot) { // if snapshot status is the same, look at dot-split parts first return compareDotSplitParts(splitOnDot(v1), splitOnDot(v2)); } else { // snapshot goes first return isV1Snapshot ? -1 : 1; } } @VisibleForTesting static String[] splitOnDot(String v) { return v.split("(?<=\\.)|(?=\\.)"); } private int compareDotSplitParts(String[] v1Parts, String[] v2Parts) { for (int i = 0; ; i++) { if (i >= v1Parts.length && i >= v2Parts.length) { // end of both return 0; } if (i == v1Parts.length) { // sequence depends whether the extra part *starts with* a number // ie // 2.0 < 2.0.0 // and // 2.0.qualifier < 2.0 < 2.0.0qualifier < 2.0.0-qualifier < 2.0.0.qualifier < 2.0.0 < 2.0.9-qualifier return isNumberInFirstCharPossiblyAfterADot(v2Parts, i) ? -1 : 1; } if (i == v2Parts.length) { // as above but inverted return isNumberInFirstCharPossiblyAfterADot(v1Parts, i) ? 1 : -1; } // not at end; compare this dot split part int result = compareDotSplitPart(v1Parts[i], v2Parts[i]); if (result!=0) return result; } } private int compareDotSplitPart(String v1, String v2) { String[] v1Parts = splitOnNonWordChar(v1); String[] v2Parts = splitOnNonWordChar(v2); for (int i = 0; ; i++) { if (i >= v1Parts.length && i >= v2Parts.length) { // end of both return 0; } if (i == v1Parts.length) { // shorter set always wins here; i.e. // 1-qualifier < 1 return 1; } if (i == v2Parts.length) { // as above but inverted return -1; } // not at end; compare this dot split part String v1p = v1Parts[i]; String v2p = v2Parts[i]; if (v1p.equals(v2p)) continue; if (isNumberInFirstChar(v1p) || isNumberInFirstChar(v2p)) { // something starting with a number is higher than something not if (!isNumberInFirstChar(v1p)) return -1; if (!isNumberInFirstChar(v2p)) return 1; // both start with numbers; can use natural order comparison *unless* // one is purely a number AND the other *begins* with that number, // followed by non-digit chars, in which case prefer the pure number // ie: // 1beta < 1 // but note // 1 < 2beta < 11beta if (isNumber(v1p) || isNumber(v2p)) { if (!isNumber(v1p)) { if (v1p.startsWith(v2p)) { if (!isNumberInFirstChar(Strings.removeFromStart(v1p, v2p))) { // v2 is a number, and v1 is the same followed by non-numbers return -1; } } } if (!isNumber(v2p)) { // as above but inverted if (v2p.startsWith(v1p)) { if (!isNumberInFirstChar(Strings.removeFromStart(v2p, v1p))) { return 1; } } } // both numbers, skip to natural order comparison } } // otherwise it is in-order int result = NaturalOrderComparator.INSTANCE.compare(v1p, v2p); if (result!=0) return result; } } @VisibleForTesting static String[] splitOnNonWordChar(String v) { Collection parts = new ArrayList(); String remaining = v; // use lookahead to split on all non-letter non-numbers, putting them into their own buckets parts.addAll(Arrays.asList(remaining.split("(?<=[^0-9\\p{L}])|(?=[^0-9\\p{L}])"))); return parts.toArray(new String[parts.size()]); } @VisibleForTesting static boolean isNumberInFirstCharPossiblyAfterADot(String[] parts, int i) { if (parts==null || parts.length<=i) return false; if (isNumberInFirstChar(parts[i])) return true; if (".".equals(parts[i])) { if (parts.length>i+1) if (isNumberInFirstChar(parts[i+1])) return true; } return false; } @VisibleForTesting static boolean isNumberInFirstChar(String v) { if (v==null || v.length()==0) return false; return Character.isDigit(v.charAt(0)); } @VisibleForTesting static boolean isNumber(String v) { if (v==null || v.length()==0) return false; return v.matches("[\\d]+"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy