/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/UriHandler.java,v 1.39 2005/02/28 17:27:09 luetzkendorf Exp $
 * $Revision: 1.39 $
 * $Date: 2005/02/28 17:27:09 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.webdav.util;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.content.Content;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.event.VetoException;
import org.apache.slide.lock.ObjectLockedException;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.Structure;
import org.apache.slide.structure.SubjectNode;


/**
 * Helper class for the handling of URIs
 */
public class UriHandler implements DeltavConstants, AclConstants, DaslConstants, Serializable {

    /**
     * Determines whether the hack that restricts the size of collections in
     * the history collection is configured to be used.
     */
    protected static boolean useHistoryCollectionHack = false;
    
    /**
     * Sets whether the hack that restricts the size of collections in
     * the history collection is configured to be used.
     */
    public static void setGloballyUseHistoryCollectionHack(boolean useHistoryCollectionHack) {
        // XXX it is truely ugly to do things this way, it would be better to have an Uri factory
        // that gets initialized with this value
        UriHandler.useHistoryCollectionHack = useHistoryCollectionHack;
    }
    
    private static Cache instanceCache;
    static {
        try {
            CacheManager manager = CacheManager.create();
            instanceCache = new Cache(
                    "org.apache.slide.webdav.util.UriHandler",
                    256,
                    false,
                    false,
                    0,
                    0);
            manager.addCache(instanceCache);
        } catch (CacheException e) {
            Domain.error("Can't create UrlHandler instance cache.", e);
        }
    }
    
    /**
     * Factory method.
     */
    public static UriHandler getUriHandler( String resourcePath ) {
        Element element = null; 
        
        try {
            element = instanceCache.get(resourcePath);
        } catch (CacheException e) {
            Domain.error("Error while reading from UriHandler instance cache.", e);
        }
        if (element != null) {
            return (UriHandler)element.getValue();
        } else {
            UriHandler handler = new UriHandler( resourcePath );
            instanceCache.put(new Element(resourcePath, handler));
            return handler;
        }
    }

    /**
     * Factory method.
     */
    public static UriHandler getUriHandler( NodeRevisionDescriptors nrds, 
            NodeRevisionDescriptor nrd ) 
    {
        String uri = nrds.getUri();
        UriHandler uriHandler = UriHandler.getUriHandler( uri );
        if ( ! uriHandler.isHistoryUri() ) {
            // any resource
            return uriHandler;
        } else {
            if( (NodeRevisionNumber.HIDDEN_0_0).equals(nrd.getRevisionNumber()) ) {
                // history resource
                return uriHandler;
            } else {
                StringBuffer b = new StringBuffer();

                // version resource
                b.append( uri );
                if( !uri.endsWith("/") ) {
                    b.append( "/" );
                }
                b.append( nrd.getRevisionNumber().toString() );
                return UriHandler.getUriHandler( b.toString() );
            }
        }
        
    }

