/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/wvcm/src/org/apache/wvcm/sample/BasicCmdClient.java,v 1.9 2004/09/16 19:38:26 pnever Exp $
 * $Revision: 1.9 $
 * $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.sample;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.wvcm.ControllableResource;
import javax.wvcm.Folder;
import javax.wvcm.ControllableFolder;
import javax.wvcm.Location;
import javax.wvcm.PropertyNameList;
import javax.wvcm.PropertyNameList.PropertyName;
import javax.wvcm.Provider;
import javax.wvcm.ProviderFactory;
import javax.wvcm.ProviderFactory.Callback;
import javax.wvcm.Resource;
import javax.wvcm.Version;
import javax.wvcm.VersionHistory;
import javax.wvcm.WvcmException;
import org.apache.wvcm.sample.util.ReportFormat;
import org.apache.wvcm.sample.util.WvcmProperties;
import org.apache.wvcm.sample.util.WvcmTypes;

/**
 * Sample WVCM Application: Basic Command-line Client.
 *
 * @author <a href="mailto:peter.nevermann@softwareag.com">Peter Nevermann</a>
 * @author <a href="mailto:michael.gesmann@softwareag.com">Michael Gesmann</a>
 * @version $Revision: 1.9 $
 */
public class BasicCmdClient {
    
    private static String PATH_SEPARATOR = "/";
    private static String _VALUE_UNAVAILABLE = ".";
    
    private static Properties javaprops = new Properties();
    static {
        try {
            javaprops.load( BasicCmdClient.class.getResourceAsStream("/org/apache/wvcm/sample/sample.properties") );
        } catch (java.io.IOException e) {}
    }
    
    private String command = null;
    private Provider provider = null;
    private FolderNode rootNode = createRootNode();
    private ResourceNode currentNode = rootNode;
    private String host;
    private String port;
    private String context;
    private String realm;
    
    private SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy HH:mm");
    private ReportFormat dirRf = new ReportFormat( new int[]{20,20,17}, 1 );
    private String[] dirRHeaders = {"Binding Name", "Type", "Last Modified"};
    private ReportFormat dirRfNoProperties = new ReportFormat( new int[]{30}, 1);
    private String[] dirRHeadersNoProperties = {"BindingName"};
    private ReportFormat propsRf = new ReportFormat( new int[]{30,30}, 1 );
    private String[] propsRHeaders = {"Property Name", "Value"};
    
    /**
     *
     */
    public static void main(String[] args) {
        new BasicCmdClient().run( args );
    }
    
    private FolderNode createRootNode() {
        return new FolderNode(null, null, null);
    }
    
    // create representative for file or folder in ResourceNode tree
    private ResourceNode createResourceNode(String uri) throws Exception {
        Path p = new Path(uri);
        if (p.isRoot()) {
            return rootNode;
        }
        
        ResourceNode n = rootNode;
        for ( int i = 0; i < p.tokens.length; i++) {
            if (n.getChild(p.tokens[i]) != null) {
                // has been read earlier
                n = n.getChild(p.tokens[i]);
            }
            else {
                Resource r = null;
                // not yet read, do it now
                try {
                    Location folderLocation = provider.location( new Path(p.tokens,i+1, p.isRoot()).toString() );
                    Folder folder = folderLocation.folder();
                    r = folder.doReadProperties(null); // determine type of folder;
                } catch (WvcmException e){
                    // this might not be a folder
                    try {
                        Location fileLocation = provider.location( new Path(p.tokens,i+1, p.isRoot()).toString() );
                        Resource file = fileLocation.resource();
                        r = file.doReadProperties(null); // determine type of file;
                    } catch (WvcmException ex){
                        return null;
                    }
                }
                n.addChild(p.tokens[i], WvcmTypes.type(r));
                n = n.getChild(p.tokens[i]);
            }
        }
        return n;
    }
    
    // create a representative for file or folder in ResourceNode tree
    private ResourceNode createResourceNode( ResourceNode parent, String seg, Class type ) {
        if (type == Resource.class) {
            return new ResourceNode(parent, seg, type);
        }
        else if (type == ControllableResource.class) {
            return new ControllableResourceNode(parent, seg, type);
        }
        else if (type == ControllableFolder.class) {
            return new FolderNode(parent, seg, type);
        }
        else if (type == Version.class) {
            return new VersionNode(parent, seg, type);
        }
        else if (type == VersionHistory.class) {
            return new VersionHistoryNode(parent, seg, type);
        }
        return null;
    }
    
    private boolean alreadyConnected(){
        if (provider != null){
            return true;
        } else {
            System.out.println("not yet connected");
            return false;
        }
    }
    
