RE: CVSRoot

"Ojares Rami EINT" <[email protected]> Mon, 15 Sep 2003 14:36:48 +0300
Newsgroups gmane.comp.java.netbeans.modules.javacvs.devel
Message-ID <[email protected]>
Here is a new version of CVSRoot.
Most of it was changed and there are long
comments so it should be read from top to bottom.
It tries to address the issues talked but by no
means are all the questions answered.

import java.io.*;
import java.util.*;
import java.util.regex.*;

public class CVSRoot {
	
    /*
        Martin Entlicher has found somewhere in cvshome.org the syntax of cvsroot
        [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
        This would allow urls like
        cvs.acme.comC:\@progs\cvs\repository 
        This is supposed to represent hostname and repository. Obviously this is not easy/possible to parse correctly
        Also hostname is in practice optional for local urls that many times have only the repository component.
        So I have broken down this to two different syntax alternatives
    */
    /*
        LOCAL_FORMAT
        [[:method:][[user][:password]@]]/path/to/repository
        There are still complications Eg.
        :local:c:\@progs\cvs
        would be interpreted as
        method = local
        user = c
        password = \
        repository = progs\cvs
        So the recommendation is not to use @ sign in repository (not many do but it is legal and when it happens it can cause a long debugging session for some poor user)
    */
    private static final int LOCAL_FORMAT = 0;
    /*
        SERVER_FORMAT
        :method:[user][:password]@hostname:[port]/path/to/repository
        This is more robust and as far as I know does not have any possibility for error
        
        Note: because method is obligatory it means that when method is missing it is interpreted as LOCAL_FORMAT
    */
    private static final int SERVER_FORMAT = 1;
    
	// cvsroot as string
	String cvsroot;
	// connectionType is always represented in lowercase
	String connectionType;
    // url format (default 0)
    int urlFormat;
	// user (default = null)
	String username;
	// password (default = null)
	String password;
	// host (default = null)
	String host;
	// port (default = 0)
	int port;
	// repository as string representation
	String repository;
	
	/**
		This constructor allows to construct CVSRoot from Properties object.
		The names are exactly the same as the attribute names in this class
	*/
	public CVSRoot(Properties props) throws IllegalArgumentException {
		
		String ct = props.getProperty("connectionType");
        if (ct == null || ct.length() == 0)
            this.connectionType = "local";
		else
            connectionType = ct.toLowerCase();
        
        if (connectionType.equals("local") || connectionType.equals("fork"))
            this.urlFormat = LOCAL_FORMAT;
        else
            this.urlFormat = SERVER_FORMAT;
        
		// user & password (they are always optional)
		this.username = props.getProperty("username");
		this.password = props.getProperty("password");
		
        if (this.urlFormat == SERVER_FORMAT) {
            // host & port
            this.host = props.getProperty("host");
            if (this.host == null || this.host.length() == 0)
                throw new IllegalArgumentException("host is obligatory in non local connections.");
            try {
                int p = Integer.parseInt(props.getProperty("port"));
                if (p > 0)
                    this.port = p;
            }
            catch (Exception e) {
                // never mind
            }
        }
		
		// and the most important which is repository
		String r = props.getProperty("repository");
		if (r == null)
			throw new IllegalArgumentException("Repository is obligatory.");
		else
			this.repository = r;
		
		// construct string representation of cvsroot (ignore user & password in local format)
		if (this.urlFormat == LOCAL_FORMAT) {
			this.cvsroot = ":" + this.connectionType + ":" + repository;
		}
		if (this.urlFormat == SERVER_FORMAT) {
			
            // connection type
			String str = ":" + this.connectionType + ":";
            
            // don't put password in cvsroot
            if (this.username != null)
                str += this.username;
            
            // host
            str += "@" + this.host + ":";
            
            // port
			if (this.port > 0)
				str += "" + this.port;
            
            // repository
			str += this.repository;
            
			this.cvsroot = str;
		}
	}
	