    /**
     * Generates the next available history URI and returns an URI handler for it.
     * @param sToken the Slide token
     * @param nsaToken the namespace access token
     * @param uh the URI handler of the resource being set under version control
     */
    public static UriHandler
    createNextHistoryUri( SlideToken sToken, NamespaceAccessToken nsaToken, UriHandler uh )
    throws ObjectNotFoundException, AccessDeniedException, ObjectLockedException,
    LinkedObjectNotFoundException, ServiceAccessException,
    RevisionDescriptorNotFoundException, RevisionNotFoundException, VetoException {
        
        UriHandler result = null;
        String nsName = nsaToken.getName();
        UriHandler hpathHandler =
            HistoryPathHandler.getResolvedHistoryPathHandler( nsName, uh );
        Content content = nsaToken.getContentHelper();
        String hpath = hpathHandler.toString();
        
        Structure structure = nsaToken.getStructureHelper();
        String uniqueUri = structure.generateUniqueUri(sToken, hpath);

        if (uniqueUri != null) {
            if (UriHandler.useHistoryCollectionHack) {
                // XXX this is a bit silly: first compose it, now analyze it... 
                String nextHnStr = uniqueUri.substring(uniqueUri.lastIndexOf('/') + 1);
                // XXX start with (at least) 10 to
                // assure no resources are created directly in history folder
                long nextHnLong = Long.parseLong(nextHnStr) + 10;
                nextHnStr = String.valueOf(nextHnLong);

                // make hpath/1/2/3 out of 1234
                StringBuffer pathBuffer = new StringBuffer(hpath);
                for (int i = 0; i < nextHnStr.length() - 1; i++) {
                    pathBuffer.append('/');
                    pathBuffer.append(nextHnStr.charAt(i));
                    // as we have no information how this number may look like we will have to assure the full 
                    // path is created
                    String dir = pathBuffer.toString();
                    try {
                        structure.retrieve(sToken, dir);
                    } catch (ObjectNotFoundException onfe) {
                        try {
                            structure.create(sToken, new SubjectNode(), dir);
                            content.create(sToken, dir, new NodeRevisionDescriptor(0), null);
                        } catch (ObjectAlreadyExistsException oae) {
                            Domain.warn("Object " + dir + " already exists.");
                        } catch (RevisionAlreadyExistException rae) {
                            Domain.warn("Revision " + dir + " already exists.");
                        }
                    }
                }

                // make hpath/1/2/3/h4 out of 1234
                pathBuffer.append("/h").append(nextHnStr.charAt(nextHnStr.length() - 1));
                String fullPath = pathBuffer.toString();
                
                return UriHandler.getUriHandler(fullPath);
                
            } else {
                return UriHandler.getUriHandler(uniqueUri);
            }
        } else {

            NodeRevisionDescriptors hpathNrds = content.retrieve(sToken, hpath);
            NodeRevisionDescriptor hpathNrd = content.retrieve(sToken, hpathNrds);
            NodeProperty nextHnProp = hpathNrd.getProperty(PN_NEXT_HISTORY_NAME);

            if (UriHandler.useHistoryCollectionHack) {

                if (nextHnProp == null || nextHnProp.getValue() == null) {
                    // XXX start with historyCollectionHackChildren to assure no
                    // resources are created directly in history folder
                    nextHnProp = new NodeProperty(PN_NEXT_HISTORY_NAME, "10", 
                            true); // protected
                    hpathNrd.setProperty(nextHnProp);
                }

                String nextHnStr = (String) nextHnProp.getValue();
                long nextHnLong = Long.parseLong(nextHnStr);

                if (nextHnLong % 10 == 0) {
                    // create parent collection
                    long dirNum = nextHnLong / 10;
                    char dirChar[] = Long.toString(dirNum).toCharArray();
                    StringBuffer buf = new StringBuffer();
                    for (int i = 0; i < dirChar.length - 1; i++) {
                        buf.append(dirChar[i]);
                        buf.append('/');
                    }
                    buf.append(dirChar[dirChar.length - 1]);
                    String dirPath = hpath + "/" + buf.toString();

                    try {
                        structure.create(sToken, new SubjectNode(), dirPath);
                        //content.create(sToken,dirPath,true);
                        //NodeRevisionDescriptors dnrds =
                        // structure.retrieve(stoken,dirPath);
                        content.create(sToken, dirPath, new NodeRevisionDescriptor(0), null);
                    } catch (ObjectAlreadyExistsException oae) {
                        Domain.warn("Object " + dirPath + " already exists.");
                    } catch (RevisionAlreadyExistException rae) {
                        Domain.warn("Revision " + dirPath + " already exists.");
                    }
                }

                StringBuffer buf = new StringBuffer();
                char nextHnChar[] = nextHnStr.toCharArray();
                for (int i = 0; i < nextHnChar.length - 1; i++) {
                    buf.append(nextHnChar[i]);
                    buf.append('/');
                }
                buf.append('h');
                buf.append(nextHnChar[nextHnChar.length - 1]);

                result = UriHandler.getUriHandler(hpath + "/" + buf.toString());
                
                nextHnProp = new NodeProperty(PN_NEXT_HISTORY_NAME, 
                        String.valueOf(nextHnLong + 1));
                hpathNrd.setProperty(nextHnProp);

            } else {

                if (nextHnProp == null) {
                    // convert to slide namespace if this property is still
                    // in DAV: namespace
                    nextHnProp = hpathNrd.getProperty(PN_NEXT_HISTORY_NAME_COMP);
                    if (nextHnProp != null) {
                        hpathNrd.removeProperty(nextHnProp);
                        nextHnProp = new NodeProperty(PN_NEXT_HISTORY_NAME, 
                                nextHnProp.getValue(),
                                true); // protected
                        hpathNrd.setProperty(nextHnProp);
                    }
                }
                if (nextHnProp == null || nextHnProp.getValue() == null) {
                    nextHnProp = new NodeProperty(PN_NEXT_HISTORY_NAME, 
                            I_INITIAL_HISTORY_NAME, 
                            true); // protected
                    hpathNrd.setProperty(nextHnProp);
                }

                String nextHnStr = (String) nextHnProp.getValue();
                result = UriHandler.getUriHandler(hpath + "/" + nextHnStr);

                long nextHnLong = Long.parseLong(nextHnStr);
                nextHnProp = new NodeProperty(PN_NEXT_HISTORY_NAME, 
                        String.valueOf(nextHnLong + 1));
                hpathNrd.setProperty(nextHnProp);

            }
            content.store(sToken, hpath, hpathNrd, null); //revisionContent = null
            return result;
        }
    }

    
    /**
     * Generates the next available workingresource URI and returns an URI handler for it.
     * @param sToken the Slide token
     * @param nsaToken the namespace access token
     * @param uh the URI handler of the version resource being checked-out
     */
    public static UriHandler
    createNextWorkingresourceUri( SlideToken sToken, NamespaceAccessToken nsaToken, UriHandler uh )
    throws ObjectNotFoundException, AccessDeniedException, ObjectLockedException,
    LinkedObjectNotFoundException, ServiceAccessException,
    RevisionDescriptorNotFoundException, RevisionNotFoundException, VetoException {
        
        UriHandler result = null;
        String nsName = nsaToken.getName();
        UriHandler wrpathHandler =
            WorkingresourcePathHandler.getResolvedWorkingresourcePathHandler( nsName, uh );
        Content content = nsaToken.getContentHelper();
        String wrpath = wrpathHandler.toString();
        
        Structure structure = nsaToken.getStructureHelper();
        String uniqueUri = structure.generateUniqueUri(sToken, wrpath);

        if (uniqueUri != null) {
            result = UriHandler.getUriHandler(uniqueUri);
        } else {
            NodeRevisionDescriptors wrpathNrds =
                content.retrieve( sToken, wrpath );
            
            NodeRevisionDescriptor wrpathNrd =
                content.retrieve( sToken, wrpathNrds );
            
            NodeProperty nextWrnProp = wrpathNrd.getProperty(PN_NEXT_WORKINGRESOURCE_NAME);
            
            if (nextWrnProp == null) {
                // convert to slide namespace if this property is still
                // in DAV: namespace
                nextWrnProp = wrpathNrd.getProperty(PN_NEXT_WORKINGRESOURCE_NAME_COMP);
                if (nextWrnProp != null) {
                    wrpathNrd.removeProperty(nextWrnProp);
                    nextWrnProp = new NodeProperty(PN_NEXT_WORKINGRESOURCE_NAME,
                                                   nextWrnProp.getValue(),
                                                   true); // protected
                    wrpathNrd.setProperty( nextWrnProp );
                }
            }
            
            if( nextWrnProp == null || nextWrnProp.getValue() == null ) {
                nextWrnProp =
                    new NodeProperty(PN_NEXT_WORKINGRESOURCE_NAME,
                                     I_INITIAL_WORKINGRESOURCE_NAME,
                                     true); // protected
                wrpathNrd.setProperty( nextWrnProp );
            }
            
            String nextWrnStr = (String)nextWrnProp.getValue();
            result = UriHandler.getUriHandler( wrpath+"/"+nextWrnStr );
            
            long nextWrnLong = Long.parseLong( nextWrnStr );
            nextWrnProp = new NodeProperty(PN_NEXT_WORKINGRESOURCE_NAME,
                                           String.valueOf(nextWrnLong + 1));
            wrpathNrd.setProperty( nextWrnProp );
            
            content.store( sToken, wrpath, wrpathNrd, null ); //revisionContent = null
        }                      
        return result;
    }
    
