001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.maven; 025 026 import java.util.LinkedHashSet; 027 import java.util.Set; 028 import java.util.regex.Matcher; 029 import java.util.regex.Pattern; 030 import org.jboss.dna.common.text.TextEncoder; 031 import org.jboss.dna.common.text.NoOpEncoder; 032 import org.jboss.dna.common.util.CheckArg; 033 import org.jboss.dna.common.util.HashCode; 034 import org.jboss.dna.common.util.StringUtil; 035 036 /** 037 * Identifier of a Maven 2 artifact. 038 */ 039 public class MavenId implements Comparable<MavenId>, Cloneable { 040 041 /** 042 * Build a classpath of {@link MavenId}s by parsing the supplied string containing comma-separated Maven artifact 043 * coordinates. Any duplicates in the classpath are excluded. 044 * @param commaSeparatedCoordinates the string of Maven artifact coordinates 045 * @return the array of {@link MavenId} instances representing the classpath 046 */ 047 public static MavenId[] createClasspath( String commaSeparatedCoordinates ) { 048 if (commaSeparatedCoordinates == null) return new MavenId[] {}; 049 String[] coordinates = commaSeparatedCoordinates.split(","); 050 return createClasspath(coordinates); 051 } 052 053 /** 054 * Build a classpath of {@link MavenId}s by parsing the supplied Maven artifact coordinates. Any duplicates in the classpath 055 * are excluded. 056 * @param mavenCoordinates the array of Maven artifact coordinates 057 * @return the array of {@link MavenId} instances representing the classpath 058 */ 059 public static MavenId[] createClasspath( String... mavenCoordinates ) { 060 if (mavenCoordinates == null) return new MavenId[] {}; 061 // Use a linked set that maintains order and adds no duplicates ... 062 Set<MavenId> result = new LinkedHashSet<MavenId>(); 063 for (int i = 0; i < mavenCoordinates.length; i++) { 064 String coordinateStr = mavenCoordinates[i]; 065 if (coordinateStr == null) continue; 066 coordinateStr = coordinateStr.trim(); 067 if (coordinateStr.length() != 0) { 068 result.add(new MavenId(coordinateStr)); 069 } 070 } 071 return result.toArray(new MavenId[result.size()]); 072 } 073 074 /** 075 * Create a classpath of {@link MavenId}s by examining the supplied IDs and removing any duplicates. 076 * @param mavenIds the Maven IDs 077 * @return the array of {@link MavenId} instances representing the classpath 078 */ 079 public static MavenId[] createClasspath( MavenId... mavenIds ) { 080 // Use a linked set that maintains order and adds no duplicates ... 081 Set<MavenId> result = new LinkedHashSet<MavenId>(); 082 for (MavenId mavenId : mavenIds) { 083 if (mavenId != null) result.add(mavenId); 084 } 085 return result.toArray(new MavenId[result.size()]); 086 } 087 088 private final String groupId; 089 private final String artifactId; 090 private final Version version; 091 private final String classifier; 092 093 /** 094 * Create an Maven ID from the supplied string containing the coordinates for a Maven artifact. Coordinates are of the form: 095 * 096 * <pre> 097 * groupId:artifactId[:version[:classifier]] 098 * </pre> 099 * 100 * where 101 * <dl> 102 * <dt>groupId</dt> 103 * <dd> is the group identifier (e.g., <code>org.jboss.dna</code>), which may not be empty 104 * <dt>artifactId</dt> 105 * <dd> is the artifact identifier (e.g., <code>dna-maven</code>), which may not be empty 106 * <dt>version</dt> 107 * <dd> is the optional version (e.g., <code>org.jboss.dna</code>) 108 * <dt>classifier</dt> 109 * <dd> is the optional classifier (e.g., <code>test</code> or <code>jdk1.4</code>) 110 * </dl> 111 * @param coordinates the string containing the Maven coordinates 112 * @throws IllegalArgumentException if the supplied string is null or if the string does not match the expected format 113 */ 114 public MavenId( String coordinates ) { 115 CheckArg.isNotNull(coordinates, "coordinates"); 116 coordinates = coordinates.trim(); 117 CheckArg.isNotEmpty(coordinates, "coordinates"); 118 119 // This regular expression has the following groups: 120 // 1) groupId 121 // 2) :artifactId 122 // 3) artifactId 123 // 4) :version 124 // 5) version 125 // 6) :classifier 126 // 7) classifier 127 Pattern urlPattern = Pattern.compile("([^:]+)(:([^:]+)(:([^:]*)(:([^:]*))?)?)?"); 128 Matcher matcher = urlPattern.matcher(coordinates); 129 if (!matcher.find()) { 130 throw new IllegalArgumentException(MavenI18n.unsupportedMavenCoordinateFormat.text(coordinates)); 131 } 132 String groupId = matcher.group(1); 133 String artifactId = matcher.group(3); 134 String version = matcher.group(5); 135 String classifier = matcher.group(7); 136 CheckArg.isNotEmpty(groupId, "groupId"); 137 CheckArg.isNotEmpty(artifactId, "artifactId"); 138 this.groupId = groupId.trim(); 139 this.artifactId = artifactId.trim(); 140 this.classifier = classifier != null ? classifier.trim() : ""; 141 this.version = version != null ? new Version(version) : new Version(""); 142 } 143 144 /** 145 * Create a Maven ID from the supplied group and artifact IDs. 146 * @param groupId the group identifier 147 * @param artifactId the artifact identifier 148 * @throws IllegalArgumentException if the group or artifact identifiers are null, empty or blank 149 */ 150 public MavenId( String groupId, String artifactId ) { 151 this(groupId, artifactId, null, null); 152 } 153 154 /** 155 * Create a Maven ID from the supplied group and artifact IDs and the version. 156 * @param groupId the group identifier 157 * @param artifactId the artifact identifier 158 * @param version the version; may be null or empty 159 * @throws IllegalArgumentException if the group or artifact identifiers are null, empty or blank 160 */ 161 public MavenId( String groupId, String artifactId, String version ) { 162 this(groupId, artifactId, version, null); 163 } 164 165 /** 166 * Create a Maven ID from the supplied group ID, artifact ID, version, and classifier. 167 * @param groupId the group identifier 168 * @param artifactId the artifact identifier 169 * @param version the version; may be null or empty 170 * @param classifier the classifier; may be null or empty 171 * @throws IllegalArgumentException if the group or artifact identifiers are null, empty or blank 172 */ 173 public MavenId( String groupId, String artifactId, String version, String classifier ) { 174 CheckArg.isNotEmpty(groupId, "groupId"); 175 CheckArg.isNotEmpty(artifactId, "artifactId"); 176 this.groupId = groupId.trim(); 177 this.artifactId = artifactId.trim(); 178 this.classifier = classifier != null ? classifier.trim() : ""; 179 this.version = version != null ? new Version(version) : new Version(""); 180 } 181 182 /** 183 * A universally unique identifier for a project. It is normal to use a fully-qualified package name to distinguish it from 184 * other projects with a similar name (eg. <code>org.apache.maven</code>). 185 * @return the group identifier 186 */ 187 public String getGroupId() { 188 return this.groupId; 189 } 190 191 /** 192 * The identifier for this artifact that is unique within the group given by the group ID. An artifact is something that is 193 * either produced or used by a project. Examples of artifacts produced by Maven for a project include: JARs, source and 194 * binary distributions, and WARs. 195 * @return the artifact identifier 196 */ 197 public String getArtifactId() { 198 return this.artifactId; 199 } 200 201 /** 202 * @return classifier 203 */ 204 public String getClassifier() { 205 return this.classifier; 206 } 207 208 /** 209 * @return version 210 */ 211 public String getVersion() { 212 return this.version.toString(); 213 } 214 215 /** 216 * Return the relative JCR path for this resource, built from the components of the {@link #getGroupId() group ID}, the 217 * {@link #getArtifactId() artifact ID}, and the {@link #getVersion() version}. 218 * @return the path; never null 219 */ 220 public String getRelativePath() { 221 return getRelativePath(NoOpEncoder.getInstance()); 222 } 223 224 /** 225 * Return the relative JCR path for this resource, built from the components of the {@link #getGroupId() group ID}, the 226 * {@link #getArtifactId() artifact ID}, and the {@link #getVersion() version}. 227 * @param escapingStrategy the strategy to use for escaping characters that are not allowed in JCR names. 228 * @return the path; never null 229 */ 230 public String getRelativePath( TextEncoder escapingStrategy ) { 231 return getRelativePath(NoOpEncoder.getInstance(), true); 232 } 233 234 /** 235 * Return the relative JCR path for this resource, built from the components of the {@link #getGroupId() group ID}, the 236 * {@link #getArtifactId() artifact ID}, and the {@link #getVersion() version}. 237 * @param includeVersion true if the version is to be included in the path 238 * @return the path; never null 239 */ 240 public String getRelativePath( boolean includeVersion ) { 241 return getRelativePath(NoOpEncoder.getInstance(), includeVersion); 242 } 243 244 /** 245 * Return the relative JCR path for this resource, built from the components of the {@link #getGroupId() group ID}, the 246 * {@link #getArtifactId() artifact ID}, and the {@link #getVersion() version}. 247 * @param escapingStrategy the strategy to use for escaping characters that are not allowed in JCR names. 248 * @param includeVersion true if the version is to be included in the path 249 * @return the path; never null 250 */ 251 public String getRelativePath( TextEncoder escapingStrategy, boolean includeVersion ) { 252 StringBuilder sb = new StringBuilder(); 253 String[] groupComponents = this.getGroupId().split("[\\.]"); 254 for (String groupComponent : groupComponents) { 255 if (sb.length() != 0) sb.append("/"); 256 sb.append(escapingStrategy.encode(groupComponent)); 257 } 258 sb.append("/").append(escapingStrategy.encode(this.getArtifactId())); 259 if (includeVersion) { 260 sb.append("/").append(escapingStrategy.encode(this.getVersion())); 261 } 262 return sb.toString(); 263 } 264 265 public String getCoordinates() { 266 return StringUtil.createString("{0}:{1}:{2}:{3}", this.groupId, this.artifactId, this.version, this.classifier); 267 } 268 269 public static MavenId createFromCoordinates( String coordinates ) { 270 String[] parts = coordinates.split("[:]"); 271 String groupId = null; 272 String artifactId = null; 273 String version = null; 274 String classifier = null; 275 if (parts.length > 0) groupId = parts[0]; 276 if (parts.length > 1) artifactId = parts[1]; 277 if (parts.length > 2) version = parts[2]; 278 if (parts.length > 3) classifier = parts[3]; 279 return new MavenId(groupId, artifactId, classifier, version); 280 } 281 282 protected boolean isAnyVersion() { 283 return this.version.isAnyVersion(); 284 } 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public int hashCode() { 291 // The version is excluded from the hash code so that the 'any version' will be in the same bucket of a hash table 292 return HashCode.compute(this.groupId, this.artifactId, this.classifier); 293 } 294 295 /** 296 * {@inheritDoc} 297 */ 298 @Override 299 public boolean equals( Object obj ) { 300 if (this == obj) return true; 301 if (obj instanceof MavenId) { 302 MavenId that = (MavenId)obj; 303 if (!this.groupId.equalsIgnoreCase(that.groupId)) return false; 304 if (!this.artifactId.equalsIgnoreCase(that.artifactId)) return false; 305 if (!this.version.equals(that.version)) return false; 306 if (!this.classifier.equalsIgnoreCase(that.classifier)) return false; 307 return true; 308 } 309 return false; 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 public int compareTo( MavenId that ) { 316 if (that == null) return 1; 317 if (this == that) return 0; 318 319 // Check the group ID ... 320 int diff = this.groupId.compareTo(that.groupId); 321 if (diff != 0) return diff; 322 323 // then the artifact ID ... 324 diff = this.artifactId.compareTo(that.artifactId); 325 if (diff != 0) return diff; 326 327 // then the version ... 328 diff = this.version.compareTo(that.version); 329 if (diff != 0) return diff; 330 331 // then the classifier ... 332 diff = this.classifier.compareTo(that.classifier); 333 return diff; 334 } 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override 340 public String toString() { 341 return this.getCoordinates(); 342 } 343 344 public class Version implements Comparable<Version> { 345 346 private final String version; 347 private final Object[] components; 348 349 protected Version( String version ) { 350 this.version = version != null ? version.trim() : ""; 351 this.components = getVersionComponents(this.version); 352 } 353 354 /** 355 * @return components 356 */ 357 public Object[] getComponents() { 358 return this.components; 359 } 360 361 public boolean isAnyVersion() { 362 return this.version.length() == 0; 363 } 364 365 /** 366 * {@inheritDoc} 367 */ 368 @Override 369 public String toString() { 370 return version; 371 } 372 373 /** 374 * {@inheritDoc} 375 */ 376 @Override 377 public int hashCode() { 378 return this.version.hashCode(); 379 } 380 381 /** 382 * {@inheritDoc} 383 */ 384 public int compareTo( Version that ) { 385 if (that == null) return 1; 386 Object[] thisComponents = this.getComponents(); 387 Object[] thatComponents = that.getComponents(); 388 int thisLength = thisComponents.length; 389 int thatLength = thatComponents.length; 390 int minLength = Math.min(thisLength, thatLength); 391 for (int i = 0; i != minLength; ++i) { 392 Object thisComponent = thisComponents[i]; 393 Object thatComponent = thatComponents[i]; 394 int diff = 0; 395 if (thisComponent instanceof Integer && thatComponent instanceof Integer) { 396 diff = ((Integer)thisComponent).compareTo((Integer)thatComponent); 397 } else { 398 String thisString = thisComponent.toString(); 399 String thatString = thatComponent.toString(); 400 diff = thisString.compareToIgnoreCase(thatString); 401 } 402 if (diff != 0) return diff; 403 } 404 return 0; 405 } 406 407 /** 408 * {@inheritDoc} 409 */ 410 @Override 411 public boolean equals( Object obj ) { 412 if (obj == this) return true; 413 if (obj instanceof Version) { 414 Version that = (Version)obj; 415 if (this.isAnyVersion() || that.isAnyVersion()) return true; 416 if (!this.version.equalsIgnoreCase(that.version)) return false; 417 return true; 418 } 419 return false; 420 } 421 } 422 423 /** 424 * Utility to break down the version string into the individual components. This utility splits the supplied version on 425 * periods ('.'), dashes ('-'), forward slashes ('/'), and commas (','). 426 * @param version the version string 427 * @return the array of {@link String} and {@link Integer} components; never null 428 */ 429 protected static Object[] getVersionComponents( String version ) { 430 if (version == null) return new Object[] {}; 431 version = version.trim(); 432 if (version.length() == 0) return new Object[] {}; 433 String[] parts = version.split("[\\.\\-/,]"); 434 if (parts == null) return new Object[] {}; 435 Object[] components = new Object[parts.length]; 436 for (int i = 0, len = parts.length; i < len; i++) { 437 String part = parts[i].trim(); 438 Object component = part; 439 try { 440 component = Integer.parseInt(part); 441 } catch (NumberFormatException e) { 442 // If there are any problems, we don't treat it as an integer 443 } 444 components[i] = component; 445 } 446 return components; 447 } 448 449 /** 450 * {@inheritDoc} 451 */ 452 @Override 453 public MavenId clone() { 454 return new MavenId(this.groupId, this.artifactId, this.version.toString(), this.classifier); 455 } 456 }