// Copyright (C) 2011 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.google.gerrit.server.util; import com.google.gerrit.common.data.RefConfigSection; import com.google.gerrit.server.project.RefControl; import org.apache.commons.lang.StringUtils; import java.util.Comparator; /** * Order the Ref Pattern by the most specific. This sort is done by: * <ul> * <li>1 - The minor value of Levenshtein string distance between the branch * name and the regex string shortest example. A shorter distance is a more * specific match. * <li>2 - Finites first, infinities after. * <li>3 - Number of transitions. * <li>4 - Length of the expression text. * </ul> * * Levenshtein distance is a measure of the similarity between two strings. * The distance is the number of deletions, insertions, or substitutions * required to transform one string into another. * * For example, if given refs/heads/m* and refs/heads/*, the distances are 5 * and 6. It means that refs/heads/m* is more specific because it's closer to * refs/heads/master than refs/heads/*. * * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more * transitions, which after all turns it more specific. */ public final class MostSpecificComparator implements Comparator<RefConfigSection> { private final String refName; public MostSpecificComparator(String refName) { this.refName = refName; } @Override public int compare(RefConfigSection a, RefConfigSection b) { return compare(a.getName(), b.getName()); } public int compare(final String pattern1, final String pattern2) { int cmp = distance(pattern1) - distance(pattern2); if (cmp == 0) { boolean p1_finite = finite(pattern1); boolean p2_finite = finite(pattern2); if (p1_finite && !p2_finite) { cmp = -1; } else if (!p1_finite && p2_finite) { cmp = 1; } else /* if (f1 == f2) */{ cmp = 0; } } if (cmp == 0) { cmp = transitions(pattern1) - transitions(pattern2); } if (cmp == 0) { cmp = pattern2.length() - pattern1.length(); } return cmp; } private int distance(String pattern) { String example; if (RefControl.isRE(pattern)) { example = RefControl.shortestExample(pattern); } else if (pattern.endsWith("/*")) { example = pattern.substring(0, pattern.length() - 1) + '1'; } else if (pattern.equals(refName)) { return 0; } else { return Math.max(pattern.length(), refName.length()); } return StringUtils.getLevenshteinDistance(example, refName); } private boolean finite(String pattern) { if (RefControl.isRE(pattern)) { return RefControl.toRegExp(pattern).toAutomaton().isFinite(); } else if (pattern.endsWith("/*")) { return false; } else { return true; } } private int transitions(String pattern) { if (RefControl.isRE(pattern)) { return RefControl.toRegExp(pattern).toAutomaton() .getNumberOfTransitions(); } else if (pattern.endsWith("/*")) { return pattern.length(); } else { return pattern.length(); } } }