/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/common/Uri.java,v 1.22 2004/11/24 11:35:26 ozeigermann Exp $
 * $Revision: 1.22 $
 * $Date: 2004/11/24 11:35:26 $
 *
 * ====================================================================
 *
 * 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.common;

import java.io.IOException;
import java.util.Enumeration;

import org.apache.slide.store.Store;

/**
 * This class manages the unique identifier of an object which is
 * manipulated by Slide.
 *
 * @version $Revision: 1.22 $
 */
public class Uri implements Cloneable, java.io.Serializable {
    
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param namespace Namespace associated with this Uri
     * @param uri Uri
     * 
     * @deprecated use signature with SlideToken instead
     */
    public Uri(Namespace namespace, String uri) {
        this(null, namespace, uri);
    }
    
    
    /**
     * Constructor.
     *
     * @param token Slide token
     * @param namespace Namespace associated with this Uri
     * @param uri Uri
     */
    public Uri(SlideToken token, Namespace namespace, String uri) {
        this.token = token;
        this.namespace = namespace;
        this.uri = uri;
        parseUri(uri);
    }
    
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Uri's namespace.
     */
    protected transient Namespace namespace;
    
    
    /**
     * uri MUST be unique.
     */
    protected transient String uri;
    
    
    /**
     * Scopes to which this Uri belongs.
     */
    protected transient ScopeTokenizer scopes;
    
    
    /**
     * FIXME : Is that still used ?
     */
    protected transient Scope scope;
    
    
    /**
     * Associated Store instance.
     */
    protected transient Store store;
    
    
    /**
     * Associated SlideToken.
     */
    protected transient SlideToken token;
    
    
    // ------------------------------------------------------------- Properties
    
    
    /**
     * Uri mutator.
     *
     * @param uri New uri
     */
    public void setUri(String uri) {
        parseUri(uri);
    }
    
    
    /**
     * Method getRoot
     *
     * @param    uri                 an Uri
     *
     * @return   an Uri
     *
     */
    public static Uri getRoot(Uri uri) {
        return new Uri(uri.getToken(), uri.getNamespace(), uri.getScope().toString());
    }
    
    
    /**
     * Scope accessor.
     *
     * @return StringTokenizer
     */
    public Scope getScope() {
        return this.scope;
    }
    
    
    /**
     * Returns the scopes tokenized by the ScopeTokenizer associated
     * with this Uri object.
     *
     * @return Enumeration
     */
    public Enumeration getScopes() {
        return this.scopes.elements();
    }
    
    
    /**
     * Store accessor.
     *
     * @return Store
     */
    public Store getStore() {
        return this.store;
    }
    
    
    /**
     * Token accessor.
     *
     * @return The SlideToken, or null if no token has been set
     * (which is valid)
     */
    public SlideToken getToken() {
        return token;
    }
    
    
    /**
     * Token mutator.
     *
     * @param token New Slide token
     */
    public void setToken(SlideToken token) {
        this.token = token;
    }
    
    
    // --------------------------------------------------------- Public Methods
    
    
    /**
     * Return the namespace to which this Uri belongs.
     *
     * @return Namespace
     */
    public Namespace getNamespace() {
        return namespace;
    }
    
    
    /**
     * Get the parent uri.
     *
     * @return Uri
     */
    public Uri getParentUri() {
        Uri result = scopes.getParentUri();
        if (result != null)
            result.setToken(token);
        return result;
    }
    
    
    /**
     * Invalidate the current Services and PK. Used if there are changes in
     * the namespace at runtime.
     */
    public void invalidateServices() {
        store = null;
        parseUri(this.uri);
    }
    
    
    /**
     * Reconnect the Uri services.
     */
    public void reconnectServices() {
        try {
            if (token == null) {
                store.connectIfNeeded(null);
            } else {
                store.connectIfNeeded(token.getCredentialsToken());
            }
        } catch (ServiceConnectionFailedException e) {
            parseUri(this.uri);
        } catch (ServiceAccessException e) {
            parseUri(this.uri);
        }
    }
    
    
    /**
     * Get the relative path to the matched scope.
     */
    public String getRelative() {
        return (uri.substring(scope.toString().length()));
    }
    
    /**
     * Test whether this Uri is equivalent to its scope
     * @return   a boolean
     */
    public boolean isStoreRoot() {
        UriPath thisPath = new UriPath(uri);
        UriPath scopePath = new UriPath(scope.toString());
        return thisPath.equals(scopePath);
    }
    
    // --------------------------------------------------------- Object Methods
    
    
    /**
     * String representation of the uri object.
     *
     * @return String
     */
    public String toString() {
        return uri;
    }
    
    
    /**
     * Hash code.
     *
     * @return int hash code
     */
    public int hashCode() {
        return this.uri.hashCode();
    }
    
    
    /**
     * Tests equivalence of two Uris.
     *
     * @param obj Object to test
     * @return boolean
     */
    public boolean equals(Object obj) {
        if ((obj != null) && (obj instanceof Uri)) {
            return (uri.equals(obj.toString()));
        } else {
            return false;
        }
    }
    
    
    /**
     * Do a fast clone of the Uri object.
     */
    public Uri cloneObject() {
        Uri result = null;
        try {
            result = (Uri) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return result;
    }
    
    
    /**
     * Tests if the given uri represents a parent object.
     *
     * @param uri Uri to test
     * @return boolean
     */
    public boolean isParent(Uri uri) {
        return this.uri.startsWith(uri.toString());
    }
    
    
    // -------------------------------------------------------- Private Methods
    
    
    /**
     * This function is called by the constructor and when uri is changed.
     *
     * @param uri Uri to parse
     */
    protected void parseUri(String uri) {
        // We first try to tokenize the uri string.
        
        scopes = new ScopeTokenizer(token, namespace, uri);
        
        this.uri = scopes.getUri(); // why that
        
        // Find the qualifiying stuff from the registry.
        // Then we contentStore the scope of the found Data Source
        // within scope.
        store = null;
        while ((store == null) && (scopes.hasMoreElements())) {
            Scope courScope = scopes.nextScope();
            try {
                if (store == null) {
                    if (token == null) {
                        store = namespace.retrieveStore(courScope, null);
                    } else {
                        store = namespace.retrieveStore(courScope, token.getCredentialsToken());
                    }
                    
                    if (store != null) {
                        scope = courScope;
                    }
                }
            } catch (ServiceConnectionFailedException e) {
                throw new SlideRuntimeException(e.toString(), true);
            } catch (ServiceAccessException e) {
                throw new SlideRuntimeException(e.toString(), true);
            }
        }
        
        // If descriptorsStore or contentStore is still null, then no valid
        // scope is defined in the namespace ...
        if (store == null) {
            throw new ServiceMissingOnRootNodeException();
        }
        
    }
    
    
    // ---------------------------------------------------------- Serialization
    
    
    /**
     * Read serialized object.
     */
    private void readObject(java.io.ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        String namespaceName = (String) in.readObject();
        namespace = Domain.getNamespace(namespaceName);
        parseUri((String) in.readObject());
    }
    
    
    /**
     * Write serialized object.
     */
    private void writeObject(java.io.ObjectOutputStream out)
        throws IOException {
        out.writeObject(namespace.getName());
        out.writeObject(uri);
    }
    
}
