diff -urN src.orig/java/org/apache/ivy/core/settings/IvySettings.java src/java/org/apache/ivy/core/settings/IvySettings.java --- src.orig/java/org/apache/ivy/core/settings/IvySettings.java 2010-06-27 13:17:06.000000000 -0700 +++ src/java/org/apache/ivy/core/settings/IvySettings.java 2010-07-26 12:20:33.000000000 -0700 @@ -73,6 +73,7 @@ import org.apache.ivy.plugins.latest.LatestRevisionStrategy; import org.apache.ivy.plugins.latest.LatestStrategy; import org.apache.ivy.plugins.latest.LatestTimeStrategy; +import org.apache.ivy.plugins.latest.OSGiLatestRevisionStrategy; import org.apache.ivy.plugins.lock.ArtifactLockStrategy; import org.apache.ivy.plugins.lock.LockStrategy; import org.apache.ivy.plugins.lock.NoLockStrategy; @@ -242,9 +243,11 @@ } LatestLexicographicStrategy latestLexicographicStrategy = new LatestLexicographicStrategy(); LatestRevisionStrategy latestRevisionStrategy = new LatestRevisionStrategy(); + OSGiLatestRevisionStrategy osgiLatestRevisionStrategy = new OSGiLatestRevisionStrategy(); LatestTimeStrategy latestTimeStrategy = new LatestTimeStrategy(); addLatestStrategy("latest-revision", latestRevisionStrategy); + addLatestStrategy("osgi-latest-revision", osgiLatestRevisionStrategy); addLatestStrategy("latest-lexico", latestLexicographicStrategy); addLatestStrategy("latest-time", latestTimeStrategy); @@ -253,8 +256,12 @@ addConflictManager("latest-revision", new LatestConflictManager("latest-revision", latestRevisionStrategy)); + addConflictManager("osgi-latest-revision", new LatestConflictManager("osgi-latest-revision", + osgiLatestRevisionStrategy)); addConflictManager("latest-compatible", new LatestCompatibleConflictManager("latest-compatible", latestRevisionStrategy)); + addConflictManager("osgi-latest-compatible", + new LatestCompatibleConflictManager("osgi-latest-compatible", osgiLatestRevisionStrategy)); addConflictManager("latest-time", new LatestConflictManager("latest-time", latestTimeStrategy)); addConflictManager("all", new NoConflictManager()); diff -urN src.orig/java/org/apache/ivy/plugins/latest/AbstractLatestRevisionStrategy.java src/java/org/apache/ivy/plugins/latest/AbstractLatestRevisionStrategy.java --- src.orig/java/org/apache/ivy/plugins/latest/AbstractLatestRevisionStrategy.java 1969-12-31 16:00:00.000000000 -0800 +++ src/java/org/apache/ivy/plugins/latest/AbstractLatestRevisionStrategy.java 2010-07-26 12:30:33.000000000 -0700 @@ -0,0 +1,140 @@ +/* + * 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.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; + +/** + * + * @author jgibson + */ +public abstract class AbstractLatestRevisionStrategy extends ComparatorLatestStrategy { + + /** + * 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 { + + public int compare(Object o1, Object o2) { + String rev1 = ((ArtifactInfo) o1).getRevision(); + String rev2 = ((ArtifactInfo) 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, getModuleRevisionIdComparator()); + return c >= 0 ? 1 : -1; + } else if (vmatcher.isDynamic(mrid2)) { + int c = vmatcher.compare(mrid2, mrid1, getModuleRevisionIdComparator()); + return c >= 0 ? -1 : 1; + } + + return getModuleRevisionIdComparator().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 DEFAULT_SPECIAL_MEANINGS; + + static { + DEFAULT_SPECIAL_MEANINGS = new HashMap(); + DEFAULT_SPECIAL_MEANINGS.put("dev", new Integer(-1)); + DEFAULT_SPECIAL_MEANINGS.put("rc", new Integer(1)); + DEFAULT_SPECIAL_MEANINGS.put("final", new Integer(2)); + } + private Map specialMeanings = null; + private boolean usedefaultspecialmeanings = true; + private final Comparator artifactInfoComparator = new ArtifactInfoComparator(); + + public AbstractLatestRevisionStrategy(final String name) { + setComparator(artifactInfoComparator); + setName(name); + } + + public void addConfiguredSpecialMeaning(SpecialMeaning meaning) { + meaning.validate(); + getSpecialMeanings().put(meaning.getName().toLowerCase(Locale.US), meaning.getValue()); + } + + public synchronized Map getSpecialMeanings() { + if (specialMeanings == null) { + specialMeanings = new HashMap(); + if (isUsedefaultspecialmeanings()) { + specialMeanings.putAll(AbstractLatestRevisionStrategy.DEFAULT_SPECIAL_MEANINGS); + } + } + return specialMeanings; + } + + public boolean isUsedefaultspecialmeanings() { + return usedefaultspecialmeanings; + } + + public void setUsedefaultspecialmeanings(boolean usedefaultspecialmeanings) { + this.usedefaultspecialmeanings = usedefaultspecialmeanings; + } + + protected abstract Comparator getModuleRevisionIdComparator(); +} diff -urN src.orig/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java src/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java --- src.orig/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java 2010-06-27 13:16:56.000000000 -0700 +++ src/java/org/apache/ivy/plugins/latest/LatestRevisionStrategy.java 2010-07-26 12:33:20.000000000 -0700 @@ -18,15 +18,12 @@ 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 { +public class LatestRevisionStrategy extends AbstractLatestRevisionStrategy { /** * Compares two ModuleRevisionId by their revision. Revisions are compared using an algorithm * inspired by PHP version_compare one. @@ -87,111 +84,13 @@ } } - /** - * 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 { - public int compare(Object o1, Object o2) { - String rev1 = ((ArtifactInfo) o1).getRevision(); - String rev2 = ((ArtifactInfo) 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 DEFAULT_SPECIAL_MEANINGS; - static { - DEFAULT_SPECIAL_MEANINGS = new HashMap(); - DEFAULT_SPECIAL_MEANINGS.put("dev", new Integer(-1)); - DEFAULT_SPECIAL_MEANINGS.put("rc", new Integer(1)); - DEFAULT_SPECIAL_MEANINGS.put("final", new Integer(2)); - } - private final Comparator mridComparator = new MridComparator(); - private final Comparator artifactInfoComparator = new ArtifactInfoComparator(); - - private Map 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 getSpecialMeanings() { - if (specialMeanings == null) { - specialMeanings = new HashMap(); - if (isUsedefaultspecialmeanings()) { - specialMeanings.putAll(DEFAULT_SPECIAL_MEANINGS); - } - } - return specialMeanings; - } - - public boolean isUsedefaultspecialmeanings() { - return usedefaultspecialmeanings; + super("latest-revision"); } - public void setUsedefaultspecialmeanings(boolean usedefaultspecialmeanings) { - this.usedefaultspecialmeanings = usedefaultspecialmeanings; + protected Comparator getModuleRevisionIdComparator() { + return mridComparator; } } diff -urN src.orig/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategy.java src/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategy.java --- src.orig/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategy.java 1969-12-31 16:00:00.000000000 -0800 +++ src/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategy.java 2010-07-26 12:34:45.000000000 -0700 @@ -0,0 +1,120 @@ +/* + * 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.ivy.plugins.latest; + +import java.util.Comparator; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.ivy.core.module.id.ModuleRevisionId; + +public class OSGiLatestRevisionStrategy extends AbstractLatestRevisionStrategy { + + private static final Pattern ALPHA_NUM_REGEX = Pattern.compile("([a-zA-Z])(\\d)"); + private static final Pattern NUM_ALPHA_REGEX = Pattern.compile("(\\d)([a-zA-Z])"); + private static final Pattern LABEL_REGEX = Pattern.compile("[_\\-\\+]"); + + /** + * Compares two ModuleRevisionId by their revision. Revisions are compared using an algorithm + * inspired by PHP version_compare one. + */ + final class MridComparator implements Comparator { + public int compare(Object o1, Object o2) { + String rev1 = ((ModuleRevisionId) o1).getRevision(); + String rev2 = ((ModuleRevisionId) o2).getRevision(); + + String[] outerParts1 = rev1.split("[\\.]"); + String[] outerParts2 = rev2.split("[\\.]"); + + for (int i=0; i < outerParts1.length && i < outerParts2.length; i++) { + String outerPart1 = outerParts1[i]; + String outerPart2 = outerParts2[i]; + + if (outerPart1.equals(outerPart2)) { + continue; + } + + outerPart1 = ALPHA_NUM_REGEX.matcher(outerPart1).replaceAll("$1_$2"); + outerPart1 = NUM_ALPHA_REGEX.matcher(outerPart1).replaceAll("$1_$2"); + outerPart2 = ALPHA_NUM_REGEX.matcher(outerPart2).replaceAll("$1_$2"); + outerPart2 = NUM_ALPHA_REGEX.matcher(outerPart2).replaceAll("$1_$2"); + + String[] innerParts1 = LABEL_REGEX.split(outerPart1); + String[] innerParts2 = LABEL_REGEX.split(outerPart2); + + for (int j=0; j < innerParts1.length && j < innerParts2.length; j++) { + if (innerParts1[j].equals(innerParts2[j])) { + continue; + } + boolean is1Number = isNumber(innerParts1[j]); + boolean is2Number = isNumber(innerParts2[j]); + if (is1Number && !is2Number) { + return 1; + } + if (is2Number && !is1Number) { + return -1; + } + if (is1Number && is2Number) { + return Long.valueOf(innerParts1[j]).compareTo(Long.valueOf(innerParts2[j])); + } + // both are strings, we compare them taking into account special meaning + Map specialMeanings = getSpecialMeanings(); + Integer sm1 = (Integer) specialMeanings.get(innerParts1[j].toLowerCase(Locale.US)); + Integer sm2 = (Integer) specialMeanings.get(innerParts2[j].toLowerCase(Locale.US)); + if (sm1 != null) { + sm2 = sm2 == null ? new Integer(0) : sm2; + return sm1.compareTo(sm2); + } + if (sm2 != null) { + return new Integer(0).compareTo(sm2); + } + return innerParts1[j].compareTo(innerParts2[j]); + } + if (i < innerParts1.length) { + return isNumber(innerParts1[i]) ? 1 : -1; + } + if (i < innerParts2.length) { + return isNumber(innerParts2[i]) ? -1 : 1; + } + } + + if(outerParts1.length > outerParts2.length) { + return 1; + } else if(outerParts1.length < outerParts2.length) { + return -1; + } + + return 0; + } + + private boolean isNumber(String str) { + return str.matches("\\d+"); + } + } + + private final Comparator mridComparator = new MridComparator(); + + public OSGiLatestRevisionStrategy() { + super("osgi-latest-revision"); + } + + protected Comparator getModuleRevisionIdComparator() { + return mridComparator; + } +} diff -urN test.orig/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategyTest.java test/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategyTest.java --- test.orig/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategyTest.java 1969-12-31 16:00:00.000000000 -0800 +++ test/java/org/apache/ivy/plugins/latest/OSGiLatestRevisionStrategyTest.java 2010-07-15 10:34:21.000000000 -0700 @@ -0,0 +1,122 @@ +/* + * 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.ivy.plugins.latest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import junit.framework.TestCase; + +public class OSGiLatestRevisionStrategyTest extends TestCase { + public void testComparator() { + ArtifactInfo[] revs = toMockAI(new String[] {"0.2a", "0.2_b", "0.2rc1", "0.2-final", + "1.0-dev1", "1.0-dev2", "1.0-alpha1", "1.0-alpha2", "1.0-beta1", "1.0-beta2", + "1.0-gamma", "1.0-rc1", "1.0-rc2", "1.0", "1.0.1", + "2.0", "2.0.0", "2.0.0.b006","2.0.0.b012","2.0.0.xyz"}); + + List shuffled = new ArrayList(Arrays.asList(revs)); + Collections.shuffle(shuffled); + Collections.sort(shuffled, new OSGiLatestRevisionStrategy().new ArtifactInfoComparator()); + assertEquals(Arrays.asList(revs), shuffled); + } + + public void testSort() { + ArtifactInfo[] revs = toMockAI(new String[] {"0.2a", "0.2_b", "0.2rc1", "0.2-final", + "1.0-dev1", "1.0-dev2", "1.0-alpha1", "1.0-alpha2", "1.0-beta1", "1.0-beta2", + "1.0-gamma", "1.0-rc1", "1.0-rc2", "1.0", "1.0.1", "2.0"}); + + List shuffled = new ArrayList(Arrays.asList(revs)); + ArtifactInfo[] shuffledRevs = (ArtifactInfo[]) shuffled + .toArray(new ArtifactInfo[revs.length]); + + OSGiLatestRevisionStrategy latestRevisionStrategy = new OSGiLatestRevisionStrategy(); + List sorted = latestRevisionStrategy.sort(shuffledRevs); + assertEquals(Arrays.asList(revs), sorted); + } + + public void testFindLatest() { + ArtifactInfo[] revs = toMockAI(new String[] {"0.2a", "0.2_b", "0.2rc1", "0.2-final", + "1.0-dev1", "1.0-dev2", "1.0-alpha1", "1.0-alpha2", "1.0-beta1", "1.0-beta2", + "1.0-gamma", "1.0-rc1", "1.0-rc2", "1.0", "1.0.1", "2.0"}); + + List shuffled = new ArrayList(Arrays.asList(revs)); + Collections.shuffle(shuffled); + ArtifactInfo[] shuffledRevs = (ArtifactInfo[]) shuffled + .toArray(new ArtifactInfo[revs.length]); + + OSGiLatestRevisionStrategy latestRevisionStrategy = new OSGiLatestRevisionStrategy(); + ArtifactInfo latest = latestRevisionStrategy.findLatest(shuffledRevs, new Date()); + assertNotNull(latest); + assertEquals("2.0", latest.getRevision()); + } + + public void testSpecialMeaningComparator() { + ArtifactInfo[] revs = toMockAI(new String[] {"0.1", "0.2-pre", "0.2-dev", "0.2-rc1", + "0.2-final", "0.2-QA", "1.0-dev1"}); + + List shuffled = new ArrayList(Arrays.asList(revs)); + Collections.shuffle(shuffled); + OSGiLatestRevisionStrategy latestRevisionStrategy = new OSGiLatestRevisionStrategy(); + OSGiLatestRevisionStrategy.SpecialMeaning specialMeaning = new OSGiLatestRevisionStrategy.SpecialMeaning(); + specialMeaning.setName("pre"); + specialMeaning.setValue(new Integer(-2)); + latestRevisionStrategy.addConfiguredSpecialMeaning(specialMeaning); + specialMeaning = new OSGiLatestRevisionStrategy.SpecialMeaning(); + specialMeaning.setName("QA"); + specialMeaning.setValue(new Integer(4)); + latestRevisionStrategy.addConfiguredSpecialMeaning(specialMeaning); + Collections.sort(shuffled, latestRevisionStrategy.new ArtifactInfoComparator()); + assertEquals(Arrays.asList(revs), shuffled); + } + + private static class MockArtifactInfo implements ArtifactInfo { + + private long _lastModified; + + private String _rev; + + public MockArtifactInfo(String rev, long lastModified) { + _rev = rev; + _lastModified = lastModified; + } + + public String getRevision() { + return _rev; + } + + public long getLastModified() { + return _lastModified; + } + + public String toString() { + return _rev; + } + } + + private ArtifactInfo[] toMockAI(String[] revs) { + ArtifactInfo[] artifactInfos = new ArtifactInfo[revs.length]; + for (int i = 0; i < artifactInfos.length; i++) { + artifactInfos[i] = new MockArtifactInfo(revs[i], 0); + } + return artifactInfos; + } + +}