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

package org.apache.slide.content;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.slide.common.ObjectValidationFailedException;
import org.apache.slide.util.EmptyEnumeration;
import org.apache.slide.util.Messages;

/**
 * Node Revision Descriptors class.
 *
 * @version $Revision: 1.19 $
 */
public final class NodeRevisionDescriptors implements Serializable, Cloneable {
    
    
    // -------------------------------------------------------------- Constants
    
    
    public static final String MAIN_BRANCH = "main";
    
    
    protected static final NodeRevisionNumber initialRevision
        = new NodeRevisionNumber();
    
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Client side constructor.
     */
    public NodeRevisionDescriptors() {
        this.latestRevisionNumbers = new Hashtable();
        this.branches = new Hashtable();
        this.workingRevisions = new Hashtable();
        this.useVersioning = false;
    }
    
    
    /**
     * Client side constructor.
     */
    public NodeRevisionDescriptors(boolean isVersioned) {
        this.latestRevisionNumbers = new Hashtable();
        this.branches = new Hashtable();
        this.workingRevisions = new Hashtable();
        this.useVersioning = isVersioned;
    }
    
    
    /**
     * Store Constructor.
     */
    public NodeRevisionDescriptors(String uri,
                                   NodeRevisionNumber initialRevision,
                                   Hashtable workingRevisions,
                                   Hashtable latestRevisionNumbers,
                                   Hashtable branches, boolean isVersioned) {
        this.uri = uri;
        this.latestRevisionNumbers = latestRevisionNumbers;
        this.branches = branches;
        this.workingRevisions = workingRevisions;
        this.useVersioning = isVersioned;
    }
    
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Use versioning.
     */
    private boolean useVersioning;
    
    
    /**
     * Uri.
     */
    private String uri;
    
    
    /**
     * The original URI (in case URI was redirected).
     */
    private String originalUri;
    
    
    /**
     * Working revisions.
     * 
     * <p>TODO: <code>workingRevisions</code> are only writen never read!
     */
    private Hashtable workingRevisions;
    
    
    /**
     * Latest revision numbers.
     */
    private Hashtable latestRevisionNumbers;
    
    
    /**
     * Branches.
     */
    private Hashtable branches;
    
    
    // ------------------------------------------------------------- Properties
    
    
    /**
     * Use versioning accessor.
     */
    public boolean isVersioned() {
        return this.useVersioning;
    }
    
    
    /**
     * Use versioning mutator.
     */
    void setVersioned(boolean useVersioning) {
        this.useVersioning = useVersioning;
    }
    
    
    /**
     * Has revision ?
     */
    public boolean hasRevisions() {
        return !(this.latestRevisionNumbers.isEmpty());
    }
    
    
    /**
     * Uri accessor.
     */
    public String getUri() {
        return this.uri;
    }
    
    
    /**
     * OriginalUri accessor.
     */
    public String getOriginalUri() {
        if( this.originalUri != null )
            return this.originalUri;
        else
            return this.uri;
    }
    
    
    /**
     * OriginalUri mutator.
     */
    void setOriginalUri( String originalUri ) {
        this.originalUri = originalUri;
    }
    
    
    /**
     * Uri mutator.
     */
    public void setUri(String uri) {
        this.uri = uri;
    }
    
    
    /**
     * Get initial revision.
     */
    public NodeRevisionNumber getInitialRevision() {
        return NodeRevisionDescriptors.initialRevision;
    }
    
    
    /**
     * Get latest revision from main branch.
     */
    public NodeRevisionNumber getLatestRevision() {
        return getLatestRevision(MAIN_BRANCH);
    }
    
    
    /**
     * Get latest revision from a branch.
     */
    public NodeRevisionNumber getLatestRevision(String branchName) {
        Object number = null;
        number = this.latestRevisionNumbers.get(branchName);
        if (number != null) {
            return (NodeRevisionNumber) number;
        } else {
            return null;
        }
    }
    
    
    /**
     * Latest revision mutator.
     */
    void setLatestRevision(NodeRevisionNumber number) {
        this.latestRevisionNumbers.put(MAIN_BRANCH, number);
    }
    
    
    /**
     * Latest revision mutator.
     */
    void setLatestRevision(String branch, NodeRevisionNumber number) {
        this.latestRevisionNumbers.put(branch, number);
    }
    
    
    // --------------------------------------------------------- Public Methods
    
    
    /**
     * Get relations.
     */
    public Enumeration getSuccessors(NodeRevisionNumber number) {
        Object result = this.branches.get(number);
        if (result != null) {
            return ((Vector) result).elements();
        } else {
            return EmptyEnumeration.INSTANCE;
        }
    }
    