    /**
     * Creates a VR URI for the specified version in the specified history and
     * returns an URI handler for it.
     */
    public static UriHandler
        createVersionUri( UriHandler vhrUri, String version ) {
        return new UriHandler( vhrUri, version );
    }
    
    /**
     * Registerd stores: namespaceName -> (scope -> store)
     */
    protected static Map registeredStores = new HashMap();
    protected static Set allScopes = new HashSet();
    protected static Set allStoreNames = new HashSet();
    
    /**
     *  Called when a Namespace registers a Store
     */
    public static void notifyStoreCreated( String namespaceName, String scope, String storeName ) {
        Map storesInNamespace = (Map)registeredStores.get( namespaceName );
        if( storesInNamespace == null ) {
            storesInNamespace = new HashMap();
            registeredStores.put( namespaceName, storesInNamespace );
        }
        UriHandler scopeUh = UriHandler.getUriHandler(scope);
        storesInNamespace.put( scopeUh, storeName );
        allScopes.add( scopeUh );
        allStoreNames.add( storeName );
    }
    
    /**
     *  Return the best matching scope for the specified URI in the specified namespace
     */
    protected static UriHandler bestMatchingScope( String namespaceName, UriHandler uh ) {
        UriHandler result = null;
        Map storesInNamespace = (Map)registeredStores.get( namespaceName );
        Iterator i = storesInNamespace.keySet().iterator();
        int matchSize = 0;
        while( i.hasNext() ) {
            UriHandler scopeUh = (UriHandler)i.next();
            if( scopeUh.isAncestorOf(uh) && scopeUh.length() > matchSize ) {
                matchSize = scopeUh.length();
                result = scopeUh;
            }
        }
        return result;
    }
    
