001: package org.antmod.scm;
002:
003: import java.text.ParseException;
004: import java.util.StringTokenizer;
005:
006: import org.apache.commons.lang.math.NumberUtils;
007:
008: /**
009: * Representation of an SCM url.
010: * <p/>
011: * The format of this element is as follows:
012: * <pre>scm:<provider>:<provider-parameters></pre>
013: * <p/>
014: *
015: * For cvs, the format for pserver repositories should be (password is optional):
016: * <ul>
017: * <pre>scm:cvs:pserver:user[:password]@host:/cvs/root:module-name</pre>
018: * </ul>
019: *
020: * For local system repositories
021: * <ul>
022: * <pre>scm:cvs:local:ignored:/cvs/root:module-name</pre>
023: * </ul>
024: *
025: * For ssh access:
026: * <ul>
027: * <pre>scm:cvs:ext:user@host:/cvs/root:module-name</pre>
028: * </ul>
029: *
030: * Subversion syntax (between brackets is optional):
031: * <ul>
032: * <pre>scm:svn:protocol://[user[:password]@]host/path/to/svn[:module-name]</pre>
033: * </ul>
034: *
035: * Subversion urls using this syntax look for example like this:
036: * <ul>
037: * <pre>scm:svn:http://user:password@host.example.com/path/to/svn</pre>
038: * <pre>scm:svn:http://host.example.com:8080/path/to/svn</pre>
039: * <pre>scm:svn:svn://host.example.com/path/to/svn</pre>
040: * <pre>scm:svn:file:///C:/path/to/localsvn</pre>
041: * <pre>scm:svn:file:///cygdrive/c/path/to/localsvn</pre>
042: * </ul>
043: *
044: * Remember that CVS will expect an environment variable called
045: * <code>CVS_RSH</code> to be set, typically to <code>ssh</code> or your ssh client.
046: *
047: * @author Klaas Waslander
048: */
049: public class ScmUrl {
050: private String urlString;
051: private String type;
052: private String protocol;
053: private String user;
054: private String password;
055: private String host;
056: private String path;
057: private String module;
058:
059: /**
060: * Constructs new ScmUrl instance based on the parsed
061: * contents of the given scm url string.
062: * @param urlString The string representing the SCM url.
063: */
064: public ScmUrl(String urlString) throws IllegalArgumentException,
065: ParseException {
066: this .urlString = urlString;
067: StringTokenizer st = new StringTokenizer(urlString, ":");
068: if (!st.hasMoreTokens() || !st.nextToken().equals("scm")) {
069: throw new IllegalArgumentException("Invalid URL \""
070: + urlString + "\": does not start with 'scm:'");
071: }
072: this .type = st.nextToken();
073:
074: if (this .type.equals("cvs")) {
075: cvsParse(st);
076: } else if (this .type.equals("svn")) {
077: svnParse(st);
078: } else {
079: throw new IllegalArgumentException("Unknown URL type: '"
080: + this .type + "'");
081: }
082: }
083:
084: /** utility to parse CVS tokens, assuming first token with type is already parsed */
085: private void cvsParse(StringTokenizer st) throws ParseException {
086: // required protocol element
087: if (!st.hasMoreTokens()) {
088: throw new ParseException(
089: "Element 'protocol' missing from CVS url", 1);
090: }
091: this .protocol = st.nextToken();
092:
093: // required user-host element
094: if (!st.hasMoreTokens()) {
095: throw new ParseException(
096: "Element 'user@host' missing from CVS url", 2);
097: }
098:
099: // for non-local URLs parse
100: String userHost = st.nextToken();
101: if (!this .protocol.equals("local")) {
102: int atIndex = userHost.indexOf("@");
103: if (atIndex < 0) {
104: // safely assume password in URL
105: this .user = userHost;
106:
107: if (!st.hasMoreTokens()) {
108: throw new ParseException(
109: "After username \""
110: + this .user
111: + "\" the element 'password@host' is missing from CVS url",
112: 3);
113: }
114: String passHost = st.nextToken();
115: atIndex = passHost.indexOf("@");
116: if (atIndex < 0) {
117: throw new ParseException(
118: "After username \""
119: + this .user
120: + "\" the element 'password@host' is missing from CVS url",
121: 3);
122: } else {
123: this .password = passHost.substring(0, atIndex);
124: this .host = passHost.substring(atIndex + 1);
125: }
126: } else {
127: // no password in URL
128: this .user = userHost.substring(0, atIndex);
129: this .host = userHost.substring(atIndex + 1);
130: }
131: }
132:
133: // required path element
134: if (this .protocol.equals("local")
135: && !userHost.equalsIgnoreCase("ignored")) {
136: this .path = userHost;
137: } else {
138: if (!st.hasMoreTokens()) {
139: throw new ParseException(
140: "Element 'path' missing from CVS url", 4);
141: }
142: this .path = st.nextToken();
143: }
144:
145: // handle windows path with drive letter in it
146: if (this .path.length() == 1) {
147: // assume this is a windows drive letter with path following
148: this .path = this .path + ":" + st.nextToken();
149: this .protocol = null;
150: }
151:
152: // check if path is prepended with a port number
153: int slashIndex = this .path.indexOf("/");
154: if (slashIndex > 0
155: && NumberUtils.isNumber(this .path.substring(0,
156: slashIndex))) {
157: this .host = this .host + ":"
158: + this .path.substring(0, slashIndex);
159: this .path = this .path.substring(slashIndex);
160: }
161:
162: // optional module element
163: if (st.hasMoreTokens()) {
164: this .module = st.nextToken();
165: }
166: }
167:
168: /** utility to parse subversion tokens, assuming first token with type is already parsed */
169: private void svnParse(StringTokenizer st) throws ParseException {
170: // required protocol element
171: if (!st.hasMoreTokens()) {
172: throw new ParseException(
173: "Element 'protocol' missing from Subversion url", 1);
174: }
175: this .protocol = st.nextToken();
176:
177: // required user-host element
178: if (!st.hasMoreTokens()) {
179: throw new ParseException(
180: "Subversion url seems to have only protocol part.",
181: 2);
182: }
183:
184: String userHost = st.nextToken();
185: if (!userHost.startsWith("//")) {
186: throw new ParseException(
187: "Subversion url does not have '//' after protocol.",
188: 3);
189: }
190:
191: if (protocol.equals("file")) {
192: while (userHost.startsWith("/")) {
193: userHost = userHost.substring(1);
194: }
195: this .path = userHost;
196: } else {
197: // http or svn protocol
198: userHost = userHost.substring(2);
199:
200: int atIndex = userHost.indexOf("@");
201: int slashIndex = userHost.indexOf("/");
202: if (atIndex > 0) {
203: // there is a username and no password here
204: this .user = userHost.substring(0, atIndex);
205: userHost = userHost.substring(atIndex + 1);
206: } else if (slashIndex < 0) {
207: // there is a username, and password follows in next token
208: this .user = userHost;
209:
210: if (!st.hasMoreTokens()) {
211: throw new ParseException(
212: "After username \""
213: + this .user
214: + "\" the element 'password@host' is missing from Subversion url",
215: 4);
216: }
217: String passHost = st.nextToken();
218: atIndex = passHost.indexOf("@");
219: if (atIndex < 0) {
220: throw new ParseException(
221: "After username \""
222: + this .user
223: + "\" the element 'password@host' is missing from CVS url",
224: 4);
225: }
226: this .password = passHost.substring(0, atIndex);
227: userHost = passHost.substring(atIndex + 1);
228: } else {
229: // no username or password, nothing to do
230: }
231:
232: // parse hostname and path next
233: slashIndex = userHost.indexOf("/");
234: if (slashIndex < 0) {
235: // check if a port number is inserted before the path
236: if (st.hasMoreTokens()) {
237: String portPath = st.nextToken();
238: slashIndex = portPath.indexOf("/");
239: if (slashIndex < 0) {
240: throw new ParseException(
241: "Element 'path' missing from Subversion url, no '/' found that separates host and path",
242: 5);
243: } else {
244: this .host = userHost
245: + portPath.substring(0, slashIndex);
246: this .path = portPath.substring(slashIndex);
247: }
248: } else {
249: throw new ParseException(
250: "Element 'path' missing from Subversion url, no '/' found that separates host and path",
251: 5);
252: }
253: } else {
254: this .host = userHost.substring(0, slashIndex);
255: this .path = userHost.substring(slashIndex);
256: }
257: }
258:
259: // make sure path does not end with a slash
260: if (this .path.endsWith("/")) {
261: this .path = this .path.substring(0, this .path.length() - 1);
262: }
263: if (this .path.length() == 1) {
264: // assume this is a windows drive letter with path following
265: this .path = this .path + ":" + st.nextToken();
266: }
267:
268: // optional module element
269: if (st.hasMoreTokens()) {
270: this .module = st.nextToken();
271: }
272: }
273:
274: /**
275: * @return
276: */
277: public String getHost() {
278: return host;
279: }
280:
281: /**
282: * @return
283: */
284: public String getModule() {
285: return module;
286: }
287:
288: /**
289: * @return
290: */
291: public String getPassword() {
292: return password;
293: }
294:
295: /**
296: * @return
297: */
298: public String getPath() {
299: return path;
300: }
301:
302: /**
303: * @return
304: */
305: public String getProtocol() {
306: return protocol;
307: }
308:
309: /**
310: * @return
311: */
312: public String getType() {
313: return type;
314: }
315:
316: /**
317: * @return
318: */
319: public String getUser() {
320: return user;
321: }
322:
323: /**
324: * Get the original URL string with which this ScmUrl was created.
325: */
326: public String getUrlString() {
327: return urlString;
328: }
329:
330: /**
331: * Get the original URL string with which this ScmUrl was created,
332: * without the module element (if any).
333: */
334: public String getUrlStringNoModule() {
335: if (getModule() != null) {
336: int lastColon = urlString.lastIndexOf(":");
337: if (lastColon <= 0) {
338: throw new IllegalStateException(
339: "URL string appears to have a module element but no ':' in its url string.");
340: }
341: return urlString.substring(0, lastColon);
342: } else {
343: return urlString;
344: }
345: }
346: }
|