RE: CVSRoot

"Ojares Rami EINT" <[email protected]> Mon, 15 Sep 2003 14:39:51 +0300
Newsgroups gmane.comp.java.netbeans.modules.javacvs.devel
Message-ID <[email protected]>
Here is CVSRoot again but this time it compiles

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) {
            try {
                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;
                }
            }
            catch (IOException ioe) {
                // something went wrong when invoking getCanonicalFile() so return false
                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 String 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;
    }

}