/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/content/NodeRevisionNumber.java,v 1.9 2004/07/28 09:37:54 ib Exp $
 * $Revision: 1.9 $
 * $Date: 2004/07/28 09:37:54 $
 *
 * ====================================================================
 *
 * 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.StringTokenizer;
import org.apache.slide.common.ObjectValidationFailedException;
import org.apache.slide.util.Messages;

/**
 * Node Revision Number class.
 * 
 * @version $Revision: 1.9 $
 */
public final class NodeRevisionNumber implements Serializable, Cloneable {
    
    
    // -------------------------------------------------------------- Constants
    
    public static final int MAJOR = 0;
    public static final int MINOR = 1;
    
    // The hidden revision number is used for DeltaV-related system data
    // ("version-history" and "backup" branches in VHR and VCR resources)
    public static final NodeRevisionNumber HIDDEN_0_0 =
        new NodeRevisionNumber( 0, 0 ); // major=0, minor=0
    
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Create a new revision number.
     */
    public NodeRevisionNumber() {
        number = new int[2];
        number[MAJOR] = 1;
        number[MINOR] = 0;
    }
    
    
    /**
     * Create a new revision number.
     * 
     * @param major 
     * @param minor 
     */
    public NodeRevisionNumber(int major, int minor) {
        number = new int[2];
        number[MAJOR] = major;
        number[MINOR] = minor;
    }
    
    
    /**
     * Create a new revision number.
     * 
     * @param revisionNumberStr String representation of the revision number
     */
    public NodeRevisionNumber(String revisionNumberStr) {
        parseString(revisionNumberStr);
    }
    
    
    /**
     * Create a new revision number based on a previous revision number.
     * 
     * @param revisionNumber Previous revision number
     */
    public NodeRevisionNumber(NodeRevisionNumber revisionNumber) {
        this(revisionNumber, false);
    }
    
    
    /**
     * Create a new revision number based on a previous revision number.
     * 
     * @param revisionNumber Previous revision number
     * @param createBranch True if a new branch is to be created
     */
    public NodeRevisionNumber(NodeRevisionNumber revisionNumber, 
                              boolean createBranch) {
        if (revisionNumber != null) {
            if (createBranch) {
                parseString(revisionNumber.toString() + ".1");
            } else {
                parseString(revisionNumber.toString());
                next();
            }
        } else {
            number = new int[2];
            number[MAJOR] = 1;
            number[MINOR] = 0;
        }
    }
    
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Number storage.
     * <b>Note: when this array or it's content is modified, you must reset
     * {@link #cachedToString} to null!</b>
     */
    private int[] number;
    
    
    // --------------------------------------------------------- Public Methods
    
    
    /**
     * Get the first number in the revision number.
     * 
     * @return int 
     */
    public int getMajor() {
        return number[MAJOR];
    }
    
    
    /**
     * Get the second number in the revision number.
     * 
     * @return int 
     */
    public int getMinor() {
        return number[MINOR];
    }
    
    
    /**
     * Get a number by specifying its order.
     * 
     * @param pos Position of the number
     * @return int 
     */
    public int getNumber(int pos) {
        return number[pos];
    }
    
    
    /**
     * Return number of digits if the revision number.
     * 
     * @return int
     */
    public int getNbDigits() {
        return number.length;
    }
    
    
    // -------------------------------------------------------- Private Methods
    
    
    /**
     * Parse a String.
     * 
     * @param revisionNumberStr String representation of the revision number
     */
    private void parseString(String revisionNumberStr) {
        // We tokenize the string using "." as a delimiter.
        StringTokenizer tokenizer = 
            new StringTokenizer(revisionNumberStr, ".");
        int nbDigits = tokenizer.countTokens();
        if (nbDigits > 0) {
            number = new int[nbDigits];
            for (int i=0; i<nbDigits; i++) {
                number[i] = (new Integer(tokenizer.nextToken())).intValue();
            }
            cachedToString=null;  // reset cache
        } else {
            number = new int[2];
            number[MAJOR] = 1;
            number[MINOR] = 0;
            cachedToString=null;  // reset cache
        }
    }
    
    
    /**
     * Next revision.
     */
    private void next() {
        if (number.length > 0) {
            number[number.length - 1] += 1;
            cachedToString=null;  // reset cache
        }
    }
    
    
    // --------------------------------------------------------- Public Methods


    /**
     * Caches the result of last toString()-call. This will also heavily improve
     * hashCode() as the String.hashCode() method is also buffered.
     * This imust be resetet to null whenever the {@link #number} array changes.
     */
    private String cachedToString;

    /**
     * Get a String representation of the revision number.
     * 
     * @return String 
     */
    public String toString() {
        if(cachedToString!=null) return cachedToString;
        int count=number.length;
        if(count==2) cachedToString=(number[MAJOR]+"."+number[MINOR]);
        else
        {
          StringBuffer buf = new StringBuffer();
          buf.append(number[MAJOR]);
          for (int i=1; i<count; i++) {
              buf.append('.').append(number[i]);
          }
          cachedToString=buf.toString();
        }
        return cachedToString;
        /* old code being replaced by the cached one
        StringBuffer buf = new StringBuffer();
        buf.append(number[MAJOR]);
        for (int i=1; i<number.length; i++) {
            buf.append(".").append(number[i]);
        }
        return buf.toString();*/
    }
    
    
    /**
     * HashCode.
     * 
     * @return int 
     */
    public int hashCode() {
        return toString().hashCode();
    }
    
    
    /**
     * Equals.
     * 
     * @param obj Object to test
     * @return boolean True if the two object are equal :
     * <li>obj is of type NodeRevisionNumber and is not null</li>
     * <li>The string representations are equal are equal</li>
     */
    public boolean equals(Object obj) {
        boolean result = false;
        if ((obj != null) && (obj instanceof NodeRevisionNumber)) {
            result = (this.toString().equals(obj.toString()));
        }
        return result;
    }
    
    
    /**
     * Clone.
     * 
     * @return Object clone
     */
    NodeRevisionNumber cloneObject() {
        NodeRevisionNumber result = null;
        try {
            result = (NodeRevisionNumber) super.clone();
        } catch(CloneNotSupportedException e) {
        }
        return result;
    }
    
    
    /**
     * Validate.
     */
    public void validate() {
        
        if (number == null)
            throw new ObjectValidationFailedException
                (Messages.message
                 (NodeRevisionNumber.class.getName() + ".nullNumber"));
        
        if (number.length < 2)
            throw new ObjectValidationFailedException
                (Messages.message
                 (NodeRevisionNumber.class.getName() + ".invalidNumber"));
        
    }
    
    
}