    /**
     * This method is called after start up.
     */
    private void run(String[] args) {
        // Print application prompt to console.
        System.out.println("Basic Command-line Client");
        
        while (true) {
            String prompt = currentNode.toString();
            
            // get next command to be executed
            command = readInput(prompt, ">", null);
            if (command == null || command.length() == 0) {
                continue;
            }
            
            try {
                if (command.startsWith("help")) {
                    help();
                }
                else if (command.startsWith("show")) {
                    show();
                }
                else if (command.startsWith("con")) {
                    // connect to server
                    connect();
                }
                else if (command.startsWith("prop")) {
                    // list properties of current resource (folder or file)
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens()){
                                // return properties of specified path
                                properties(new Path(t.nextToken()));
                            } else {
                                // return properties of current node
                                properties();
                            }
                        }
                    }
                }
                else if (command.startsWith("vc")) {
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                vc(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("cd")) {
                    // move to a folder resource
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                cd(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("lls")) {
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens()){
                                // return properties of specified path
                                ls(new Path(t.nextToken()), true);
                            } else {
                                // return properties of current node
                                ls(true);
                            }
                        }
                    }
                }
                else if (command.startsWith("ls")) {
                    // list members of current folder without properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens()){
                                // return properties of specified path
                                ls(new Path(t.nextToken()), false);
                            } else {
                                // return properties of current node
                                ls(false);
                            }
                        }
                    }
                }
                else if (command.startsWith("read")) {
                    // move to a file resource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                readFile(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("save")) {
                    // move to a file resource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            saveFile(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("checkout")) {
                    // move to a file resource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                checkout(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("uncheckout")) {
                    // move to a file resource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                uncheckout(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("checkin")) {
                    // move to a file dresource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            checkin(new Path(t.nextToken()));
                        }
                    }
                }
                else if (command.startsWith("mkdir")) {
                    // list members of current folder without properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                mkdir(t.nextToken());
                        }
                    }
                }
                else if (command.startsWith("rmdir")) {
                    // list members of current folder without properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens()){
                                // return properties of specified path
                                rmdir(new Path(t.nextToken()));
                            } else {
                                // return properties of current node
                                rmdir();
                            }
                        }
                    }
                }
                else if (command.startsWith("rm")) {
                    // move to a file resource
                    // list members of current folder with their properties
                    if (alreadyConnected()){
                        if (command != null){
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens())
                                rm(new Path(t.nextToken()));
                        }
                    }
                }
                    
                else if (command.startsWith("move")) {
                    // list members of current folder without properties
                    if (alreadyConnected()){
                        if (command != null){
                            Path from = null;
                            Path to = null;
                            StringTokenizer t = new StringTokenizer(command, " ");
                            t.nextToken();
                            if (t.hasMoreTokens()){
                                from = new Path(t.nextToken());
                            }
                            if (t.hasMoreTokens()){
                                to = new Path(t.nextToken());
                            }
                            move(from, to);
                        }
                    }
                }
                else if (command.startsWith("test")) {
                    // list members of current folder without properties
                    if (alreadyConnected()){
                        testIt();
                    }
                }
                else if (command.startsWith("exit")) {
                    // leave application
                    System.out.println("Bye!");
                    break;
                }
                else {
                    System.out.println("Unknown comand: "+command);
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    private void help() {
        System.out.println("available calls:");
        System.out.println("- help                 - This information");
        System.out.println("- show                 - Show environment");
        System.out.println("- con[nect]            - Connect to server; parameters will be asked for");
        System.out.println("- prop [path]          - Show properties; without path for current folder");
        System.out.println("- read [path]          - read a file into current working directory");
        System.out.println("- save [path]          - save a file from current working directory");
        System.out.println("- vc [path]            - set file under version control");
        System.out.println("- checkout [path]      - checkout a file");
        System.out.println("- checkin [path]       - checkin a file");
        System.out.println("- uncheckout [path]    - uncheckout a file, i.e. redo checkout without checkin");
        System.out.println("- rm [path]            - remove a file");
        System.out.println("- cd [path]            - change directory");
        System.out.println("- ls [path]            - Show members; without path for current folder");
        System.out.println("- lls [path]           - Show members with their properties; without path for current folder");
        System.out.println("- mkdir [folderName]   - create a new folder");
        System.out.println("- rmdir [path]         - remove folder; without path for current folder and move to parent");
        System.out.println("- move frompath topath - move file/folder from \"from\" to \"to\"");
        System.out.println("- exit                 - exit this application");
        System.out.println();
        System.out.println("Parameters not specified here will be ignored.");
//        System.out.println("This sample cannot handle the situation where a file and");
//        System.out.println("      a folder have the same name in the same parent.");
        System.out.println("\"working directory\" is a folder in your file system where you have started this program.");
    }
    
    private void show() {
        System.out.println("- Host:     "+host);
        System.out.println("- Port:     "+port);
        System.out.println("- Context:  "+context);
        System.out.println("- Realm:    "+realm);
    }
    
    private void testIt() throws Exception {
        // prepare
        
        Location loc = provider.location("/slide/files");
        ControllableFolder rootFolder = (ControllableFolder)loc.folder();
        ControllableFolder f = (ControllableFolder)rootFolder.location().child("foo").folder();
        f.doUnbind();
        f.doCreateResource();
        System.out.println("\nCreated folder 'f': "+f);
        ControllableFolder f2 = (ControllableFolder)rootFolder.location().child("to_move").folder();
        f2.doUnbind();
        f2.doCreateResource();
        System.out.println("Created folder 'f2': "+f2);
        ControllableResource r = rootFolder.location().child("to_move.txt").controllableResource();
        r.doUnbind();
        r.doCreateResource();
        r.doWriteContent(new ByteArrayInputStream("Test Content\n".getBytes("utf-8")), null);
        System.out.println("Created file 'r': "+r);
        // move r into f2
        Location f3 = provider.location("/slide/files");
        r.doRebind(f2.location().child("to_move.txt"), false);
        System.out.println("Moved file 'r' to 'f2': "+r);
        f2.doRebind(f.location().child("to_move"), false);
        System.out.println("Moved folder 'f2' to 'f': "+f2);
    }
    
    
    private void connect() throws Exception {
        // Read user input from console.
        host = readInput("Host", ": ", host != null ? host : (String)javaprops.get("host"));
        port = readInput("Port", ": ", port != null ? port : (String)javaprops.get("port"));
        context = readInput("Context", ": ", context != null ? context : (String)javaprops.get("context"));
        realm = readInput("Realm", ": ", realm != null ? realm : (String)javaprops.get("realm"));
        
        // create the provider
        Callback callback = new CallbackImpl();
        Hashtable h = new Hashtable();
        if( host != null ) h.put( "host", host );
        if( port != null ) h.put( "port", port );
        if( realm != null ) h.put( "realm", realm );
        if( context != null ) h.put( "context", context );
        provider = ProviderFactory.createProvider(
            "org.apache.wvcm.ProviderImpl", callback, h );
        currentNode = rootNode;
        currentNode.resetChildren();
        currentNode.resetReferences();
        currentNode.addChild(context, ControllableFolder.class);
        currentNode = (FolderNode)currentNode.getChild(context);
    }
    
    private void properties() throws Exception {
        System.out.println( propsRf.format(propsRHeaders) );
        System.out.println( propsRf.separator('-') );
        currentNode.showProperties();
    }
    
    private void properties(Path path) throws Exception {
        ResourceNode saveIt = currentNode;
        if (currentNode instanceof FolderNode){
            if (walkThroughPath(path)){
                properties();
            }
            currentNode = saveIt;
        } else {
            System.out.println("current node is not a folder");
        }
    }
    
    private void ls(boolean withProperties) throws Exception {
        if (!(currentNode instanceof FolderNode)) {
            System.out.println(currentNode.toString()+" is not a folder");
        } else
                ((FolderNode)currentNode).showMembers(withProperties);
    }
    
    private void ls(Path path, boolean withProperties) throws Exception {
        ResourceNode saveIt = currentNode;
        if (cd(path)){
            ((FolderNode)currentNode).showMembers(withProperties);
        }
        currentNode = saveIt;
    }
    
    private void vc(Path path) throws Exception {
        
        if (makeFileCurrent(path)){
            if (!(currentNode instanceof ControllableResourceNode)) {
                System.out.println(currentNode.toString()+" is not controllable");
            }
            ((ControllableResourceNode)currentNode).versionControl();
            currentNode = currentNode.getParent();
        }
        
    }
    
    private boolean walkThroughPath (Path path) throws Exception {
        
        if (path == null || path.isRoot()) {
            return true;
        }
        
        // instantiates all possible ResourceNodes for path
        createResourceNode(currentNode.toString()+"/"+path);
        
        if( path.startsAtRoot() )
            currentNode = currentNode.getRoot();
        
        String [] pathTokens = path.tokens;
        for (int i=0; i<path.tokens.length; i++){
            String seg = pathTokens[i];
            if ("..".equals(seg)) {
                if (currentNode.getParent() != null){
                    currentNode = currentNode.getParent();
                }
            }
            else if (".".equals(seg)) {
                //ignore
            }
            else {
                ResourceNode cand = currentNode.getChild(seg);
                
                if (cand == null) {
                    cand = currentNode.getRef(seg);
                }
                if (cand == null) {
                    return false;
                } else {
                    currentNode = cand;
                }
            }
        }
        return (true);
    }
    
    private boolean cd (Path path) throws Exception {
        
        ResourceNode saveIt = currentNode;
        if (walkThroughPath(path)){
            if (!(currentNode instanceof FolderNode)){
                System.out.println("path does not point to a folder");
                currentNode = saveIt;
                return false;
            } else {
                return true;
            }
        } else {
            System.out.println("not a valid path");
            currentNode = saveIt;
            return false;
        }
    }
    
    private boolean makeFileCurrent(Path path) throws Exception {
        
        ResourceNode saveIt = currentNode;
        if (walkThroughPath(path)){
            if ((currentNode instanceof FolderNode)){
                System.out.println("path does not point to a file");
                currentNode = saveIt;
                return false;
            } else {
                return true;
            }
        } else {
//            System.out.println("file does not exist");
            currentNode = saveIt;
            return false;
        }
    }
    
    
    private void saveFile(Path path) throws Exception {
        
        boolean created = false;
        if (!makeFileCurrent(path)){
            ((FolderNode)currentNode).createFile(path.toString());
            created = true;
            makeFileCurrent(path);
        }
        if (!currentNode.saveContent(path.toString()) && created){
            currentNode = currentNode.getParent();
            rm(path);
        }
        currentNode = currentNode.getParent();
    }
    
    private void readFile(Path path) throws Exception {
        
        if (makeFileCurrent(path)){
            currentNode.readContent();
            currentNode = currentNode.getParent();
        }
    }
    
    
    private void checkin(Path path) throws Exception {
        
        if (!makeFileCurrent(path)){
            ((FolderNode)currentNode).createFile(path.toString());
            makeFileCurrent(path);
        }
        currentNode.checkin();
        currentNode = currentNode.getParent();
    }
    
    private void checkout(Path path) throws Exception {
        
        if (makeFileCurrent(path)){
            currentNode.checkout();
            currentNode = currentNode.getParent();
        }
    }
    
    
    private void uncheckout(Path path) throws Exception {
        
        if (makeFileCurrent(path)){
            currentNode.checkout();
            currentNode = currentNode.getParent();
        }
    }
    
    
    private void rm(Path path) throws Exception {
        
        ResourceNode saveIt = currentNode;
        if (walkThroughPath(path)){
            if (currentNode instanceof FolderNode){
                System.out.println("path does not point to a file");
                currentNode = saveIt;
            } else {
                String fileName = path.getLastSegment();
                if (fileName.length() > 0){
                    currentNode = currentNode.getParent();
                    ((FolderNode)currentNode).deleteFile(fileName);
                }
            }
        } else {
            System.out.println("not a valid path");
        }
        currentNode = saveIt;
    }
    
    private void mkdir(String newFolderName) throws Exception {
        if (!(currentNode instanceof FolderNode)) {
            System.out.println(currentNode.toString()+" is not a folder");
        } else {
            Resource r = ((FolderNode)currentNode).createFolder(newFolderName);
            r.doReadProperties(null);
            currentNode.addChild(newFolderName, WvcmTypes.type(r));
        }
    }
    
    private void rmdir() throws Exception {
        
        if (!(currentNode instanceof FolderNode)) {
            System.out.println(currentNode.toString()+" is not a folder");
        } else
                ((FolderNode)currentNode).deleteFolder();
        currentNode = currentNode.getParent();
        currentNode.resetChildren();
        currentNode.resetReferences();
    }
    
    private void rmdir(Path path) throws Exception {
        
        ResourceNode saveIt = currentNode;
        if (path != null){
            if (cd(path)){
                rmdir();
            }
        }
        currentNode = saveIt;
    }
    
    private void move(Path fromPath, Path toPath) throws Exception {
        
        ResourceNode saveIt = currentNode;
        ResourceNode rFrom = null;
        ResourceNode rTo = null;
        
        if (fromPath == null)
            return;
        if (toPath == null)
            return;
        
        if (walkThroughPath(fromPath)){
            rFrom = currentNode;
        }
        currentNode = saveIt;
        if (walkThroughPath(new Path(toPath.getPathFront()))){
            rTo = currentNode;
        }
        if ((rFrom != null) && (rTo != null)){
            rFrom.move(rTo, toPath.getLastSegment());
        } else {
            System.out.println("one of the paths does not exist");
        }
        
        currentNode = saveIt;
        
    }
    
    
    /**
     * This method reads user input from console.
     */
    private String readInput(String key, String delim, String defaultValue) {
        try {
            if( defaultValue != null && defaultValue.length() > 0)
                System.out.print(key+" ["+defaultValue+"]"+delim);
            else
                System.out.print(key+delim);
            
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int c = System.in.read();
            while (c >= 0) {
                if (c == '\n') {
                    break;
                }
                out.write( c );
                c = System.in.read();
            }
            String result = out.toString().trim();
            if (result == null || result.length() == 0 && defaultValue != null) {
                result = defaultValue;
            }
            return result;
        }
        catch (java.io.IOException e) {
            // An error occured.
            System.err.println("Error while reading input.");
            return "";
        }
    }
    
    /**
     * Callback implementation
     */
    private class CallbackImpl implements Callback {
        
        Authentication authentication = new AuthenticationImpl();
        
        /**
         * Return authentication information for the current user.
         * @param realm The authentication realm for the provider.
         */
        public Authentication getAuthentication(String realm, Integer retryCount) {
            //            System.out.println("@@@ getAuthentication("+realm+","+retryCount+")");
            return authentication;
        }
        
        private class AuthenticationImpl implements Authentication {
            private String username = null;
            private String password = null;
            
            /** Return the password of the current user. */
            public String password() {
                if( password == null )
                    password = readInput("Password", ": ", password).trim();
                return password;
            }
            
            /** Return the login name of the current user. */
            public String loginName() {
                if( username == null )
                    username = readInput("User", ": ", username).trim();
                return username;
            }
        }
    }
    
    /**
     ** URI path handler.
     **/
    private static class Path {
        
        private static String PATH_SEPARATOR = "/";
        
        /** The path tokens */
        public String[] tokens = null;
        private boolean startsAtRoot;
        
        
        Path( String uri ) {
            startsAtRoot = uri.startsWith(PATH_SEPARATOR);
            StringTokenizer ut = new StringTokenizer( uri, PATH_SEPARATOR );
            int ntok = ut.countTokens();
            this.tokens = new String[ntok];
            for( int i = 0; i < ntok; i++ )
                tokens[i] = ut.nextToken();
        }
        
        Path( Path parent, String seg ) {
            startsAtRoot = parent.isRoot() && seg.startsWith(PATH_SEPARATOR);
            String[] toks = parent.tokens;
            this.tokens = new String[toks.length+1];
            for( int i = 0; i < toks.length; i++ )
                tokens[i] = toks[i];
            tokens[toks.length] = seg;
        }
        
        
        Path( String[] toks, int number, boolean isRoot ) {
            startsAtRoot = isRoot;
            this.tokens = new String[number];
            for( int i = 0; i < number; i++ )
                tokens[i] = toks[i];
        }
        
        
        /**
         ** Check whether the path is valid.
         ** @return     true if the URI is valid
         **/
        boolean isValid() {
            return true;
        }
        
        /**
         ** Check whether this is the root.
         **/
        boolean isRoot() {
            return (tokens.length == 0);
        }
        
        
        boolean startsAtRoot() {
            return startsAtRoot;
        }
        
        /**
         * Return the parent.
         * Example: for /a/b/c returns: /a/b
         */
        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();
            b.append((startsAtRoot)?"/":"").append((tokens.length==0)?"":tokens[0]);
            
            for( int i = 1; i < tokens.length; i++ )
                b.append( "/" ).append( tokens[i] );
            return b.toString();
        }
        
        /**
         * get Path except of last element
         */
        public String getPathFront(){
            StringBuffer b = new StringBuffer();
            
            b.append((startsAtRoot)?"/":"").append((tokens.length<=1)?"":tokens[0]);
            
            for( int i = 1; i < tokens.length - 1 ; i++ )
                b.append( "/" ).append( tokens[i] );
            return b.toString();
        }
        
    }
    
    private class ResourceNode {
        
        /** The path tokens */
        Path path = null;
        ResourceNode parent;
        Map children;
        Map references;
        Class type = null;
        
        /**
         ** Creates a child of the specified parent.
         **/
        ResourceNode( ResourceNode parent, String seg, Class type ) {
            if (parent == null) {
                this.path = new Path("/");
            }
            else {
                this.path = new Path(parent.path, seg);
                this.parent = parent;
                this.type = type;
            }
        }
        
        
        /**
         * Return the parent.
         * Example: for /a/b/c returns: /a/b
         */
        ResourceNode getParent() {
            return parent;
        }
        
        FolderNode getRoot() {
            ResourceNode result = this;
            while (result.getParent() != null) {
                result = result.getParent();
            }
            return (FolderNode)result;
        }
        
        ResourceNode getChild(String seg) {
            if (children == null) {
                return null;
            }
            return (ResourceNode)children.get(seg);
        }
        
        ResourceNode getRef(String name) {
            if (references == null) {
                return null;
            }
            return (ResourceNode)references.get(name);
        }
        
        void resetChildren() {
            children = null;
        }
        
        void resetReferences() {
            references = null;
        }
        
        void addChild(String seg, Class type) {
            ResourceNode child = createResourceNode(this, seg, type);
            if( children == null )
                children = new HashMap();
            children.put(seg, child);
        }
        
        void addRef(String name, String uri) throws Exception {
            ResourceNode ref = createResourceNode(uri);
            if( references == null )
                references = new HashMap();
            references.put(name, ref);
        }
        
        void readContent() throws Exception {
            // only called for files, but not for folders
            Location location = provider.location( toString());
            if (this instanceof FolderNode){
                System.out.println("read not allowed, use ls or lls");
            } else {
                Resource resource = location.resource();
                java.io.OutputStream o = new java.io.FileOutputStream(location.lastSegment());
                resource.doReadContent(null, o);
            }
        }
        
        boolean saveContent(String fileName) throws Exception {
            Location location = provider.location( toString());
            if (this instanceof FolderNode){
                System.out.println("read not allowed, use ls or lls");
            } else {
                Resource resource = location.resource();
                try {
                    java.io.InputStream i = new java.io.FileInputStream(fileName);
                    resource.doWriteContent(i, null);
                } catch (Exception e){
                    System.out.println("saving content failed with: " + e.toString());
                    return false;
                }
            }
            return true;
        }
        
        void checkin() throws Exception {
            // only called for files, but not for folders
            Location location = provider.location( toString());
            if (this instanceof FolderNode){
                System.out.println("checkin not allowed on folder");
            } else {
                try {
                    ControllableResource resource = location.controllableResource();
                    resource.doCheckin();
                } catch (WvcmException e){
                    System.out.println(e.toString());
                }
            }
        }
        
        void checkout() throws Exception {
            Location location = provider.location( toString());
            if (this instanceof FolderNode){
                System.out.println("checkout not allowed on folder");
            } else {
                try {
                    ControllableResource resource = location.controllableResource();
                    resource.doCheckout(false, null, false, false);
                } catch (WvcmException e){
                    System.out.println(e.toString());
                }
            }
        }
        
        
        void uncheckout() throws Exception {
            Location location = provider.location( toString());
            if (this instanceof FolderNode){
                System.out.println("uncheckout not allowed on folder");
            } else {
                try {
                    ControllableResource resource = location.controllableResource();
                    resource.doUncheckout();
                } catch (WvcmException e){
                    System.out.println(e.toString());
                }
            }
        }
        
        Resource readProperties() throws Exception {
            Location location = provider.location( toString() );
            Resource resource = null;
            if (type == ControllableFolder.class || type == Folder.class) {
                resource = location.folder();
            }
            else {
                resource = location.resource();
            }
            resource = resource.doReadProperties(null); // to determine the type
            resource = resource.doReadProperties( WvcmProperties.allProperties(WvcmTypes.type(resource), true) );
            return resource;
        }
        
        
        void move(ResourceNode to, String name) throws Exception {
            
            if (!(to instanceof FolderNode)){
                System.out.println("target of move operation is not a folder");
            } else {
                Location lFrom = provider.location( toString() );
                Location lTo = provider.location( to.toString() + PATH_SEPARATOR + name );
                // this is a file, otherwise move() from FolderNode would apply.
                ControllableResource rFrom = lFrom.controllableResource();
                
                try {
                    rFrom.doRebind(lTo, false);
                } catch (WvcmException e){
                    System.out.println(e.toString());
                }
            }
        }
        
        Resource showProperties() throws Exception {
            resetReferences();
            Resource resource = readProperties();
            
            String comment                = _VALUE_UNAVAILABLE;
            String contentCharacterSet    = _VALUE_UNAVAILABLE;
            String contentIdentifier      = _VALUE_UNAVAILABLE;
            String contentLanguage        = _VALUE_UNAVAILABLE;
            String contentLength          = _VALUE_UNAVAILABLE;
            String contentType            = _VALUE_UNAVAILABLE;
            String creationDate           = _VALUE_UNAVAILABLE;
            String creatorDisplayName     = _VALUE_UNAVAILABLE;
            String displayName            = _VALUE_UNAVAILABLE;
            String lastModified           = _VALUE_UNAVAILABLE;
            
            try {comment = resource.getComment();} catch (Exception e) {}
            try {contentCharacterSet = resource.getContentCharacterSet();} catch (Exception e) {}
            try {contentIdentifier = resource.getContentIdentifier();} catch (Exception e) {}
            try {contentLanguage = resource.getContentLanguage().toString();} catch (Exception e) {}
            try {contentLength = String.valueOf(resource.getContentLength());} catch (Exception e) {}
            try {contentType = resource.getContentType();} catch (Exception e) {}
            try {creationDate = df.format(resource.getCreationDate());} catch (Exception e) {}
            try {creatorDisplayName = resource.getCreatorDisplayName();} catch (Exception e) {}
            try {displayName = resource.getDisplayName();} catch (Exception e) {}
            try {lastModified = df.format(resource.getLastModified());} catch (Exception e) {}
            
            System.out.println( propsRf.format(PropertyName.COMMENT.getString(),                  comment            ));
            System.out.println( propsRf.format(PropertyName.CONTENT_CHARACTER_SET.getString(),    contentCharacterSet));
            System.out.println( propsRf.format(PropertyName.CONTENT_IDENTIFIER.getString(),       contentIdentifier  ));
            System.out.println( propsRf.format(PropertyName.CONTENT_LANGUAGE.getString(),         contentLanguage    ));
            System.out.println( propsRf.format(PropertyName.CONTENT_LENGTH.getString(),           contentLength      ));
            System.out.println( propsRf.format(PropertyName.CONTENT_TYPE.getString(),             contentType        ));
            System.out.println( propsRf.format(PropertyName.CREATION_DATE.getString(),            creationDate       ));
            System.out.println( propsRf.format(PropertyName.CREATOR_DISPLAY_NAME.getString(),     creatorDisplayName ));
            System.out.println( propsRf.format(PropertyName.DISPLAY_NAME.getString(),             displayName        ));
            System.out.println( propsRf.format(PropertyName.LAST_MODIFIED.getString(),            lastModified       ));
            //        System.out.println( propsRf.format(PropertyName.WORKSPACE_FOLDER_LIST.getString(),    resource.getWorkspaceFolderList().toString()) );
            
            return resource;
        }
        
        /**
         *
         */
        public boolean equals( Object o ) {
            if( o instanceof ResourceNode ) {
                ResourceNode ouh = (ResourceNode)o;
                path.equals(ouh.path);
            }
            return false;
        }
        
        /**
         *
         */
        public int hashCode() {
            return path.hashCode();
        }
        
        /**
         * Return string representation.
         */
        public String toString() {
            return path.toString();
        }
    }
    
    private class ControllableResourceNode extends ResourceNode {
        
        ControllableResourceNode( ResourceNode parent, String seg, Class type ) {
            super( parent, seg, type );
        }
        
        void versionControl() throws Exception {
            Location location = provider.location( toString() );
            location.controllableResource().doControl();
        }
        
        Resource showProperties() throws Exception {
            ControllableResource resource = (ControllableResource)super.showProperties();
            
            String activityList                   = _VALUE_UNAVAILABLE;
            String autoMergeList                  = _VALUE_UNAVAILABLE;
            String checkedIn                      = _VALUE_UNAVAILABLE;
            String checkedOut                     = _VALUE_UNAVAILABLE;
            String dirtyPropertyList              = _VALUE_UNAVAILABLE;
            String isCheckedOut                   = _VALUE_UNAVAILABLE;
            String isDirtyContent                 = _VALUE_UNAVAILABLE;
            String isStaleContent                 = _VALUE_UNAVAILABLE;
            String mergeList                      = _VALUE_UNAVAILABLE;
            String predecessorList                = _VALUE_UNAVAILABLE;
            String serverState                    = _VALUE_UNAVAILABLE;
            String stalePropertyList              = _VALUE_UNAVAILABLE;
            String unreserved                     = _VALUE_UNAVAILABLE;
            String versionControllable            = _VALUE_UNAVAILABLE;
            String versionControlledConfiguration = _VALUE_UNAVAILABLE;
            String versionHistory                 = _VALUE_UNAVAILABLE;
            String workspace                      = _VALUE_UNAVAILABLE;
            
            try {
                Version checkedInV = resource.getCheckedIn();
                addRef( PropertyName.CHECKED_IN.getString(), checkedInV.location().string() );
            } catch (WvcmException e) {}
            
            try {activityList                   = resource.getActivityList().toString();} catch (Exception e) {};
            try {autoMergeList                  = resource.getAutoMergeList().toString();} catch (Exception e) {};
//            try {checkedIn                      = getRef(PropertyName.CHECKED_IN.getString()).toString();} catch (Exception e) {};
            try {checkedIn                      = resource.getCheckedIn().location().string();} catch (Exception e) {};
            try {checkedOut                     = resource.getCheckedOut().location().string();} catch (Exception e) {};
            try {dirtyPropertyList              = resource.getDirtyPropertyList().toString();} catch (Exception e) {};
            try {isCheckedOut                   = String.valueOf(resource.getIsCheckedOut());} catch (Exception e) {};
            try {isDirtyContent                 = String.valueOf(resource.getIsDirtyContent());} catch (Exception e) {};
            try {isStaleContent                 = String.valueOf(resource.getIsStaleContent());} catch (Exception e) {};
            try {mergeList                      = resource.getMergeList().toString();} catch (Exception e) {};
            try {predecessorList                = resource.getPredecessorList().toString();} catch (Exception e) {};
            try {serverState                    = resource.getServerState().toString();} catch (Exception e) {};
            try {stalePropertyList              = resource.getStalePropertyList().toString();} catch (Exception e) {};
            try {unreserved                     = String.valueOf(resource.getUnreserved());} catch (Exception e) {};
            try {versionControllable            = String.valueOf(resource.getVersionControllable());} catch (Exception e) {};
            try {versionControlledConfiguration = resource.getControlledConfiguration().toString();} catch (Exception e) {};
            try {versionHistory                 = resource.getVersionHistory().toString();} catch (Exception e) {};
            try {workspace                      = resource.getWorkspace().toString();} catch (Exception e) {};
            
            System.out.println( propsRf.format( PropertyName.ACTIVITY_LIST.getString()                   , activityList                  ));
            System.out.println( propsRf.format( PropertyName.AUTO_MERGE_LIST.getString()                 , autoMergeList                 ));
            System.out.println( propsRf.format( PropertyName.CHECKED_IN.getString()                      , checkedIn                     ));
            System.out.println( propsRf.format( PropertyName.CHECKED_OUT.getString()                     , checkedOut                    ));
            System.out.println( propsRf.format( PropertyName.DIRTY_PROPERTY_LIST.getString()             , dirtyPropertyList             ));
            System.out.println( propsRf.format( PropertyName.IS_CHECKED_OUT.getString()                  , isCheckedOut                  ));
            System.out.println( propsRf.format( PropertyName.IS_DIRTY_CONTENT.getString()                , isDirtyContent                ));
            System.out.println( propsRf.format( PropertyName.IS_STALE_CONTENT.getString()                , isStaleContent                ));
            System.out.println( propsRf.format( PropertyName.MERGE_LIST.getString()                      , mergeList                     ));
            System.out.println( propsRf.format( PropertyName.PREDECESSOR_LIST.getString()                , predecessorList               ));
            System.out.println( propsRf.format( PropertyName.SERVER_STATE.getString()                    , serverState                   ));
            System.out.println( propsRf.format( PropertyName.STALE_PROPERTY_LIST.getString()             , stalePropertyList             ));
            System.out.println( propsRf.format( PropertyName.UNRESERVED.getString()                      , unreserved                    ));
            System.out.println( propsRf.format( PropertyName.VERSION_CONTROLLABLE.getString()            , versionControllable           ));
            System.out.println( propsRf.format( PropertyName.VERSION_CONTROLLED_CONFIGURATION.getString(), versionControlledConfiguration));
            System.out.println( propsRf.format( PropertyName.VERSION_HISTORY.getString()                 , versionHistory                ));
            System.out.println( propsRf.format( PropertyName.WORKSPACE.getString()                       , workspace                     ));
            return resource;
        }
    }
    
    private class VersionNode extends ResourceNode {
        
        VersionNode( ResourceNode parent, String seg, Class type ) {
            super( parent, seg, type );
        }
    }
    
    private class VersionHistoryNode extends ResourceNode {
        
        VersionHistoryNode( ResourceNode parent, String seg, Class type ) {
            super( parent, seg, type );
        }
    }
    
    
    //
    // folder representative
    //
    private class FolderNode extends ControllableResourceNode {
        
        FolderNode( ResourceNode parent, String seg, Class type ) {
            super( parent, seg, type );
        }
        
        void createFile(String newFileName) throws Exception {
            Location newLocation = provider.location(toString()+"/"+newFileName);
            ControllableResource newResource = newLocation.controllableResource();
            newResource.doCreateResource();
//            try {
//                newResource.doControl();
//            } catch (WvcmException e){
//                // ignore is not controllable
//            }
            Resource r = newResource.doReadProperties(null);
            this.addChild(newFileName, WvcmTypes.type(r));
        }
        
        void deleteFile(String filename) throws Exception{
            Location location = provider.location(toString()+"/"+filename);
            Resource file = location.resource();
            if ((file instanceof Folder) || (file instanceof ControllableFolder)){
                System.out.println(filename + " is a folder and not a file!");
            } else {
                file.doUnbind();
                resetChildren();
                resetReferences();
            }
        }
        
        Resource createFolder(String newFolderName) throws Exception {
            Location newLocation = provider.location(toString()+"/"+newFolderName);
            ControllableFolder newFolder = (ControllableFolder)newLocation.folder();
            newFolder.doCreateResource();
            return newFolder;
        }
        
        void deleteFolder() throws Exception {
            Location location = provider.location(toString());
            ControllableFolder folder = (ControllableFolder)location.folder();
            folder.doUnbind();
        }
        
        void move(ResourceNode to) throws Exception {
            
            if (!(to instanceof FolderNode)){
                System.out.println("target of move operation is not a folder");
            } else {
                Location lSource = provider.location( toString() );
                Location lTo = provider.location( to.toString() );
                // this is a folder!
                Resource rSource = lSource.folder();
                
                try {
                    rSource.doRebind(lTo, false);
                } catch (WvcmException e){
                    System.out.println(e.toString());
                }
            }
        }
        
        
        void showMembers(boolean withProperties) throws Exception {
            Location location = provider.location( toString() );
            ControllableFolder folder = (ControllableFolder)location.folder();
            PropertyNameList pnl = new PropertyNameList(
                new PropertyName[]
                {PropertyName.LAST_MODIFIED}
            );
            Iterator mlIter = folder.doReadMemberList( pnl, false );
            if (withProperties){
                System.out.println( dirRf.format(dirRHeaders) );
                System.out.println( dirRf.separator('-') );
            } else {
                System.out.println( dirRfNoProperties.format(dirRHeadersNoProperties) );
                System.out.println( dirRfNoProperties.separator('-') );
            }
            resetChildren();
            while (mlIter.hasNext()) {
                Resource r = (Resource)mlIter.next();
                Path p = new Path(r.location().string());
                addChild(p.getLastSegment(), WvcmTypes.type(r));
                if (withProperties){
                    System.out.println( dirRf.format(p.getLastSegment(), WvcmTypes.typeName(r), df.format(r.getLastModified())) );
                } else {
                    System.out.println( dirRfNoProperties.format(p.getLastSegment()) );
                }
            }
        }
    }
}
