| /* |
| * 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 |
| * |
| * https://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.ivy.plugins.latest; |
| |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.ivy.core.IvyContext; |
| import org.apache.ivy.core.module.id.ModuleRevisionId; |
| import org.apache.ivy.plugins.version.VersionMatcher; |
| |
| public class LatestRevisionStrategy extends ComparatorLatestStrategy { |
| /** |
| * Compares two ModuleRevisionId by their revision. Revisions are compared using an algorithm |
| * inspired by PHP version_compare one. |
| */ |
| final class MridComparator implements Comparator<ModuleRevisionId> { |
| public int compare(ModuleRevisionId o1, ModuleRevisionId o2) { |
| String rev1 = o1.getRevision(); |
| String rev2 = o2.getRevision(); |
| |
| rev1 = rev1.replaceAll("([a-zA-Z])(\\d)", "$1.$2"); |
| rev1 = rev1.replaceAll("(\\d)([a-zA-Z])", "$1.$2"); |
| rev2 = rev2.replaceAll("([a-zA-Z])(\\d)", "$1.$2"); |
| rev2 = rev2.replaceAll("(\\d)([a-zA-Z])", "$1.$2"); |
| |
| String[] parts1 = rev1.split("[\\._\\-\\+]"); |
| String[] parts2 = rev2.split("[\\._\\-\\+]"); |
| |
| int i = 0; |
| for (; i < parts1.length && i < parts2.length; i++) { |
| if (parts1[i].equals(parts2[i])) { |
| continue; |
| } |
| boolean is1Number = isNumber(parts1[i]); |
| boolean is2Number = isNumber(parts2[i]); |
| if (is1Number && !is2Number) { |
| return 1; |
| } |
| if (is2Number && !is1Number) { |
| return -1; |
| } |
| if (is1Number && is2Number) { |
| return Long.valueOf(parts1[i]).compareTo(Long.valueOf(parts2[i])); |
| } |
| // both are strings, we compare them taking into account special meaning |
| Map<String, Integer> specialMeanings = getSpecialMeanings(); |
| Integer sm1 = specialMeanings.get(parts1[i].toLowerCase(Locale.US)); |
| Integer sm2 = specialMeanings.get(parts2[i].toLowerCase(Locale.US)); |
| if (sm1 != null) { |
| if (sm2 == null) { |
| sm2 = new Integer(0); |
| } |
| return sm1.compareTo(sm2); |
| } |
| if (sm2 != null) { |
| return new Integer(0).compareTo(sm2); |
| } |
| return parts1[i].compareTo(parts2[i]); |
| } |
| if (i < parts1.length) { |
| return isNumber(parts1[i]) ? 1 : -1; |
| } |
| if (i < parts2.length) { |
| return isNumber(parts2[i]) ? -1 : 1; |
| } |
| return 0; |
| } |
| |
| private boolean isNumber(String str) { |
| return str.matches("\\d+"); |
| } |
| } |
| |
| /** |
| * Compares two ArtifactInfo by their revision. Revisions are compared using an algorithm |
| * inspired by PHP version_compare one, unless a dynamic revision is given, in which case the |
| * version matcher is used to perform the comparison. |
| */ |
| final class ArtifactInfoComparator implements Comparator<ArtifactInfo> { |
| public int compare(ArtifactInfo o1, ArtifactInfo o2) { |
| String rev1 = o1.getRevision(); |
| String rev2 = o2.getRevision(); |
| |
| /* |
| * The revisions can still be not resolved, so we use the current version matcher to |
| * know if one revision is dynamic, and in this case if it should be considered greater |
| * or lower than the other one. Note that if the version matcher compare method returns |
| * 0, it's because it's not possible to know which revision is greater. In this case we |
| * consider the dynamic one to be greater, because most of the time it will then be |
| * actually resolved and a real comparison will occur. |
| */ |
| VersionMatcher vmatcher = IvyContext.getContext().getSettings().getVersionMatcher(); |
| ModuleRevisionId mrid1 = ModuleRevisionId.newInstance("", "", rev1); |
| ModuleRevisionId mrid2 = ModuleRevisionId.newInstance("", "", rev2); |
| |
| if (vmatcher.isDynamic(mrid1)) { |
| int c = vmatcher.compare(mrid1, mrid2, mridComparator); |
| return c >= 0 ? 1 : -1; |
| } else if (vmatcher.isDynamic(mrid2)) { |
| int c = vmatcher.compare(mrid2, mrid1, mridComparator); |
| return c >= 0 ? -1 : 1; |
| } |
| |
| return mridComparator.compare(mrid1, mrid2); |
| } |
| } |
| |
| public static class SpecialMeaning { |
| private String name; |
| |
| private Integer value; |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| public Integer getValue() { |
| return value; |
| } |
| |
| public void setValue(Integer value) { |
| this.value = value; |
| } |
| |
| public void validate() { |
| if (name == null) { |
| throw new IllegalStateException("a special meaning should have a name"); |
| } |
| if (value == null) { |
| throw new IllegalStateException("a special meaning should have a value"); |
| } |
| } |
| } |
| |
| private static final Map<String, Integer> DEFAULT_SPECIAL_MEANINGS; |
| static { |
| DEFAULT_SPECIAL_MEANINGS = new HashMap<>(); |
| DEFAULT_SPECIAL_MEANINGS.put("dev", -1); |
| DEFAULT_SPECIAL_MEANINGS.put("rc", 1); |
| DEFAULT_SPECIAL_MEANINGS.put("final", 2); |
| } |
| |
| private final Comparator<ModuleRevisionId> mridComparator = new MridComparator(); |
| |
| private final Comparator<ArtifactInfo> artifactInfoComparator = new ArtifactInfoComparator(); |
| |
| private Map<String, Integer> specialMeanings = null; |
| |
| private boolean usedefaultspecialmeanings = true; |
| |
| public LatestRevisionStrategy() { |
| setComparator(artifactInfoComparator); |
| setName("latest-revision"); |
| } |
| |
| public void addConfiguredSpecialMeaning(SpecialMeaning meaning) { |
| meaning.validate(); |
| getSpecialMeanings().put(meaning.getName().toLowerCase(Locale.US), meaning.getValue()); |
| } |
| |
| public synchronized Map<String, Integer> getSpecialMeanings() { |
| if (specialMeanings == null) { |
| specialMeanings = new HashMap<>(); |
| if (isUsedefaultspecialmeanings()) { |
| specialMeanings.putAll(DEFAULT_SPECIAL_MEANINGS); |
| } |
| } |
| return specialMeanings; |
| } |
| |
| public boolean isUsedefaultspecialmeanings() { |
| return usedefaultspecialmeanings; |
| } |
| |
| public void setUsedefaultspecialmeanings(boolean usedefaultspecialmeanings) { |
| this.usedefaultspecialmeanings = usedefaultspecialmeanings; |
| } |
| } |