    private String uri;
    private String[] uriTokens = null;
    private UriHandler parentUriHandler = null;
    
    
    /**
     * Protected constructor
     */
    protected UriHandler( String uri ) {
        this.uri = uri;
        
        StringTokenizer t = new StringTokenizer( uri, "/" );
        int n = t.countTokens();
        this.uriTokens = new String[n];
        for( int i = 0; i < n; i++ )
            uriTokens[i] = t.nextToken();
    }
    
    /**
     * Protected constructor
     */
    protected UriHandler( String[] uriTokens, int number ) {
        String[] t = new String[number];
        for( int i = 0; i < number; i++ )
            t[i] = uriTokens[i];
        this.uriTokens = t;
    }
    
    /**
     * Protected constructor for VR URIs
     */
    protected UriHandler( UriHandler vhrUri, String versionName ) {
        int n = vhrUri.length();
        this.uriTokens = new String[n + 1];
        for( int i = 0; i < n; i++ )
            uriTokens[i] = vhrUri.uriTokens[i];
        uriTokens[n] = versionName;
    }
    
    /**
     * Get the URI as string
     */
    public String getUri() {
        if( uri == null ) uri = toString();
        return uri;
    }
    
    /**
     * Get the name (last token)
     */
    public String getName() {
        if( uriTokens.length == 0 )
            return null;
        return uriTokens[uriTokens.length - 1];
    }
    
    /**
     * Get the UriHandler for the parent URI, null if this URI is root.
     */
    public UriHandler getParentUriHandler() {
        if( uriTokens.length == 0 )
            return null;
        if (this.parentUriHandler == null) {
            this.parentUriHandler = new UriHandler( uriTokens, uriTokens.length - 1 );
        }
        return this.parentUriHandler;
    }
    
    /**
     *  Return true, if this handler's tokens are the initial tokens of the specified handler.
     *  Example: /a/b is ancestor of /a/b/x/y/z
     */
    public boolean isAncestorOf( UriHandler uh ) {
        if( uriTokens.length > uh.length() )
            return false;
        String[] otherTokens = uh.getUriTokens();
        for( int i = 0; i < uriTokens.length; i++ ) {
            if( !uriTokens[i].equals(otherTokens[i]) ) return false;
        }
        return true;
    }
    
    /**
     * Get the tokens
     */
    public String[] getUriTokens() {
        return uriTokens;
    }
    
    /**
     * Return true, if this URI determines the root.
     */
    public boolean isRootUri() {
        return (uriTokens.length == 0);
    }
    
    /**
     * Return true, if this URI determines the history path.
     */
    public boolean isHistoryPathUri() {
        HistoryPathHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();
        return hpathHandler.isHistoryPathUri( this );
    }
    
