/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/wvcm/src/org/apache/wvcm/LocationImpl.java,v 1.16 2004/09/16 19:38:26 pnever Exp $
 * $Revision: 1.16 $
 * $Date: 2004/09/16 19:38:26 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.wvcm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import javax.wvcm.Activity;
import javax.wvcm.ControllableResource;
import javax.wvcm.Folder;
import javax.wvcm.Location;
import javax.wvcm.Principal;
import javax.wvcm.Principal.Group;
import javax.wvcm.Principal.Role;
import javax.wvcm.Provider;
import javax.wvcm.Resource;
import javax.wvcm.Workspace;
import javax.wvcm.WvcmException;
import javax.wvcm.WvcmException.ReasonCode;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.wvcm.PrincipalImpl.GroupImpl;
import org.apache.wvcm.PrincipalImpl.RoleImpl;

/**
 * Implementation of Location.
 *
 * @author <a href="mailto:peter.nevermann@softwareag.com">Peter Nevermann</a>
 * @version $Revision: 1.16 $
 */
public class LocationImpl implements Location {
    
    private Path path = null;
    private Path escapedPath = null;
    private ProviderImpl provider = null;
    private boolean isClientSide = false;
    private String hostname = null;
    private int port = 0;
    
    
    /**
     * Default constructor
     * @param string  the location string
     * @param provider the provider
     */
    public LocationImpl(String string, ProviderImpl provider) throws WvcmException {
        this(string, provider, false);
    }
    
    /**
     * Default constructor
     * @param    string              a  String
     * @param    provider            a  ProviderImpl
     * @param    escaped             if true, the string is escaped (URL encoding)
     * @throws   WvcmException
     */
    public LocationImpl(String string, ProviderImpl provider, boolean escaped) throws WvcmException {
        try {
            URI uri = initPaths(string, provider, escaped);
            this.provider = (ProviderImpl)provider;
            String hostname = null;
            int port = 0;
            String scheme = null;
            
            if( uri.isAbsoluteURI() ) {
                hostname = uri.getHost();
                port = uri.getPort();
                scheme = uri.getScheme();
                if( "file".equals(scheme) )
                    isClientSide = true;
            }
            else {
                hostname = (String)provider.initParameter( "host" );
                if( hostname == null || hostname.equals("") )
                    throw new WvcmException(
                        "Cannot determine host", string, ReasonCode.ILLEGAL_LOCATION_SYNTAX, null);
                
                try {
                    port = Integer.parseInt( (String)provider.initParameter("port") );
                }
                catch (NumberFormatException e) {
                    throw new WvcmException(
                        "Cannot determine port", string, ReasonCode.ILLEGAL_LOCATION_SYNTAX, new Exception[]{e});
                }
            }
            
            this.hostname = hostname;
            this.port = port;
        }
        catch ( URIException e ) {
            throw new WvcmException(
                "Illegal location syntax", string, ReasonCode.ILLEGAL_LOCATION_SYNTAX, new Exception[]{e});
        }
    }
    
    private URI initPaths(String string, ProviderImpl provider, boolean escaped) throws WvcmException, NullPointerException, URIException {
        URI uri = null;
        String urlenc = (String)provider.initParameter("urlencoding");
        if (urlenc != null) {
            if (escaped) {
                uri = new URI(string.toCharArray(), urlenc);
            }
            else {
                uri = new URI(string, urlenc);
            }
            uri.normalize();
            this.path = new Path(uri.getPath());
            this.escapedPath = new Path(uri.getEscapedPath());
        }
        else {
            uri = new URI(string);
            uri.normalize();
            this.path = new Path(uri.getPath());
            this.escapedPath = path;
        }
        return uri;
    }
    
    /**
     * Private constructor
     */
    private LocationImpl( String string, LocationImpl o ) {
        try {
            initPaths(string, o.provider, false);
        }
        catch (Exception e) {
            throw new IllegalStateException("Could not initialize paths for "+string);
        }
        this.provider = o.provider;
        this.isClientSide = o.isClientSide;
        this.hostname = o.hostname;
        this.port = o.port;
    }
    
    /**
     * Return a proxy for the folder at this Location.
     */
    public Folder folder() {
        path.isFolder = true;
        return new ControllableFolderImpl( this );
    }
    