    /**
    Is cvsroot case sensitive or not ??? If not then this constructor needs modification.
    There can be some problems parsing local cvsroot eg.
    :local:c:\@progs\cvs
    This would be parsed currently
    user = c
    password = \
    repository progs\cvs
    */
	public CVSRoot(String cvsroot) throws IllegalArgumentException {
		
        // keep the cvsroot as it is given (no normalization)
		this.cvsroot = cvsroot;
		
        if (cvsroot.startsWith(":")) {
            
            // connection method is given so parse it
            int conEndColon = cvsroot.indexOf(":", 1);
            this.connectionType = cvsroot.substring(1, conEndColon);
            
            // Figure out url syntax
            if (this.connectionType.equals("local") ||
                this.connectionType.equals("fork"))
                this.urlFormat = LOCAL_FORMAT;
            else
                this.urlFormat = SERVER_FORMAT;
            
            if (this.urlFormat = LOCAL_FORMAT) {
                // upEnd = end position of user and password combination
                // here is the possibility of error when repository contains @
                int upEnd = cvsroot.indexOf("@", conEndColon+1);
                if (upEnd != -1) {
                    String up = cvsroot.substring(conEndColon+1, upEnd);
                    int upDivider = up.indexOf(":");
                    if (upDivider != -1) {
                        this.username = up.substring(0, upDivider);
                        this.password = up.substring(upDivider+1);
                    }
                    else {
                        this.username = up;
                    }
                    // everything after @ is repository
                    this.repository = cvsroot.substring(upEnd+1);
                }
                else {
                    // everything after method is repository
                    this.repository = cvsroot.substring(conEndColon+1);
                }
            }
            else {
                // So now we parse SERVER_FORMAT
                // :method:[user][:password]@hostname:[port]/path/to/repository
                
                int at = cvsroot.indexOf("@", conEndColon+1);
                if (at == -1)
                    throw new IllegalArgumentException("@ sign was not found in cvsroot even though connection type was " + this.connectionType);
                
                // up = username + password
                String up = cvsroot.substring(conEndColon+1, at);
                if (up.length() > 0) {
                    int upDivider = up.indexOf(":");
                    if (upDivider != -1) {
                        this.username = up.substring(0, upDivider);
                        this.password = up.substring(upDivider+1);
                    }
                    else {
                        this.username = up;
                    }
                }
                
                // host
                int hostEndColon = cvsroot.indexOf(":", at+1);
                if (hostEndColon == -1)
                    throw new IllegalArgumentException("You must end hostname with : sign");
                this.host = cvsroot.substring(at+1, hostEndColon);
                
                // pr = port + repository
                String pr = cvsroot.substring(hostEndColon+1);
                Matcher matcher = Pattern.compile("\\d+").matcher(pr);
                if (matcher.lookingAt()) {
                    try {
                        this.port = Integer.parseInt(matcher.group());
                    } catch(NumberFormatException e) {}
                    this.repository = pr.substring(matcher.end());
                }
                else {
                    this.repository = pr;
                }
            }            
        }
        else {
            // method was not given so cvsroot == repository
            this.connectionType = "local";
            this.repository = cvsroot;
        }
	}
	
	public String toString() {
		return this.cvsroot;
	}
	
    /*
        All CVSRoots that have the LOCAL_FORMAT are considered the same if they have the same repository.
        All CVSRoots that have the SERVER_FORMAT are considered the same if
        - host is the same
        - repository is the same
        This means that
        :pserver:[email protected]:/data/cvs is the same as
        :ext:[email protected]:/data/cvs
        Because they both point to the same cvs repository
        
        This interpretation implies that the connection method is only a means to get to a repository and equality is only concerned with the actual location of the repository
        
        QUESTION:
        If I use cvsroot :fork:C:\cvs and the Administrative files have Root
        C:\cvs does this create problems for javacvs?
    */
	public boolean equals(Object o) {
		
		CVSRoot compared;
		
		try {
			compared = (CVSRoot) o;
		}
		catch(ClassCastException cce) {
			return false;
		}
		
		// connection comparison
		if (this.urlFormat == compared.urlFormat) {
			if (this.urlFormat == LOCAL_FORMAT) {
				if (
					(new File(this.repository)).getCanonicalFile().equals(
						new File(compared.repository).getCanonicalFile()
					)
				)
					return true;
				else
					return false;
			}
			else {
				// host is compared case insensitively
				if (
					this.host.equalsIgnoreCase(compared.host)
					&&
					this.port == compared.port
					&&
					(new File(this.repository)).getCanonicalFile().equals(
						new File(compared.repository).getCanonicalFile()
					)
				)
					return true;
				else
					return false;
			}
		}
		else
			return false;
	}
    
    public int hashCode() {
        if (this.urlFormat == LOCAL_FORMAT)
            return this.repository.hashCode();
        else
            return this.host.hashCode() + this.repository.hashCode();
    }
	
    // at the moment no setters, because they have effect on cvsroot
    // need to think whether this should be modified in code after creation.
    public String getCvsroot() {
        return cvsroot;
    }
    public int getConnectionType() {
        return connectionType;
    }
    public int getUrlFormat() {
        return urlFormat;
    }
    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
    public String getHost() {
        return host;
    }
    public int getPort() {
        return port;
    }
    public String getRepository() {
        return repository;
    }

}