/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XFileStore.java,v 1.3 2004/07/30 06:52:03 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:52:03 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 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.store.tamino.store;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
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.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.store.tamino.tools.Env;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XUri;



/**
 ** Configuration store. Manages the domain configuration (Domain.xml).
 **
 ** @author    peter.nevermann@softwareag.com
 ** @version   0.1
 **
 **/
public class XFileStore extends XMemoryStore implements XGlobals {

    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);

    // constants
    private static final String ROOTPATH = "rootpath";
    /** root path **/
    protected String rootpath = null;

    private String currentThreadName = "";


    /**
     ** Default constructor.
     **/
    public XFileStore() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }

    /**
     * Returns the path relative to te rootpath.
     */
    private String getRelative( String path ) {
        String result = null;
        if( path.length() <= rootpath.length() ) {
            result = XUri.SEP;
        }
        else {
            result = path.substring( rootpath.length() );
            if( !result.startsWith(XUri.SEP) )
                result = XUri.SEP+result;
        }
        return result;
    }

    /**
     * Initializes this child store with a set of parameters.
     * These are:
     * <li>not yet defined...
     *
     * @param parameters Hashtable containing the parameters' name
     * and associated value
     *
     * @exception   ServiceParameterErrorException
     * @exception   ServiceParameterMissingException
     */
    public void setParameters(Hashtable parameters)
    throws ServiceParameterErrorException, ServiceParameterMissingException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setParameters", new Object[] {parameters} );

        super.setParameters( parameters );
        String rp = (String)parameters.get( ROOTPATH );
        if( rp == null ) {
            rootpath = "";
        }
        else {
            UriHandler uh = new UriHandler( rp );
            rootpath = uh.getPath();
        }

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "setParameters" );
    }


    /**
     * Commit the global transaction specified by xid.
     * @param xid  transaction id
     * @param onePhase true if it can do in one phase
     * @exception XAException transaction error
     */
    public void commit( Xid xid, boolean onePhase ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "commit",
            new Object[] {xid, new Boolean(onePhase)}  );

        super.commit( xid, onePhase );
        clearCaches();
        currentThreadName = "";

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "commit" );
    }

    /**
     * Inform the resource manager to roll back work done on behalf of a
     * transaction branch.
     * @param xid transaction id
     * @exception XAException  transaction error
     */
    public void rollback( Xid xid ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "rollback",
            new Object[] {xid}  );

        super.rollback( xid );
        clearCaches();
        currentThreadName = "";

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "rollback" );
    }

    /**
     * Clear the caches
     */
    protected void clearCaches() {
        objects.clear();
        descriptors.clear();
        descriptor.clear();
        content.clear();
        permissions.clear();
        locks.clear();
    }

    // ------------------------------------------------------------------------
    // ContentStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve revision content.
     *
     * @param uri Uri
     * @param revisionDescriptor  revision descriptor
     * @return node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionNotFoundException Revision not found
     */
    public NodeRevisionContent retrieveRevisionContent(Uri uri, NodeRevisionDescriptor revisionDescriptor)
    throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "retrieveRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
               ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
               : null) } );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        NodeRevisionContent result = new NodeRevisionContent();

        try {
            result.setContent( new FileInputStream(rcf) );
        }
        catch( FileNotFoundException x ) {
            throw new RevisionNotFoundException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        }

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "retrieveRevisionContent", result );
        return result;
    }

    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionAlreadyExistException Revision already exists
     */
    public void createRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor,
    NodeRevisionContent revisionContent )
    throws ServiceAccessException, RevisionAlreadyExistException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                 : null), revisionContent} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( rcf.exists() ) {
            throw new RevisionAlreadyExistException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        }

        try {
            BufferedInputStream in = new BufferedInputStream( revisionContent.streamContent() );
            BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(rcf) );

            int next = in.read();
            while( next != -1 ) {
                out.write( next );
                next = in.read();
            }

            in.close();
            out.flush();
            out.close();
        }
        catch( IOException x ) {
            x.printStackTrace();
            throw new ServiceAccessException(this, x);
        }

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }

    /**
     * Modify the latest revision of an object.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception RevisionNotFoundException Revision not found
     */
    public void storeRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor,
    NodeRevisionContent revisionContent)
    throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "storeRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                 : null), revisionContent} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() ) {
            throw new RevisionNotFoundException(
                uriStr, revisionDescriptor.getRevisionNumber() );
        }

        try {
            BufferedInputStream in = new BufferedInputStream( revisionContent.streamContent() );
            BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(rcf) );

            int next = in.read();
            while( next != -1 ) {
                out.write( next );
                next = in.read();
            }

            in.close();
            out.flush();
            out.close();
        }
        catch( IOException x ) {
            x.printStackTrace();
            throw new ServiceAccessException(this, x);
        }

        if (logger.isLoggable(Level.FINE))
            logger.exiting( CLASSNAME, "storeRevisionContent" );
    }

    /**
     * Remove revision.
     *
     * @param uri Uri
     * @param revisionDescriptor revision descriptor
     * @exception ServiceAccessException Error accessing the Descriptors Store
     */
    public void removeRevisionContent( Uri uri, NodeRevisionDescriptor revisionDescriptor )
    throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeRevisionContent", new Object[] {uri,
            (revisionDescriptor!=null
                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                 : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );
        boolean deleted = rcf.delete();
