KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > lib > cvsclient > CVSRoot


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Rami Ojares. Portions created by Rami Ojares are Copyright (C) 2003.
17  * All Rights Reserved.
18  *
19  * Contributor(s): Rami Ojares
20  */

21
22 package org.netbeans.lib.cvsclient;
23
24 import java.io.*;
25 import java.util.*;
26 import org.netbeans.lib.cvsclient.connection.Connection;
27 import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
28
29 /**
30     <p>
31         CVSRoot represents the cvsroot that identifies the cvs repository's location
32         and the means to get to it. We use following definition of cvsroot:
33     </p>
34
35     <code>
36         [:method:][[user][:password]@][hostname[:[port]]]/path/to/repository
37     </code>
38
39     <p>
40         When the method is not defined, we treat it as local or ext method
41         depending on whether the hostname is present or not.
42         This gives us two different formats:
43     </p>
44     
45     <h4>1. Local format</h4>
46     <code>[:method:]/path/to/repository</code> or
47     <code>(:local:|:fork:)anything<code>
48     
49     <h4>2. Server format</h4>
50     <code>
51         [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
52     </code>
53     
54     <p>
55         There are currently 6 different methods that are implemented by 3 different connection classes.
56         <ul>
57             <li>:local:, :fork: & no-method --> LocalConnection (LOCAL_FORMAT)</li>
58             <li>:server: & :ext: --> SSH2Connection (SERVER_FORMAT)</li>
59             <li>:pserver: --> PServerConnection (SERVER_FORMAT)</li>
60         </li>
61         gserver and kserver are not included. Environment variables are not used (like CVS_RSH).
62     </p>
63     <p>
64         local and no-method work like fork. They start the cvs server program on
65         the local machine thus using the remote protocol on the local machine.
66         According to Cederqvist fork's relation to local is:
67         "In other words it does pretty much the same thing as :local:,
68         but various quirks, bugs and the like are those of the remote CVS rather
69         than the local CVS."
70     </p>
71     <p>
72         server is using ssh. According to Cederqvist it would use an internal RSH
73         client but since it is not known what this exactly means it just uses ssh.
74         Note ssh is able only to use ssh protocol version 2 which is recommended anyways.
75     </p>
76     <p>
77         Note that cvsroot is case sensitive so remember to write the method in lowercase.
78         You can succesfully construct a cvsroot that has a different method but
79         ConnectionFactory will be unable to create a connection for such CVSRoot.
80     </p>
81     <p>
82         CVSRoot object keeps the cvsroot in components that are
83         <ul>
84             <li>method</li>
85             <li>user</li>
86             <li>password</li>
87             <li>host</li>
88             <li>port</li>
89             <li>repository</li>
90         </ul>
91         You can change these components through setters.
92         When you ask fo the cvsroot string representation it is constructed based
93         on the current values of the components. The returned cvsroot never contains
94         the password for security reasons.
95         Also "no-method" is always represented as local method.
96     </p>
97 */