    /**
     * Return true, if ancNrn and descNrn are in an ancestor-descendant relationship in
     * this history.
     */
    public boolean isAncestorDescendant( NodeRevisionNumber ancNrn, NodeRevisionNumber descNrn ) {
        
        if( ancNrn.equals(descNrn) )
            return true;
        
        Enumeration ancSuccs = getSuccessors( ancNrn );
        while( ancSuccs.hasMoreElements() ) {
            NodeRevisionNumber ancSuccNrn = (NodeRevisionNumber)ancSuccs.nextElement();
            if( isAncestorDescendant(ancSuccNrn, descNrn) )
                return true;
        }
        
        return false;
    }
    /**
     * Returns true, if and only if uri != originalUri
     */
    public boolean isRedirected() {
        return( !uri.equals(originalUri) );
    }
    
    // -------------------------------------------------------- Package Methods
    
    
    /**
     * Add relation.
     */
    void setSuccessors(NodeRevisionNumber number,
                       NodeRevisionNumber successor) {
        Vector tempVector = new Vector();
        tempVector.addElement(successor);
        setSuccessors(number, tempVector);
    }
    
    
    /**
     * Add relation.
     */
    void setSuccessors(NodeRevisionNumber number, Vector successors) {
        this.branches.put(number, successors);
    }
    
    
    /**
     * Add relation.
     */
    void addSuccessor(NodeRevisionNumber number,
                      NodeRevisionNumber successor) {
        Object result = this.branches.get(number);
        if (result != null) {
            ((Vector) result).addElement(successor);
        } else {
            setSuccessors(number, successor);
        }
    }
    
    
    /**
     * Remove relation.
     */
    void removeSuccessor(NodeRevisionNumber number,
                         NodeRevisionNumber successor) {
        Object result = this.branches.get(number);
        if (result != null) {
            ((Vector) result).removeElement(successor);
        }
    }
    
    
    /**
     * Enumerate all revision numbers in all branches.
     */
    public Enumeration enumerateRevisionNumbers() {
        return this.branches.keys();
    }
    

    /**
     * Enumerate all branch names.
     */
   public Enumeration enumerateBranchNames() {
     return this.latestRevisionNumbers.keys();
   }

 
    
    
    
    // --------------------------------------------------------- Object Methods
    
    
    /**
     * Clone.
     *
     * @return Object clone
     */
    public NodeRevisionDescriptors cloneObject() {
        NodeRevisionDescriptors result = null;
        try {
            result = (NodeRevisionDescriptors) super.clone();
            // TODO : No cloning of the working revisions list for now
            // Cloning branches
            Hashtable branchesClone = new Hashtable();
            Enumeration branchesList = this.branches.keys();
            while (branchesList.hasMoreElements()) {
                Object key = branchesList.nextElement();
                Vector value = (Vector) this.branches.get(key);
                branchesClone.put(key, value.clone());
            }
            result.branches = branchesClone;
            // Cloning latest revision list
            result.latestRevisionNumbers =
                (Hashtable) this.latestRevisionNumbers.clone();
        } catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return result;
    }
    
    
    /**
     * Equals.
     *
     * @param obj Object to test
     * @return boolean True if the two object are equal :
     * <li>obj is of type NodeRevisionDescriptors and is not null</li>
     * <li>it has the same Uri</li>
     */
    public boolean equals(Object obj) {
        boolean result = false;
        if ((obj != null) && (obj instanceof NodeRevisionDescriptors)) {
            NodeRevisionDescriptors revisionDescriptors =
                (NodeRevisionDescriptors) obj;
            result = this.getUri().equals(revisionDescriptors.getUri());
        }
        return result;
    }
    
    
    /**
     * Validate.
     */
    public void validate(String expectedUri) {
        
        if (uri == null)
            throw new ObjectValidationFailedException
                (expectedUri, Messages.message
                 (NodeRevisionDescriptors.class.getName() + ".nullUri"));
        
        if (!(uri.equals(expectedUri) || uri.equals(expectedUri + "/"))) {
            System.out.println("Uri1 : " + uri + " Uri2 : " + expectedUri);
            //throw new ObjectValidationFailedException
            //(expectedUri, Messages.message
            //(NodeRevisionDescriptors.class.getName() + ".incorrectUri"));
        }
        
        if (workingRevisions == null)
            throw new ObjectValidationFailedException
                (uri, Messages.message
                 (NodeRevisionDescriptors.class.getName()
                  + ".nullWorkingRevisions"));
        
        if (latestRevisionNumbers == null)
            throw new ObjectValidationFailedException
                (uri, Messages.message
                 (NodeRevisionDescriptors.class.getName()
                  + ".nullLatestRevisionNumbers"));
        
        if (branches == null)
            throw new ObjectValidationFailedException
                (uri, Messages.message
                 (NodeRevisionDescriptors.class.getName()
                  + ".nullBranches"));
        
        // FIXME : Check branches integrity. Problem : It's quite expensive.
        
    }
    
    
}

