001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.plugins.latest;
019:
020: import java.util.Comparator;
021: import java.util.HashMap;
022: import java.util.Locale;
023: import java.util.Map;
024:
025: import org.apache.ivy.core.IvyContext;
026: import org.apache.ivy.core.module.id.ModuleRevisionId;
027: import org.apache.ivy.plugins.version.VersionMatcher;
028:
029: public class LatestRevisionStrategy extends ComparatorLatestStrategy {
030: /**
031: * Compares two ModuleRevisionId by their revision. Revisions are compared using an algorithm
032: * inspired by PHP version_compare one.
033: */
034: final class MridComparator implements Comparator {
035: public int compare(Object o1, Object o2) {
036: String rev1 = ((ModuleRevisionId) o1).getRevision();
037: String rev2 = ((ModuleRevisionId) o2).getRevision();
038:
039: rev1 = rev1.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
040: rev1 = rev1.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
041: rev2 = rev2.replaceAll("([a-zA-Z])(\\d)", "$1.$2");
042: rev2 = rev2.replaceAll("(\\d)([a-zA-Z])", "$1.$2");
043:
044: String[] parts1 = rev1.split("[\\._\\-\\+]");
045: String[] parts2 = rev2.split("[\\._\\-\\+]");
046:
047: int i = 0;
048: for (; i < parts1.length && i < parts2.length; i++) {
049: if (parts1[i].equals(parts2[i])) {
050: continue;
051: }
052: boolean is1Number = isNumber(parts1[i]);
053: boolean is2Number = isNumber(parts2[i]);
054: if (is1Number && !is2Number) {
055: return 1;
056: }
057: if (is2Number && !is1Number) {
058: return -1;
059: }
060: if (is1Number && is2Number) {
061: return Long.valueOf(parts1[i]).compareTo(
062: Long.valueOf(parts2[i]));
063: }
064: // both are strings, we compare them taking into account special meaning
065: Map specialMeanings = getSpecialMeanings();
066: Integer sm1 = (Integer) specialMeanings.get(parts1[i]
067: .toLowerCase(Locale.US));
068: Integer sm2 = (Integer) specialMeanings.get(parts2[i]
069: .toLowerCase(Locale.US));
070: if (sm1 != null) {
071: sm2 = sm2 == null ? new Integer(0) : sm2;
072: return sm1.compareTo(sm2);
073: }
074: if (sm2 != null) {
075: return new Integer(0).compareTo(sm2);
076: }
077: return parts1[i].compareTo(parts2[i]);
078: }
079: if (i < parts1.length) {
080: return isNumber(parts1[i]) ? 1 : -1;
081: }
082: if (i < parts2.length) {
083: return isNumber(parts2[i]) ? -1 : 1;
084: }
085: return 0;
086: }
087:
088: private boolean isNumber(String str) {
089: return str.matches("\\d+");
090: }
091: }
092:
093: /**
094: * Compares two ArtifactInfo by their revision. Revisions are compared using an algorithm
095: * inspired by PHP version_compare one, unless a dynamic revision is given, in which case the
096: * version matcher is used to perform the comparison.
097: */
098: final class ArtifactInfoComparator implements Comparator {
099: public int compare(Object o1, Object o2) {
100: String rev1 = ((ArtifactInfo) o1).getRevision();
101: String rev2 = ((ArtifactInfo) o2).getRevision();
102:
103: /*
104: * The revisions can still be not resolved, so we use the current version matcher to
105: * know if one revision is dynamic, and in this case if it should be considered greater
106: * or lower than the other one. Note that if the version matcher compare method returns
107: * 0, it's because it's not possible to know which revision is greater. In this case we
108: * consider the dynamic one to be greater, because most of the time it will then be
109: * actually resolved and a real comparison will occur.
110: */
111: VersionMatcher vmatcher = IvyContext.getContext()
112: .getSettings().getVersionMatcher();
113: ModuleRevisionId mrid1 = ModuleRevisionId.newInstance("",
114: "", rev1);
115: ModuleRevisionId mrid2 = ModuleRevisionId.newInstance("",
116: "", rev2);
117: if (vmatcher.isDynamic(mrid1)) {
118: int c = vmatcher.compare(mrid1, mrid2, mridComparator);
119: return c >= 0 ? 1 : -1;
120: } else if (vmatcher.isDynamic(mrid2)) {
121: int c = vmatcher.compare(mrid2, mrid1, mridComparator);
122: return c >= 0 ? -1 : 1;
123: }
124:
125: return mridComparator.compare(mrid1, mrid2);
126: }
127: }
128:
129: public static class SpecialMeaning {
130: private String name;
131:
132: private Integer value;
133:
134: public String getName() {
135: return name;
136: }
137:
138: public void setName(String name) {
139: this .name = name;
140: }
141:
142: public Integer getValue() {
143: return value;
144: }
145:
146: public void setValue(Integer value) {
147: this .value = value;
148: }
149:
150: public void validate() {
151: if (name == null) {
152: throw new IllegalStateException(
153: "a special meaning should have a name");
154: }
155: if (value == null) {
156: throw new IllegalStateException(
157: "a special meaning should have a value");
158: }
159: }
160: }
161:
162: private static final Map DEFAULT_SPECIAL_MEANINGS;
163: static {
164: DEFAULT_SPECIAL_MEANINGS = new HashMap();
165: DEFAULT_SPECIAL_MEANINGS.put("dev", new Integer(-1));
166: DEFAULT_SPECIAL_MEANINGS.put("rc", new Integer(1));
167: DEFAULT_SPECIAL_MEANINGS.put("final", new Integer(2));
168: }
169:
170: private final Comparator mridComparator = new MridComparator();
171:
172: private final Comparator artifactInfoComparator = new ArtifactInfoComparator();
173:
174: private Map specialMeanings = null;
175:
176: private boolean usedefaultspecialmeanings = true;
177:
178: public LatestRevisionStrategy() {
179: setComparator(artifactInfoComparator);
180: setName("latest-revision");
181: }
182:
183: public void addConfiguredSpecialMeaning(SpecialMeaning meaning) {
184: meaning.validate();
185: getSpecialMeanings().put(
186: meaning.getName().toLowerCase(Locale.US),
187: meaning.getValue());
188: }
189:
190: public synchronized Map getSpecialMeanings() {
191: if (specialMeanings == null) {
192: specialMeanings = new HashMap();
193: if (isUsedefaultspecialmeanings()) {
194: specialMeanings.putAll(DEFAULT_SPECIAL_MEANINGS);
195: }
196: }
197: return specialMeanings;
198: }
199:
200: public boolean isUsedefaultspecialmeanings() {
201: return usedefaultspecialmeanings;
202: }
203:
204: public void setUsedefaultspecialmeanings(
205: boolean usedefaultspecialmeanings) {
206: this.usedefaultspecialmeanings = usedefaultspecialmeanings;
207: }
208: }
|