98 public class CVSRoot {
99     
100     /** A constant representing the "local" connection method. */
101     public static final String JavaDoc METHOD_LOCAL = "local"; // NOI18N
102
/** A constant representing the "fork" connection method. */
103     public static final String JavaDoc METHOD_FORK = "fork"; // NOI18N
104
/** A constant representing the "server" connection method. */
105     public static final String JavaDoc METHOD_SERVER = "server"; // NOI18N
106
/** A constant representing the "pserver" connection method. */
107     public static final String JavaDoc METHOD_PSERVER = "pserver"; // NOI18N
108
/** A constant representing the "ext" connection method. */
109     public static final String JavaDoc METHOD_EXT = "ext"; // NOI18N
110

111     // the connection method. no-method is represented by null
112
// the value is interned for fast comparisons
113
private String JavaDoc method;
114     // user (default = null)
115
private String JavaDoc username;
116     // password (default = null)
117
private String JavaDoc password;
118     // hostname (default = null)
119
private String JavaDoc hostname;
120     // port (default = 0) 0 means that port is not used and the protocol will use default protocol
121
private int port;
122     // repository as string representation
123
private String JavaDoc repository;
124
125     /**
126      * Parse the CVSROOT string into CVSRoot object.
127      * The CVSROOT string must be of the form
128      * [:method:][[user][:password]@][hostname:[port]]/path/to/repository
129      */

130     public static CVSRoot parse(String JavaDoc cvsroot) throws IllegalArgumentException JavaDoc {
131         return new CVSRoot(cvsroot);
132     }
133     
134     /**
135      * Construct CVSRoot from Properties object.
136      * The names are exactly the same as the attribute names in this class.
137      */

138     public static CVSRoot parse(Properties props) throws IllegalArgumentException JavaDoc {
139         return new CVSRoot(props);
140     }
141     
142     /**
143      * This constructor allows to construct CVSRoot from Properties object.
144      * The names are exactly the same as the attribute names in this class.
145      */

146     protected CVSRoot(Properties props) throws IllegalArgumentException JavaDoc {
147
148         String JavaDoc mtd = props.getProperty("method");
149         if (mtd != null) {
150             this.method = mtd.intern();
151         }
152
153         // host & port
154
this.hostname = props.getProperty("hostname");
155         
156         if (this.hostname.length() == 0)
157             this.hostname = null;
158         //this.localFormat = this.hostname == null || this.hostname.length() == 0;
159

160         if (this.hostname != null) {
161             
162             // user & password (they are always optional)
163
this.username = props.getProperty("username");
164             this.password = props.getProperty("password");
165             
166             // host & port
167
// We already have hostname
168
try {
169                 int p = Integer.parseInt(props.getProperty("port"));
170                 if (p > 0)
171                     this.port = p;
172                 else
173                     throw new IllegalArgumentException JavaDoc("The port is not a positive number.");
174             }
175             catch (NumberFormatException JavaDoc e) {
176                 throw new IllegalArgumentException JavaDoc("The port is not a number: '"+props.getProperty("port")+"'.");
177             }
178         }
179
180         // and the most important which is repository
181
String JavaDoc r = props.getProperty("repository");
182         if (r == null)
183             throw new IllegalArgumentException JavaDoc("Repository is obligatory.");
184         else
185             this.repository = r;
186     }
187     
188     /**
189      * Breaks the string representation of cvsroot into it's components:
190      *
191      * The valid format (from the cederqvist) is:
192      *
193      * :method:[[user][:password]@]hostname[:[port]]/path/to/repository
194      *
195      * Also parse alternative format from WinCVS, which stores connection
196      * parameters such as username and hostname in method options:
197      *
198      * :method[;option=arg...]:other_connection_data
199      *
200      * e.g. :pserver;username=anonymous;hostname=localhost:/path/to/repository
201      *
202      * For CVSNT compatability it also supports following local repository path format
203      *
204      * driveletter:path\\path\\path
205      *
206      */

207     protected CVSRoot(String JavaDoc cvsroot) throws IllegalArgumentException JavaDoc {
208         
209         int colonPosition = 0;
210         boolean localFormat;
211         if (cvsroot.startsWith(":") == false) {
212             
213             // no method mentioned guess it using heuristics
214

215             localFormat = cvsroot.startsWith("/");
216             if (localFormat == false) {
217                 if (cvsroot.indexOf(':') == 1 && cvsroot.indexOf('\\') == 2) {
218                     //#67504 it looks like windows drive => local
219
method = METHOD_LOCAL;
220                     repository = cvsroot;
221                     normalize();
222                     return;
223                 }
224                 colonPosition = cvsroot.indexOf(':');
225                 if (colonPosition < 0) {
226                     // No colon => server format, but there must be a '/' in the middle
227
int slash = cvsroot.indexOf('/');
228                     if (slash < 0) {
229                         throw new IllegalArgumentException JavaDoc("CVSROOT must be an absolute pathname.");
230                     }
231                     method = METHOD_SERVER;
232                 } else {
233                     method = METHOD_EXT;
234                 }
235                 colonPosition = 0;
236             } else {
237                 method = METHOD_LOCAL;
238             }
239         } else {
240             // connection method is given so parse it
241

242             colonPosition = cvsroot.indexOf(':', 1);
243             if (colonPosition < 0)
244                 throw new IllegalArgumentException JavaDoc("The connection method does not end with ':'.");
245             int methodNameEnd = colonPosition;
246             int semicolonPosition = cvsroot.indexOf(";", 1);
247
248             if (semicolonPosition != -1 && semicolonPosition < colonPosition) {
249                 // method has options
250
methodNameEnd = semicolonPosition;
251                 String JavaDoc options = cvsroot.substring(semicolonPosition +1, colonPosition);
252                 StringTokenizer tokenizer = new StringTokenizer(options, "=;");
253                 while (tokenizer.hasMoreTokens()) {
254                     String JavaDoc option = tokenizer.nextToken();
255                     if (tokenizer.hasMoreTokens() == false) {
256                         throw new IllegalArgumentException JavaDoc("Undefined " + option + " option value.");
257                     }
258                     String JavaDoc value = tokenizer.nextToken();
259                     if ("hostname".equals(option)) { // NOI18N
260
hostname = value;
261                     } else if ("username".equals(option)) { // NOI18N
262
username = value;
263                     } else if ("password".equals(option)) { // NOI18N
264
password = value;
265                     } if ("port".equals(option)) { // NOI18N
266
try {
267                             port = Integer.parseInt(value, 10);
268                         } catch (NumberFormatException JavaDoc ex) {
269                             throw new IllegalArgumentException JavaDoc("Port option must be number.");
270                         }
271                     }
272                 }
273             }
274
275             this.method = cvsroot.substring(1, methodNameEnd).intern();
276
277             //#65742 read E!e>2.0 workdirs
278
if ("extssh".equals(method)) { // NOI18N
279
method = METHOD_EXT;
280             }
281             // CVSNT supports :ssh;ver=2:username@cvs.sf.net:/cvsroot/xoops
282
if ("ssh".equals(method)) { // NOI18N
283
method = METHOD_EXT;
284             }
285             colonPosition++;
286             // Set local format in case of :local: or :fork: methods.
287
localFormat = isLocalMethod(this.method);
288         }
289         
290         if (localFormat) {
291             // everything after method is repository in local format
292
this.repository = cvsroot.substring(colonPosition);
293         } else {
294             /* So now we parse SERVER_FORMAT
295             :method:[[user][:password]@]hostname[:[port]]/reposi/tory
296             ALGORITHM:
297             - find the first '@' character
298                 - not found:
299                     - find the first ':'
300                         - not found
301                             - find the first '/'
302                                 - not found
303                                     - exception
304                                 - found
305                                     - parse hostname/path/to/repository
306                         - found
307                             - parse rest
308                 - found
309                     - find the following ':' character
310                         - not found
311                             - exception
312                         - found
313                             parse rest
314             */

315             
316             int startSearch = cvsroot.indexOf('@', colonPosition);
317             if (startSearch < 0) startSearch = colonPosition;
318             String JavaDoc userPasswdHost;
319             int pathBegin = -1;
320             int hostColon = cvsroot.indexOf(':', startSearch);
321             if (hostColon == -1) {
322                 pathBegin = cvsroot.indexOf('/', startSearch);
323                 if (pathBegin < 0) {
324                     throw new IllegalArgumentException JavaDoc("cvsroot " + cvsroot + " is malformed, host name is missing.");
325                 } else {
326                     userPasswdHost = cvsroot.substring(colonPosition, pathBegin);
327                 }
328             } else {
329                 userPasswdHost = cvsroot.substring(colonPosition, hostColon);
330             }
331
332             int at = userPasswdHost.indexOf('@');
333             if (at == -1) {
334                 // there is no user or password, only hostname before port
335
if (userPasswdHost.length() > 0) {
336                     this.hostname = userPasswdHost;
337                 }
338             }
339             else {
340                 // there is user, password or both before hostname
341
// up = username, password or both
342
String JavaDoc up = userPasswdHost.substring(0, at);
343                 if (up.length() > 0) {
344                     int upDivider = up.indexOf(':');
345                     if (upDivider != -1) {
346                         this.username = up.substring(0, upDivider);
347                         this.password = up.substring(upDivider+1);
348                     }
349                     else {
350                         this.username = up;
351                     }
352                 }
353
354                 // hostname
355
this.hostname = userPasswdHost.substring(at+1);
356             }
357             
358             if (hostname == null || hostname.length() == 0) {
359                 throw new IllegalArgumentException JavaDoc("Didn't specify hostname in CVSROOT '"+cvsroot+"'.");
360             }
361
362             /*
363             Now we are left with port (optional) and repository after hostColon
364             pr = possible port and repository
365             */

366             if (hostColon > 0) {
367                 String JavaDoc pr = cvsroot.substring(hostColon+1);
368                 int index = 0;
369                 int port = 0;
370                 char c;
371                 while (pr.length() > index && Character.isDigit(c = pr.charAt(index))) {
372                     int d = Character.digit(c, 10);
373                     port = port*10 + d;
374                     index++;
375                 }
376                 this.port = port;
377                 if (index > 0) pr = pr.substring(index);
378                 if (pr.startsWith(":")) { // NOI18N
379
pr = pr.substring(1);
380                 }
381                 this.repository = pr;
382             } else {
383                 this.port = 0;
384                 this.repository = cvsroot.substring(pathBegin);
385             }
386         }
387         normalize();
388     }
389     
390     /**
391      * Removes trailing slashes from repository.
392      */

393     private void normalize() {
394         int i = repository.length();
395         int n = i - 1;
396         while (i > 0 && repository.charAt(--i) == '/');
397         if (i < n) {
398             repository = repository.substring(0, i + 1);
399         }
400     }
401
402     /**
403      * Test whether this cvsroot describes a local connection or remote connection.
404      * The connection is local if and only if the host name is <code>null</code>.
405      * E.g. for local or fork methods.
406      */

407     public boolean isLocal() {
408         return hostname == null;
409     }
410     
411     /**
412         <ul>
413             <li>
414                 <code>LOCAL_FORMAT --> :method:/reposi/tory</code>
415                 <br/>
416                 "no method" is always represented internally as null
417             </li>
418             <li>
419                 <code>SERVER_FORMAT --> :method:user@hostname:[port]/reposi/tory</code>
420                 <br/>
421                 Password is never included in cvsroot string representation. Use getPassword to get it.
422             </li>
423         </ul>
424     */

425     
426     public String JavaDoc toString() {
427         
428         if (this.hostname == null) {
429             if (this.method == null)
430                 return this.repository;
431             
432             return ":" + this.method + ":" + this.repository;
433         } else {
434         
435             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
436             
437             if (this.method != null) {
438                 buf.append(':');
439                 buf.append(this.method);
440                 buf.append(':');
441             }
442             
443             // don't put password in cvsroot
444
if (this.username != null) {
445                 buf.append(this.username);
446                 buf.append('@');
447             }
448             
449             // hostname
450
buf.append(this.hostname);
451             buf.append(':');
452             
453             // port
454
if (this.port > 0)
455                 buf.append(this.port);
456             
457             // repository
458
buf.append(this.repository);
459             
460             return buf.toString();
461         }
462     }
463     
464     /**
465     <p>
466         With this method it is possible to compare how close two CVSRoots are to each other. The possible values are:
467     </p>
468     
469     <ul>
470         <li>-1 = not compatible - if none of the below match</li>
471         <li>0 = when equals(..) returns true</li>
472         <li>1 = refers to same repository on the same machine using same method on same port and same user</li>
473         <li>2 = refers to same repository on the same machine using same method</li>
474         <li>3 = refers to same repository on the same machine</li>
475     </ul>
476     */

477     public int getCompatibilityLevel(CVSRoot compared) {
478         
479         if (equals(compared))
480             return 0;
481         
482         
483         boolean sameRepository = isSameRepository(compared);
484         boolean sameHost = isSameHost(compared);
485         boolean sameMethod = isSameMethod(compared);
486         boolean samePort = isSamePort(compared);
487         boolean sameUser = isSameUser(compared);
488         
489         if (sameRepository && sameHost && sameMethod && samePort && sameUser)
490             return 1;
491         else if (sameRepository && sameHost && sameMethod)
492             return 2;
493         else if (sameRepository && sameHost)
494             return 3;
495         else
496             return -1;
497     }
498     
499     private boolean isSameRepository(CVSRoot compared) {
500         if (this.repository.equals(compared.repository)) {
501             return true;
502         }
503         try {
504             if (
505                 (new File(this.repository)).getCanonicalFile().equals(
506                     new File(compared.repository).getCanonicalFile()
507                 )
508             )
509                 return true;
510             else
511                 return false;
512         }
513         catch (IOException ioe) {
514             // something went wrong when invoking getCanonicalFile() so return false
515
return false;
516         }
517     }
518     
519     private boolean isSameHost(CVSRoot compared) {
520         String JavaDoc comparedHostName = compared.getHostName();
521         if (this.hostname == comparedHostName) {
522             return true;
523         }
524         if (this.hostname != null) {
525             return this.hostname.equalsIgnoreCase(comparedHostName);
526         } else {
527             return false;
528         }
529     }
530     
531     private boolean isSameMethod(CVSRoot compared) {
532         if (this.method == null)
533             if (compared.getMethod() == null)
534                 return true;
535             else
536                 return false;
537         else if (this.method.equals(compared.getMethod()))
538             return true;
539         else
540             return false;
541     }
542     
543     private boolean isSamePort(CVSRoot compared) {
544         if (this.isLocal() == compared.isLocal())
545             if (this.isLocal())
546                 return true;
547             else if (this.port == compared.getPort())
548                 return true;
549             else {
550                 try {
551                     Connection c1 = ConnectionFactory.getConnection(this);
552                     Connection c2 = ConnectionFactory.getConnection(compared);
553                     // Test actual ports used by the connections.
554
// This is necessary in case that port in CVSRoot is zero and the conection is using some default port number.
555
return c1.getPort() == c2.getPort();
556                 } catch (IllegalArgumentException JavaDoc iaex) {
557                     return false;
558                 }
559             }
560         else
561             return false;
562     }
563     
564     private boolean isSameUser(CVSRoot compared) {
565         String JavaDoc user = compared.getUserName();
566         if (user == getUserName()) return true;
567         if (user != null) {
568             return user.equals(getUserName());
569         }
570         return false;
571     }
572     
573     /**
574      * CVSRoots are equal if their toString representations are equal.
575      * This puts some extra pressure on the toString method that should be defined very precisely.
576      */

577     public boolean equals(Object JavaDoc o) {
578         // This should be null safe, right?
579
if (!(o instanceof CVSRoot))
580             return false;
581         
582         CVSRoot compared = (CVSRoot) o;
583         
584         if (toString().equals(compared.toString()))
585             return true;
586         
587         return false;
588     }
589     
590     public int hashCode() {
591         return toString().hashCode();
592     }
593     
594     /**
595      * Get the format of this cvsroot.
596      * @return Either {@link #LOCAL_FORMAT} or {@link #SERVER_FORMAT}.
597      *
598     public int getUrlFormat() {
599         return urlFormat;
600     }
601      */

602     
603     /**
604      * Get the connection method.
605      * @return The connection method or <code>null</code> when no method is defined.
606      */

607     public String JavaDoc getMethod() {
608         return method;
609     }
610     
611     /**
612     setting the method has effects on other components.
613     The method might change
614     - urlFormat
615     - username and password
616     - hostname/port
617     If urlFormat becomes LOCAL_FORMAT then username, password and hostname are set to null and port to 0.
618     If urlFormat becomes SERVER_FORMAT then hostname must not be null.
619     */

620     protected void setMethod(String JavaDoc method) {
621         if (method != null) {
622             this.method = method.intern();
623         } else {
624             method = null;
625         }
626         if (isLocalMethod(method)) {
627             this.username = null;
628             this.password = null;
629             this.hostname = null;
630             this.port = 0;
631         }
632         else {
633             if (this.hostname == null)
634                 throw new IllegalArgumentException JavaDoc("Hostname must not be null when setting a remote method.");
635         }
636     }
637     
638     // test whether the method is "local" or "fork"
639
private boolean isLocalMethod(String JavaDoc method) {
640         return METHOD_LOCAL == method || METHOD_FORK == method;
641     }
642     
643     /**
644      * Get the user name.
645      * @return The user name or code>null</code> when the user name is not defined.
646      */

647     public String JavaDoc getUserName() {
648         return username;
649     }
650     /**
651      * Set the user name.
652      * @param username The user name.
653      */

654     protected void setUserName(String JavaDoc username) {
655         this.username = username;
656     }
657     
658     /**
659      * Get the password.
660      * @return The password or <code>null</code> when the password is not defined.
661      */

662     public String JavaDoc getPassword() {
663         return this.password;
664     }
665     /**
666      * Set the password.
667      * @param password The password
668      */

669     protected void setPassword(String JavaDoc password) {
670         this.password = password;
671     }
672     
673     /**
674      * Get the host name.
675      * @return The host name or <code>null</code> when the host name is
676      * not defined
677      */

678     public String JavaDoc getHostName() {
679         return this.hostname;
680     }
681     /**
682      * Set the host name.
683      * @param hostname The host name or <code>null</code> when the host name is
684      * not defined.
685      */

686     protected void setHostName(String JavaDoc hostname) {
687         this.hostname = hostname;
688     }
689     
690     /**
691      * Get the port number.
692      * @return The port number or zero when the port is not defined.
693      */

694     public int getPort() {
695         return this.port;
696     }
697     /**
698      * Set the port number.
699      * @param port The port number or zero when the port is not defined.
700      */

701     public void setPort(int port) {
702         this.port = port;
703     }
704     
705     /**
706      * Get the repository.
707      * @return The repository. This is never <code>null</code>.
708      */

709     public String JavaDoc getRepository() {
710         return repository;
711     }
712     /**
713      * Set the repository.
714      * @param repository The repository. Must not be <code>null</code>.
715      */

716     protected void setRepository(String JavaDoc repository) {
717         if (repository == null) {
718             throw new IllegalArgumentException JavaDoc("The repository must not be null.");
719         }
720         this.repository = repository;
721     }
722
723 }
724
Popular Tags