    /**
     * Return a proxy for the resource at this Location.
     */
    public Resource resource() {
        return new ResourceImpl( this );
    }
    
    /**
     * Return the string value for this Location.
     */
    public String string() {
        return String.valueOf( path );
    }
    
    /**
     * Return a proxy for the controllable resource at this Location.
     */
    public ControllableResource controllableResource() {
        return new ControllableResourceImpl( this );
    }
    
    /**
     * Return the Location of the child with the specified
     * binding name in the folder at this Location.
     * The string value of the Location of the child
     * is commonly created by appending a "/" and the name
     * of the child to the string value of this Location.
     */
    public Location child(String bindingName) throws WvcmException {
        return new LocationImpl( string()+"/"+bindingName, this );
    }
    
    /**
     * Return a proxy for the workspace at this location.
     */
    public Workspace workspace() {
        return new WorkspaceImpl( this );
    }
    
    /**
     * Return the Location of the parent folder of the resource
     * at this Location.  If this Location
     * is the root of the namespace, <code>null</code> is returned.
     * The string value of the Location of the parent folder
     * is commonly created by stripping off the last segment
     * beginning with a "/" character of this Location.
     */
    public Location parent() {
        Path parent = path.getParent();
        if (parent == null) {
            return null;
        }
        else {
            return new LocationImpl(parent.toString(), this);
        }
    }
    
    /**
     * Return a proxy for the activity at this Location.
     */
    public Activity activity() {
        return new ActivityImpl( this );
    }
    
    /**
     * NOT YET STANDARD
     * Return a proxy for the principal at this location.
     */
    public Principal principal() {
        return new PrincipalImpl( this );
    }
    
    /**
     * NOT YET STANDARD
     * Return a proxy for the role principal at this location.
     */
    public Role role() {
        return new RoleImpl( this );
    }
    
    /**
     * NOT YET STANDARD
     * Return a proxy for the group principal at this location.
     */
    public Group group() {
        return new GroupImpl( this );
    }
    
    /**
     * NOT YET STANDARD
     * Return a proxy for the privilege at this location.
     */
    public javax.wvcm.AccessControlElement.Privilege privilege() {
        return new PrivilegeImpl( this );
    }
    
    /**
     * NOT YET STANDARD
     */
    public String lastSegment() {
        return path.getLastSegment();
    }
    public String escapedLastSegment() {
        return escapedPath.getLastSegment();
    }
    
    // -------------------------------------------------------------------------
    // Non-API methods
    // -------------------------------------------------------------------------
    
    /**
     * Return true if this location is a client-side location
     *
     * @return   true, if this location is a client-side location
     */
    public boolean isClientSide() {
        return isClientSide;
    }
    
    /**
     * Get the host
     *
     * @return   a String
     */
    public String hostname() {
        return hostname;
    }
    
    /**
     * Get the port
     *
     * @return   an int
     */
    public int port() {
        return port;
    }
    
    /**
     * Return a proxy as instance of the specified class for the resource at this Location.
     *
     * @param    cls the class of the result resource
     * @return   a Resource
     *
     */
    public Resource resource( Class cls ) throws WvcmException {
        if( cls == null || !isSubtypeOfResourceImpl(cls) )
            throw new WvcmException( "Cannot create instance for "+cls,
                                    string(), ReasonCode.CANNOT_CREATE_NEW_RESOURCE, null );
        if (isSubtypeOfControllableFolderImpl(cls)) {
            path.isFolder = true;
        }
        try {
            Class[] parmtypes = {Location.class};
            Object[] initargs = {this};
            return (Resource)cls.getConstructor(parmtypes).newInstance(initargs);
        }
        catch( Exception e ) {
            Exception[] nested = {e};
            throw new WvcmException( "Cannot create instance for "+cls,
                                    string(), ReasonCode.CANNOT_CREATE_NEW_RESOURCE, nested );
        }
    }
    
    private boolean isSubtypeOfResourceImpl( Class cls ) {
        if( cls == ResourceImpl.class )
            return true;
        else
            return isSubtypeOfResourceImpl( cls.getSuperclass() );
    }
    