    /**
     * Return true, if this is a history URI
     */
    public boolean isHistoryUri() {
        HistoryPathHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();

        if (UriHandler.useHistoryCollectionHack) {
            if (hpathHandler.isAncestorOf(this)) {
                String[] hpathTokens = hpathHandler.getUriTokens();
                if (uriTokens.length < hpathTokens.length + 1) {
                    return false;
                }
                String h = uriTokens[uriTokens.length - 1];
                return (h.charAt(0) == 'h');
            } else {
                return false;
            }
        } else {
            String[] hpathTokens = hpathHandler.getUriTokens();

            if ((hpathTokens.length + 1) == uriTokens.length) {
                if (hpathHandler.isHistoryPathUri(getParentUriHandler())) {
                    try {
                        Integer.parseInt(uriTokens[uriTokens.length - 1]);
                        return true;
                    } catch (NumberFormatException x) {
                    }
                }
            }
            return false;
        }
    }
    
    /**
     * Return true, if this is a version URI
     */
    public boolean isVersionUri() {
        HistoryPathHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();

        if (UriHandler.useHistoryCollectionHack) {
            /*
             * Version Uris look like /my/history/path/1/2/3/h9/1.0
             */

            if (hpathHandler.isAncestorOf(this)) {
                String[] hpathTokens = hpathHandler.getUriTokens();
                if (uriTokens.length < hpathTokens.length + 2) {
                    return false;
                }
                String h = uriTokens[uriTokens.length - 2];
                return (h.charAt(0) == 'h');
            } else {
                return false;
            }
        } else {

            String[] hpathTokens = hpathHandler.getUriTokens();

            if ((hpathTokens.length + 2) == uriTokens.length) {
                if (getParentUriHandler().isHistoryUri())
                    return true;
            }
            return false;
        }
    }
    
    /**
     * Returns <code>true</code> if this is a collection below the history path,
     * but not a history. This is relevant only with the HistoryCollectionHack
     * where we have collections like this: /my/history/path/1/2, that are not 
     * histories of versions.
     */
    public boolean isHistoryCollectionUri() {
        HistoryPathHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();

        if (UriHandler.useHistoryCollectionHack) {
            return hpathHandler.isAncestorOf(this) && !isHistoryUri()
                    && !isVersionUri();
        } else {
            return false;
        }
    }

    /**
     * Returns <code>true</code> if this is any uri below the history path.
     */
    public boolean isHistoryRelatedUri() {
        HistoryPathHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();
        return hpathHandler.isAncestorOf(this);
    }
    
    /**
     * Return true, if this URI determines the workspace path.
     */
    public boolean isWorkspacePathUri() {
        WorkspacePathHandler wspathHandler = WorkspacePathHandler.getWorkspacePathHandler();
        return wspathHandler.isWorkspacePathUri( this );
    }
        