//        int n = 0;
//
//        while( !deleted && n < 20 ) {
//            try {
//                Thread.currentThread().sleep(100);
//              rcf = new File( rootpath+uriRel );
//                deleted = rcf.delete();
//                n++;
//            }
//            catch( InterruptedException x ) {};
//        }

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionContent", new Boolean(deleted) );
    }

    // ------------------------------------------------------------------------
    // NodeStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to retrieve
     * @return objectnode
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject(Uri uri)
    throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "retrieveObject",
            new Object[] {uri} );

        if( !currentThreadName.equals(Thread.currentThread().getName()) ) {
            currentThreadName = Thread.currentThread().getName();
            clearCaches();
        }

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        UriHandler uh = new UriHandler( uriRel );
        File rcf = new File( rootpath+uriRel );
        Object snode = objects.get(uriStr);

        if( !rcf.exists() ) {
            if( uh.isRoot() ) {
                rcf.mkdir();
            }
            else if( snode == null ) {
                throw new ObjectNotFoundException(uri);
            }
        }

        if (snode == null) {
            Vector children = new Vector();
            Vector links = new Vector();
            String[] rcfList = rcf.list();
            if( rcfList != null ) {
                for( int i = 0; i < rcfList.length; i++ ) {
                    children.add( i, uriStr+XUri.SEP+rcfList[i] );
                }
            }
            snode = new SubjectNode( uriStr, children, links );
            objects.put( uriStr, snode );
        }
        ObjectNode result = ((ObjectNode)snode).cloneObject();

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "retrieveObject",
            (result!=null ? result.getUri() : null) );
        return result;
    }

    /**
     * Store an object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to update
     * @param object Object to update
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to update was not found
     */
    public void storeObject(Uri uri, ObjectNode object)
    throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "storeObject",
            new Object[] {uri, (object!=null ? object.getUri() : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() && !objects.containsKey(uriStr) )
            throw new ObjectNotFoundException(uri);

        objects.put( uriStr, object.cloneObject() );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "storeObject" );
    }

    /**
     * Create a new object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to create
     * @param object SlideObject
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectAlreadyExistsException An object already exists
     * at this Uri
     */
    public void createObject(Uri uri, ObjectNode object)
    throws ServiceAccessException, ObjectAlreadyExistsException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "createObject",
            new Object[] {uri, (object!=null ? object.getUri() : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( rcf.exists() )
            throw new ObjectAlreadyExistsException(uriStr);

        objects.put( uriStr, object.cloneObject() );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "createObject" );
    }

    /**
     * Remove an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to remove
     * @param object Object to remove **IGNORED**
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to remove was not found
     */
    public void removeObject(Uri uri, ObjectNode object)
    throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "removeObject",
            new Object[] {uri, (object!=null ? object.getUri() : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        Object snode = objects.get(uriStr);

        if( snode == null )
            throw new ObjectNotFoundException(uri);

        objects.remove(uriStr);

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "removeObject" );
    }

    // ------------------------------------------------------------------------
    // RevisionDescriptorsStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve a revision descriptors.
     *
     * @param uri Uri
     * @return node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public NodeRevisionDescriptors retrieveRevisionDescriptors(Uri uri)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "retrieveRevisionDescriptors", new Object[] {uri} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() )
            throw new RevisionDescriptorNotFoundException(uriStr);

        NodeRevisionDescriptors result = XFactory.createNRDs( uriStr );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "retrieveRevisionDescriptors", (result!=null ? result.getUri() : null) );
        return result;
    }

    /**
     * Create new revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptors
    (Uri uri, NodeRevisionDescriptors revisionDescriptors)
    throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "createRevisionDescriptors", new Object[] {uri,
            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "createRevisionDescriptors" );
    }

    /**
     * Update revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptors
    (Uri uri, NodeRevisionDescriptors revisionDescriptors)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "storeRevisionDescriptors", new Object[] {uri,
            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() )
            throw new RevisionDescriptorNotFoundException(uriStr);

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "storeRevisionDescriptors" );
    }

    /**
     * Remove revision descriptors.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptors(Uri uri) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "removeRevisionDescriptors", new Object[] {uri} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( rcf.exists() ) {
            // Assuming resourcetype=<collection/> ... otherwise removeRevisionContent had
            // been called before and rcf would not exist
            rcf.delete();
        }

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "removeRevisionDescriptors" );
    }

    // ------------------------------------------------------------------------
    // RevisionDescriptorStore interface
    // ------------------------------------------------------------------------

    /**
     * Retrieve revision descriptor.
     *
     * @param uri Uri
     * @param revisionNumber Node revision number
     * @exception ServiceAccessException Service access error
     * @return node revision descriptor
     * @exception RevisionDescriptorNotFoundException Revision descriptor not found
     */
    public NodeRevisionDescriptor retrieveRevisionDescriptor
    (Uri uri, NodeRevisionNumber revisionNumber)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "retrieveRevisionDescriptor", new Object[] {uri, revisionNumber} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() )
            throw new RevisionDescriptorNotFoundException(uriStr);

        NodeRevisionDescriptor result = null;
        if( rcf.isDirectory() ) {
            result = XFactory.createCollectionNRD( uriStr );
        }
        else {
            result = XFactory.createMemberNRD( uriStr );
        }

        long lastModified = rcf.lastModified();
        if( lastModified > 0 ) {
            result.setLastModified( new Date(lastModified) );
            result.setCreationDate( new Date(lastModified) );
        }

        result.setContentLength( rcf.length() );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "retrieveRevisionDescriptor", (result!=null
            ? result.getRevisionNumber()+"@"+result.getBranchName()
            : null) );
        return result;
    }

    /**
     * Create new revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptor
    (Uri uri, NodeRevisionDescriptor revisionDescriptor) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "createRevisionDescriptor", new Object[] {uri, (revisionDescriptor!=null
            ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
            : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() && isCollection(revisionDescriptor) ) {
            rcf.mkdir();
        }

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "createRevisionDescriptor" );
    }

    /**
     * Update revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptor
    (Uri uri, NodeRevisionDescriptor revisionDescriptor)
    throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "storeRevisionDescriptor", new Object[] {uri, (revisionDescriptor!=null
            ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
            : null)} );

        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        File rcf = new File( rootpath+uriRel );

        if( !rcf.exists() )
            throw new RevisionDescriptorNotFoundException(uriStr);

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "storeRevisionDescriptor" );
    }

    /**
     * Remove revision descriptor.
     *
     * @param uri Uri
     * @param number Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(Uri uri, NodeRevisionNumber number)
    throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
            "removeRevisionDescriptor", new Object[] {uri, number} );

        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
            "removeRevisionDescriptor" );
    }

    /**
     ** Uri handler.
     **
     ** @author    peter.nevermann@softwareag.com
     ** @version   0.1
     **/
    static class UriHandler {

        private final String PH_START = "${";
        private final String PH_END = "}";

        /** tokens **/
        String[] tokens = null;
        /** resolved **/
        boolean resolved = false;

        /**
         ** Default constructor.
         **
         ** @pre        true
         ** @post       true
         **
         ** @param      uri the uri
         **/
        UriHandler( String uri ) {
            StringTokenizer ut = new StringTokenizer( uri, XUri.SEP );
            int ntok = ut.countTokens();
            this.tokens = new String[ntok];

            for( int i = 0; i < ntok; i++ )
                tokens[i] = ut.nextToken();
        }

        /**
         ** Check whether the uriRel is corresponds to the root folder
         **
         ** @return     true if the uriRel corresponds to the root folder
         **/
        boolean isRoot() {
            return (tokens.length == 0);
        }

        /**
         ** Return a file system path for the resolved uri
         **
         ** @return     the path
         **/
        String getPath() {
            StringBuffer b = new StringBuffer();
            resolve();
            for( int i = 0; i < tokens.length; i++ ) {
                if( i > 0 )
                    b.append(File.separator);
                b.append(tokens[i]);
            }
            return b.toString();
        }

        /**
         ** Resolve ${property} placeholders
         **
         **/
        private void resolve() {
            if( !resolved ) {
                for( int i = 0; i < tokens.length; i++ ) {
                    if( tokens[i].startsWith(PH_START) && tokens[i].endsWith(PH_END) ) {
                        String p = tokens[i].substring(2, tokens[i].length() - 1);
                        tokens[i] = Env.get().getProperty(p);
                    }
                }
                resolved = true;
            }
        }
    }
}