    private boolean isSubtypeOfControllableFolderImpl( Class cls ) {
        if (cls == null) {
            return false;
        }
        if( cls == ControllableFolderImpl.class )
            return true;
        else
            return isSubtypeOfControllableFolderImpl( cls.getSuperclass() );
    }
    
    /**
     * Get the path of this location
     *
     * @return   the path
     *
     */
    public String path() {
        return path.toString();
    }
    
    /**
     * Get the escaped path of this location
     *
     * @return   the path
     *
     */
    public String escapedPath() {
        return escapedPath.toString();
    }
    
    /**
     * Get the provider of this location
     *
     * @return   the provider
     *
     */
    public Provider provider() {
        return provider;
    }
    
    /**
     *
     */
    public boolean equals( Object o ) {
        if( o instanceof LocationImpl ) {
            return path.equals(((LocationImpl)o).path);
        }
        return false;
    }
    
    /**
     *
     */
    public int hashCode() {
        return path.hashCode();
    }
    
    public String toString() {
        return string();
    }
    
    /**
     ** URI path handler.
     **/
    private static class Path {
        
        /** The path tokens */
        String[] tokens = null;
        boolean isFolder = false;
        
        /**
         ** Default constructor.
         **/
        Path( String uri ) throws WvcmException {
            if (uri != null && uri.endsWith("/")) {
                isFolder = true;
            }
            StringTokenizer ut = new StringTokenizer( uri, "/" );
            int ntok = ut.countTokens();
            this.tokens = new String[ntok];
            for( int i = 0; i < ntok; i++ )
                tokens[i] = ut.nextToken();
            checkValid();
        }
        
        /**
         ** Default constructor.
         ** @pre number <= toks.length
         **/
        Path( String[] toks, int number ) throws WvcmException {
            this.tokens = new String[number];
            for( int i = 0; i < number; i++ )
                tokens[i] = toks[i];
            checkValid();
        }
        
        /**
         ** Check whether the path is valid.
         ** @return     true if the URI is valid
         **/
        void checkValid() throws WvcmException {
            if (tokens.length < 1) {
                throw new WvcmException("Path is out of scope",
                                        toString(),
                                        ReasonCode.ILLEGAL_LOCATION_SYNTAX,
                                        null);
            }
        }
        
        /**
         ** Check whether this is the root.
         **/
        boolean isRoot() {
            return (tokens.length == 1);
        }
        
        /**
         * Return the URI hierarhy above.
         * Example: for /a/b/c returns: { /a, /a/b, /a/b/c }
         */
        List getHierarchy() throws WvcmException {
            List result = new ArrayList();
            for( int i = 0; i < tokens.length; i++ )
                result.add( new Path(tokens, i + 1) );
            return result;
        }
        
        /**
         * Checks whether this URI is equal or descendant of the specified URI
         */
        boolean isDescendantOf( Path uh ) throws WvcmException {
            return getHierarchy().indexOf( uh ) >= 0;
        }
        
        /**
         * Return the parent.
         * Example: for /a/b/c returns: /a/b
         */
        Path getParent() {
            
            try {
                if( isRoot() ) {
                    return null;
                }
                else {
                    Path result = new Path(tokens, tokens.length - 1);
                    result.isFolder = true;
                    return result;
                }
            }
            catch (WvcmException e) {
                // should never happen
                return null;
            }
        }
        
        /**
         * Return the last segment.
         * @return   a String
         */
        String getLastSegment() {
            return tokens[ tokens.length - 1 ];
        }
        
        /**
         *
         */
        public boolean equals( Object o ) {
            if( o instanceof Path ) {
                Path ouh = (Path)o;
                return Arrays.equals( tokens, ouh.tokens );
            }
            return false;
        }
        
        /**
         *
         */
        public int hashCode() {
            return tokens.length;
        }
        
        /**
         * Return string representation.
         */
        public String toString() {
            StringBuffer b = new StringBuffer();
            if( tokens.length == 0 )
                b.append( "/" );
            for( int i = 0; i < tokens.length; i++ )
                b.append( "/" ).append( tokens[i] );
            if (isFolder && !b.toString().endsWith("/")) {
                b.append("/");
            }
            return b.toString();
        }
    }
}