    /**
     * Return true, if this is a valid workspace URI
     */
    public boolean isWorkspaceUri() {
        WorkspacePathHandler wspathHandler = WorkspacePathHandler.getWorkspacePathHandler();
        String[] wspathTokens = wspathHandler.getUriTokens();
        
        if( (wspathTokens.length + 1) == uriTokens.length ) {
            if( wspathHandler.isWorkspacePathUri(getParentUriHandler()) ) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Return true, if this is an URI of a resource in a workspace
     */
    public boolean isResourceInWorkspaceUri() {
        WorkspacePathHandler wspathHandler = WorkspacePathHandler.getWorkspacePathHandler();
        String[] wspathTokens = wspathHandler.getUriTokens();
        
        if( (wspathTokens.length + 1) < uriTokens.length ) {
            UriHandler p = new UriHandler( uriTokens, wspathTokens.length + 1 );
            if( p.isWorkspaceUri() ) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Return true, if this URI determines the workingresource path.
     */
    public boolean isWorkingresourcePathUri() {
        WorkingresourcePathHandler wrpathHandler = WorkingresourcePathHandler.getWorkingresourcePathHandler();
        return wrpathHandler.isWorkingresourcePathUri( this );
    }
    
    /**
     * Return true, if this is a valid workingresource URI
     */
    public boolean isWorkingresourceUri() {
        WorkingresourcePathHandler wrpathHandler = WorkingresourcePathHandler.getWorkingresourcePathHandler();
        String[] wrpathTokens = wrpathHandler.getUriTokens();
        
        if( (wrpathTokens.length + 1) == uriTokens.length ) {
            if( wrpathHandler.isWorkingresourcePathUri(getParentUriHandler()) ) {
                try {
                    Integer.parseInt( uriTokens[uriTokens.length - 1] );
            return true;
        }
                catch( NumberFormatException x ) {};
    }
        }
        return false;    }
    
    /**
     * Returns the workspace identifier from the uri
     * @pre isWorkspaceUri()
     */
    public String getWorkspaceName() {
        UriHandler wspathHandler = WorkspacePathHandler.getWorkspacePathHandler();
        String[] wspathTokens = wspathHandler.getUriTokens();
        
        if( !isWorkspaceUri() )
            throw new IllegalStateException(
                "URI "+uri+" is not a workspace URI" );
        
        return uriTokens[wspathTokens.length];
    }
    
    /**
     * Returns the history identifier from the uri
     * @pre isHistoryUri() || isVersionUri()
     */
    public String getHistoryName() {
        UriHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();
        String[] hpathTokens = hpathHandler.getUriTokens();

        if (UriHandler.useHistoryCollectionHack) {
            if (isHistoryUri()) {
                return getName();
            } else if (isVersionUri()) {
                return uriTokens[uriTokens.length - 2];
            } else {
                throw new IllegalStateException("URI " + toString() + " is neither history nor version URI");
            }
        } else {
            if (!isHistoryUri() && !isVersionUri())
                throw new IllegalStateException("URI " + uri + " is neither history nor version URI");

            return uriTokens[hpathTokens.length];
        }
    }
    
    /**
     * Returns the version name from the uri
     * 
     * @pre isVersionUri()
     */
    public String getVersionName() {
        if (!isVersionUri())
            throw new IllegalStateException("URI " + uri + " is not a version URI");

        if (UriHandler.useHistoryCollectionHack) {
            return getName();
        } else {
            UriHandler hpathHandler = HistoryPathHandler.getHistoryPathHandler();
            String[] hpathTokens = hpathHandler.getUriTokens();

            return uriTokens[hpathTokens.length + 1];
        }
    }
    
    /**
     * Returns the associated history URI of this version URI
     * 
     * @pre isVersionUri()
     */
    public String getAssociatedHistoryUri() {
        if( !isVersionUri() )
            throw new IllegalStateException(
                "URI "+uri+" is not a version URI" );
        
        StringBuffer b = new StringBuffer();
        for( int i = 0; i < (uriTokens.length - 1); i++ ) {
            b.append( "/" );
            b.append( uriTokens[i] );
        }
        return b.toString();
    }

    /**
     * Returns the associated workspace URI if this URI represents a resource
     * in a workspace; if the resource is a workspace itself, its own URI is returned.
     * @return the associated workspace URI; null, if the resource is neither a workspace itself
     *         nor contained in a workspace
     */
    public String getAssociatedWorkspaceUri() {
        if( !isWorkspaceUri() && !isResourceInWorkspaceUri() )
            return null;
        
        UriHandler wspathHandler = WorkspacePathHandler.getWorkspacePathHandler();
        String[] wspathTokens = wspathHandler.getUriTokens();
        StringBuffer b = new StringBuffer();
        for( int i = 0; i < (wspathTokens.length + 1); i++ ) {
            b.append( "/" );
            b.append( uriTokens[i] );
        }
        return b.toString();
    }
    
    /**
     * Returns the associated <b>base</b> store name.
     * Examples (assume historyPath is parameterized: /history/${store}):
     *   uri=/history/mycoll/1       --->  result=mycoll  // history URI
     *   uri=/history/files/1/1.17   --->  result=files   // version URI
     *   uri=/files/foo/bar.xml      --->  result=files   // best matching scope
     * If the URI is e.g. a version URI but historyPath is not parameterized, no associated
     * base store can be determined.
     *
     * @return the associated base store name.
     */
    public String getAssociatedBaseStoreName( String namespaceName ) {
        String result = "";
        
        if( isHistoryPathUri() || isHistoryUri() || isVersionUri() ) {
            if( HistoryPathHandler.parameterized ) {
                result = extractStoreName( HistoryPathHandler.historyPathHandler );
            }
            else {
                Domain.info( "Cannot determine base store name for URI "+this );
            }
        }
        else if( isWorkspacePathUri() || isWorkspaceUri() || isResourceInWorkspaceUri() ) {
            if( WorkspacePathHandler.parameterized ) {
                result = extractStoreName( WorkspacePathHandler.workspacePathHandler );
            }
            else {
                Domain.info( "Cannot determine base store name for URI "+this );
            }
        }
        else if( isWorkingresourcePathUri() || isWorkingresourceUri() ) {
            if( WorkingresourcePathHandler.parameterized ) {
                result = extractStoreName( WorkingresourcePathHandler.workingresourcePathHandler );
            }
            else {
                Domain.info( "Cannot determine base store name for URI "+this );
            }
        }
        else {
            UriHandler scopeUh = bestMatchingScope( namespaceName, this );
            Map storesInNamespace = (Map)registeredStores.get( namespaceName );
            result = (String)storesInNamespace.get( scopeUh );
        }
        return result;
    }
    
    /**
     * Extract the store name from a parameterized path.
     */
    private String extractStoreName( UriHandler pUh ) {
        String result = "";
        String[] pTokens = pUh.getUriTokens();
        int ptIndex = -1;
        for( int i = 0; i < pTokens.length; i++ ) {
            if( pTokens[i].indexOf(I_STORE_PLACE_HOLDER_IN_PATH) >= 0 ) {
                ptIndex = i;
                break;
            }
        }
        if( ptIndex >= 0 ) {
            result = extractStoreName( pTokens[ptIndex], uriTokens[ptIndex] );
        }
        return result;
    }
    
    /**
     * Extract the store name from a parameterized path.
     */
    private String extractStoreName( String prmStr, String srcStr ) {
        String tail = null;
        int start = prmStr.indexOf( I_STORE_PLACE_HOLDER_IN_PATH );
        int endP = start + I_STORE_PLACE_HOLDER_IN_PATH.length();
        int endS = -1;
        
        if( endP < prmStr.length() ) {
            tail = prmStr.substring( endP );
            endS = srcStr.lastIndexOf( tail );
        }
        else
            endS = srcStr.length();
        
        return srcStr.substring( start, endS );
    }
    
    /**
     * Returns <code>true</code> if the usage of this URI
     * is restricted by the server, means the server may refuse to
     * create resource at this URI due to a client request like <code>PUT</code>.
     * (e.g. an URI that determines a resource in a history folder).
     *
     * @return     <code>true</code> if the usage of the URI is restricted.
     */
    public boolean isRestrictedUri() {
        if( isRootUri() )
            return true;
        if( isVersionUri() )
            return true;
        if( isHistoryUri() )
            return true;
        if( isHistoryPathUri() )
            return true;
        if( useHistoryCollectionHack && isHistoryCollectionUri() ) 
            return true;
        if( isWorkspaceUri() )
            return true;
        if( isWorkspacePathUri() )
            return true;
        if( isWorkingresourceUri() )
            return true;
        if( isWorkingresourcePathUri() )
            return true;
        
        UriHandler p = getParentUriHandler();
        if( p != null ) {
            if( p.isRootUri() )
                return true;
            if( p.isHistoryPathUri() )
                return true;
            if( p.isWorkingresourcePathUri() )
                return true;
        }
        return false;
    }

    /**
     *  Overwrites Object.equals()
     */
    public boolean equals( Object o ) {
        if( !(o instanceof UriHandler) )
            return false;
        String[] oTokens = ((UriHandler)o).getUriTokens();
        if( oTokens.length != uriTokens.length )
            return false;
        for( int i = 0; i < uriTokens.length; i++ )
            if( !oTokens[i].equals(uriTokens[i]) )
                return false;
        return true;
    }

    /**
     *  Overwrites Object.hashCode()
     */
    public int hashCode() {
        return uriTokens.length;
    }

    /**
     *  Get the length of this handler.
     */
    public int length() {
        return uriTokens.length;
    }
    
    /**
     * Return string representation of the object
     */
    public String toString() {
        StringBuffer b = new StringBuffer();
        if( uriTokens.length == 0 )
            b.append("/");
        else
            for( int i = 0; i < uriTokens.length; i++ )
            b.append("/").append( uriTokens[i] );
        return b.toString();
    }
}

