001: /*
002: * All content copyright (c) 2003-2007 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tc.bundles;
005:
006: import com.tc.util.Assert;
007:
008: import java.util.regex.Matcher;
009: import java.util.regex.Pattern;
010:
011: /**
012: * This class provides static helper methods to convert Maven identifiers to valid OSGi
013: * bundle identifiers. For example, converting Maven artifactId to OSGi bundle symbolic names.
014: */
015: public class MavenToOSGi {
016: private MavenToOSGi() {
017: }
018:
019: // Cache pattern defining a valid Maven version: x[.y[.z]][-classifier][-i]
020: // which is done with a regex like digits(.digits(.digits))-any
021: // reminder: \\ is Java string escaping,
022: // (?:) is a non-capture group,
023: // ()? is an optional group
024: // \d is a digit
025: private static final Pattern MAVEN_VERSION_PATTERN = Pattern
026: .compile("^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?(?:-(.*))?$");
027:
028: // Cache pattern defining a valid OSGi version: x[.y[.z[.qualifier]]]
029: // which is done with a regex like digits(.digits(.digits))-any
030: // reminder: \\ is Java string escaping,
031: // (?:) is a non-capture group,
032: // ()? is an optional group
033: // \d is a digit
034: private static final Pattern OSGI_VERSION_PATTERN = Pattern
035: .compile("^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(\\.(.*))?)?)?$");
036:
037: // Cache pattern defining characters that aren't valid in OSGi symbolic name or version qualifier for speed
038: private static final Pattern INVALID_OSGI_CHAR_PATTERN = Pattern
039: .compile("[^a-zA-Z0-9._\\-]");
040:
041: private static String replaceInvalidChars(String value) {
042: return INVALID_OSGI_CHAR_PATTERN.matcher(value).replaceAll("_");
043: }
044:
045: /**
046: * Valid OSGi symbolic name format from OSGi v4 spec:
047: * <pre>
048: * token ::= ( alphanum | '_' | '-' )+
049: * symbolic-name :: = token('.'token)*
050: * </pre>
051: *
052: * However, I've seen errors from Knoplerfish using - in symbolic names, so we are
053: * currently replacing anything other than alphanumeric or _ with _. If groupId and artifact are
054: * both null or empty string, then you'll see some
055: *
056: * @param groupId Maven groupId, like "org.terracotta.modules", might be null
057: * @param artifactId Maven artifactId, like "tim-terracotta-cache", might be null
058: * @return Valid OSGi symbolic name format based on the groupId and artifactId
059: * @throws IllegalArgumentException If groupId AND artifactId are null or empty string
060: */
061: public static String artifactIdToSymbolicName(String groupId,
062: String artifactId) {
063: String name = groupId;
064: if (name == null) {
065: name = "";
066: }
067:
068: if (artifactId != null && artifactId.length() > 0) {
069: if (name.length() > 0) {
070: name = name + ".";
071: }
072: name = name + artifactId;
073: }
074:
075: if (name.length() == 0) {
076: throw new IllegalArgumentException(
077: "Maven groupId and artifactId are both null or empty, at least one must be defined.");
078: }
079:
080: return replaceInvalidChars(name);
081: }
082:
083: /**
084: * Maven versions are defined as:
085: * <pre>
086: * x[.y[.z]][-classifier][-i]
087: * </pre>
088: *
089: * see: http://docs.codehaus.org/display/MAVENUSER/Dependency+Mechanism
090: *
091: * The OSGi spec defines the OSGi bundle version as:
092: *
093: * <pre>
094: * version ::= major( '.' minor ( '.' micro ( '.' qualifier )? )? )?
095: * major ::= number
096: * minor ::= number
097: * micro ::= number
098: * qualifier ::= ( alphanum | '_' | '-' )+
099: * </pre>
100: *
101: * The -classifer-i part (such as -ALPHA-1 or -SNAPSHOT) will be parsed upstream, specifically
102: * the leading -, which should not be passed. So, a Maven version 1.0-SNAPSHOT should be passed as
103: * {1, 0, 0, SNAPSHOT} to this method. This method will rebuild it and return it as "1.0.0.SNAPSHOT".
104: *
105: * @param majorVersion Major version from Maven, must be >= 0 (0 if not specified)
106: * @param minorVersion Minor version from Maven, must be >= 0 (0 if not specified)
107: * @param incrementalVersion Incremental version from Maven, must be >= 0 (0 if not specified)
108: * @param classifier Classifier string from Maven, this is expected to be parsed and not contain the leading dash, may be null
109: */
110: public static String projectVersionToBundleVersion(
111: int majorVersion, int minorVersion, int incrementalVersion,
112: String classifier) {
113: checkNonNegative(majorVersion, "major version");
114: checkNonNegative(minorVersion, "minor version");
115: checkNonNegative(minorVersion, "micro version");
116:
117: String projectVersion = majorVersion + "." + minorVersion + "."
118: + incrementalVersion;
119:
120: if (classifier != null && classifier.length() > 0) {
121: return projectVersion + "."
122: + replaceInvalidChars(classifier);
123: } else {
124: return projectVersion;
125: }
126: }
127:
128: /**
129: * Parse a Maven version string of the form "digits(.digits(.digits))-classifier". Maven
130: * implements this parsing in a class called org.apache.maven.artifact.versioning.DefaultArtifactVersion,
131: * which I theoretically could have used except I would have added a Maven dependency to common
132: * which I didn't want to do.
133: * @param mavenVersion Maven version string, such as 1.2.3-RC-1
134: * @return OSGi bundle version to use, such as 1.2.3.RC_1
135: */
136: public static String projectVersionToBundleVersion(
137: String mavenVersion) {
138: if (mavenVersion == null) {
139: mavenVersion = "";
140: }
141:
142: int major = 0;
143: int minor = 0;
144: int micro = 0;
145: String classifier = null;
146:
147: Matcher matcher = MAVEN_VERSION_PATTERN.matcher(mavenVersion);
148: if (matcher.matches()) {
149: // Grab the capturing groups from the pattern, only first is required, others may be null, 0=whole match, so ignored
150: String majorStr = matcher.group(1);
151: String minorStr = matcher.group(2);
152: String microStr = matcher.group(3);
153: classifier = matcher.group(4);
154:
155: major = Integer.parseInt(majorStr);
156: if (minorStr != null) {
157: minor = Integer.parseInt(minorStr);
158: }
159: if (microStr != null) {
160: micro = Integer.parseInt(microStr);
161: }
162:
163: } else if (OSGI_VERSION_PATTERN.matcher(mavenVersion).matches()) {
164: // if doesn't match pattern, check if this is already a valid osgi format
165: return mavenVersion;
166:
167: } else {
168: // if doesn't match pattern, use whole thing as classifier
169: // some Maven examples show something like "RELEASE" as a version == 0.0.0-RELEASE
170: classifier = mavenVersion;
171: }
172:
173: return projectVersionToBundleVersion(major, minor, micro,
174: classifier);
175: }
176:
177: private static void checkNonNegative(int value,
178: String valueDescription) {
179: if (value < 0) {
180: Assert.fail("Invalid " + valueDescription + ": " + value
181: + ", must be >= 0");
182: }
183: }
184: }
|