/*
 * @(#)file      SnmpOid.java
 * @(#)author    Sun Microsystems, Inc.
 * @(#)version   4.22
 * @(#)date      05/04/13
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */
// Copyright (c) 1995-96 by Cisco Systems, Inc.

package com.sun.jmx.snmp;


// java imports
//
import java.util.StringTokenizer;
import java.util.NoSuchElementException;

/**
 * Represents an SNMP Object Identifier (OID).
 *
 * <p><b>This API is a Sun Microsystems internal API  and is subject 
 * to change without notice.</b></p>
 * @version     4.22     12/19/03
 * @author      Sun Microsystems, Inc
 * @author      Cisco Systems, Inc.
 */

public class SnmpOid extends SnmpValue {

    // CONSTRUCTORS
    //-------------
    /**
     * Constructs a new <CODE>SnmpOid</CODE> with no components.
     */
    public SnmpOid() {
	components = new long[15] ;
	componentCount = 0 ;
    }
  
    /**
     * Constructs a new <CODE>SnmpOid</CODE> from the specified component array.
     * @param oidComponents The initialization component array.
     */
    public SnmpOid(long[] oidComponents) {
	components = (long[])oidComponents.clone() ;
	componentCount = components.length ;
    }

    /**
     * Constructs a new <CODE>SnmpOid</CODE> containing one component with the
     * specified value.
     * @param id The initialization component value.
     */
    public SnmpOid(long id) {
	components = new long[1] ;
	components[0] = id ;
	componentCount = components.length ;
    }
  
    /**
     * Constructs a new <CODE>SnmpOid</CODE> containing four components 
     * with the specified values.
     * @param id1 The first component value.
     * @param id2 The second component values.
     * @param id3 The third component values.
     * @param id4 The fourth component values.
     */
    public SnmpOid(long id1, long id2, long id3, long id4) {
	components = new long[4] ;
	components[0] = id1 ;
	components[1] = id2 ;
	components[2] = id3 ;
	components[3] = id4 ;
	componentCount = components.length ;
    }

    /**
     * Constructs a new <CODE>SnmpOid</CODE> from a dot-formatted <CODE>String</CODE> or a MIB variable
     * name. It generates an exception if the variable name cannot be resolved, or
     * if the dot-formatted <CODE>String</CODE> has an invalid subidentifier.
     * This constructor helps build an OID object with a <CODE>String</CODE> like .1.2.3.4 or 1.2.3.4
     * or <CODE>ifInOctets</CODE> or <CODE>ifInOctets</CODE>.0.
     * @param s <CODE>String</CODE> or MIB variable of the form .1.2.3 or 1.2.3 or <CODE>ifInOctets</CODE>.
     * @exception IllegalArgumentException The subidentifier is neither a numeric <CODE>String</CODE>
     * nor a <CODE>String</CODE> of the MIB database.
     */
    public SnmpOid(String s) throws IllegalArgumentException {
	String dotString = s ;

	if (s.startsWith(".") == false) {
	    try {
		dotString = resolveVarName(s);
	    } catch(SnmpStatusException e) {
		throw new IllegalArgumentException(e.getMessage());
	    }
	}

	StringTokenizer st = new StringTokenizer(dotString, ".", false) ;
	componentCount= st.countTokens();
    
	// Now extract the ids
	//
	if (componentCount == 0) {
	    components = new long[15] ;
	}  else {
	    components = new long[componentCount] ;
	    try {
		for (int i = 0 ; i < componentCount ; i++) {
		    try {
			components[i] = Long.parseLong(st.nextToken()) ;
		    }
		    catch(NoSuchElementException e) {}
		}
	    }
	    catch(NumberFormatException e) {
		throw new IllegalArgumentException(s) ;
	    }
	}
    }

    // PUBLIC METHODS
    //---------------
    /**
     * Gets the number of components in this OID.
     * @return The number of components.
     */
    public int getLength() {
	return componentCount ;
    }

    /**
     * Returns a copy of the components array of this <CODE>SnmpOid</CODE>.
     * @return The copy of the components array.
     */
    public long[] longValue() {
	long[] result = new long[componentCount] ;
	System.arraycopy(components,0,result,0,componentCount);
	return result ;
    }

    /**
     * Returns the components array of this <CODE>SnmpOid</CODE>.
     * If <code>duplicate</code> is true, a copy is returned.
     * Otherwise, a reference to the internal array is returned,
     * in which case the caller <b>shall not</b> modify this array. 
     * This method is provided to optimize processing in those cases 
     * where the caller needs only to read the components array.
     *
     * @param duplicate Indicates whether a copy or a reference must 
     *        be returned: 
     *        <li><code>true</code> if a copy must be returned,</li>
     *        <li><code>false</code> if a reference on the internal data
     *            can be returned.</li>
     * @return A copy of (or a reference on) the components array.
     */
    public final long[] longValue(boolean duplicate) {
	if (duplicate) return longValue();
	if (componentCount == components.length) return components ;
	components =  longValue();
	componentCount = components.length;
	return components ;
    }

    /**
     * Returns the value of the OID arc found at the requested position
     * in the <CODE>components</CODE> array. The first element is at
     * position <code>0</code>.
     *
     * @param  pos The position at which the OID arc should be peeked.
     *
     * @return The OID arc found at the requested position.
     *
     * @exception SnmpStatusException No OID arc was found at the requested 
     *            position.
     */
    public final long getOidArc(int pos) throws SnmpStatusException {
	try {
	    return components[pos];
	} catch(Exception e) {
	    throw new SnmpStatusException(SnmpStatusException.noAccess);
	}
    }

    /**
     * Converts the OID value to its <CODE>Long</CODE> form.
     * @return The <CODE>Long</CODE> representation of the value.
     */
    public Long toLong() {
	if (componentCount != 1) {
	    throw new IllegalArgumentException() ;
	}
	return new Long(components[0]) ;
    }

    /**
     * Converts the OID value to its <CODE>Integer</CODE> form.
     * @return The <CODE>Integer</CODE> representation of the value.
     */
    public Integer toInteger() {
	if ((componentCount != 1) || (components[0] > Integer.MAX_VALUE)) {
	    throw new IllegalArgumentException() ;
	}
	return new Integer((int)components[0]) ;
    }

    /**
     * Converts the OID value to its <CODE>String</CODE> form.
     * @return The <CODE>String</CODE> representation of the value.
     */
    public String toString() {
	String result = "" ;
	if (componentCount >= 1) {
	    for (int i = 0 ; i < componentCount - 1 ; i++) {
		result = result + components[i] + "." ;
	    }
	    result = result + components[componentCount - 1] ;
	}
	return result ;
    }
    
    /**
     * Converts the OID value to its <CODE>Boolean</CODE> form.
     * @return The <CODE>Boolean</CODE> representation of the value.
     */
    public Boolean toBoolean() {
	if ((componentCount != 1) && (components[0] != 1) && (components[0] != 2)) {
	    throw new IllegalArgumentException() ;
	}
	return new Boolean(components[0] == 1) ;
    }
  
    /**
     * Converts the OID value to its array of <CODE>Bytes</CODE> form.
     * @return The array of <CODE>Bytes</CODE> representation of the value.
     */
    public Byte[] toByte() {
	Byte[] result = new Byte[componentCount] ;
	for (int i =0 ; i < componentCount ; i++) {
	    if (components[0] > 255) {
		throw new IllegalArgumentException() ;
	    }
	    result[i] = new Byte((byte)components[i]) ;
	}
	return result ;
    }

    /**
     * Converts the OID value to its <CODE>SnmpOid</CODE> form.
     * @return The OID representation of the value.
     */
    public SnmpOid toOid() {
	long[] ids = new long[componentCount] ;
	for (int i = 0 ; i < componentCount ; i++) {
	    ids[i] = components[i] ;
	}
	return new SnmpOid(ids) ;
    }
  
    /**
     * Extracts the OID from an index OID and returns its
     * value converted as an <CODE>SnmpOid</CODE>.
     * @param index The index array.
     * @param start The position in the index array.
     * @return The OID representing the OID value.
     * @exception SnmpStatusException There is no OID value
     * available at the start position.
     */
    public static SnmpOid toOid(long[] index, int start) throws SnmpStatusException {
	try {
	    if (index[start] > Integer.MAX_VALUE) {
		throw new SnmpStatusException(SnmpStatusException.noSuchName) ;
	    }
	    int idCount = (int)index[start++] ;
	    long[] ids = new long[idCount] ;
	    for (int i = 0 ; i < idCount ; i++) {
		ids[i] = index[start + i] ;
	    }
	    return new SnmpOid(ids) ;
	}
	catch(IndexOutOfBoundsException e) {
	    throw new SnmpStatusException(SnmpStatusException.noSuchName) ;
	}
    }

    /**
     * Scans an index OID, skips the OID value and returns the position
     * of the next value.
     * @param index The index array.
     * @param start The position in the index array.
     * @return The position of the next value.
     * @exception SnmpStatusException There is no OID value
     * available at the start position.
     */
    public static int nextOid(long[] index, int start) throws SnmpStatusException {
	try {
	    if (index[start] > Integer.MAX_VALUE) {
		throw new SnmpStatusException(SnmpStatusException.noSuchName) ;
	    }
	    int idCount = (int)index[start++] ;
	    start += idCount ;
	    if (start <= index.length) {
		return start ;
	    }
	    else {
		throw new SnmpStatusException(SnmpStatusException.noSuchName) ;
	    }
	}
	catch(IndexOutOfBoundsException e) {
	    throw new SnmpStatusException(SnmpStatusException.noSuchName) ;
	}
    }

    /**
     * Appends an <CODE>SnmpOid</CODE> representing an <CODE>SnmpOid</CODE> to another OID.
     * @param source An OID representing an <CODE>SnmpOid</CODE> value.
     * @param dest Where source should be appended.
     */
    public static void appendToOid(SnmpOid source, SnmpOid dest) {
	dest.append(source.getLength()) ;
	dest.append(source) ;
    }
  
    /**
     * Performs a clone action. This provides a workaround for the
     * <CODE>SnmpValue</CODE> interface.
     * @return The SnmpValue clone.
     */
    final synchronized public SnmpValue duplicate() {
	return (SnmpValue)clone() ;
    }

    /**
     * Clones the <CODE>SnmpOid</CODE> object, making a copy of its data.
     * @return The object clone.
     */
    public Object clone() {
	try {
	    SnmpOid obj = (SnmpOid)super.clone() ;
	    obj.components = new long[this.componentCount] ;

	    System.arraycopy(this.components, 0, obj.components, 0,
			     this.componentCount) ;
	    return obj ;
	} catch (CloneNotSupportedException e) {
	    throw new InternalError() ;  // should never happen. VM bug.
	}
    }

    /**
     * Inserts a subid at the beginning of this <CODE>SnmpOid</CODE>.
     * @param id The long subid to insert.
     */
    public void insert(long id) {
	enlargeIfNeeded(1) ;
	for (int i = componentCount - 1 ; i >= 0 ; i--) {
	    components[i + 1] = components[i] ;
	}
	components[0] = id ;
	componentCount++ ;
    }
 
    /**
     * Inserts a subid at the beginning of this <CODE>SnmpOid</CODE>.
     * @param id The integer subid to insert.
     */
    public void insert(int id) {
	insert((long)id) ;
    }
  
    /**
     * Appends the specified <CODE>SnmpOid</CODE> to the end of this <CODE>SnmpOid</CODE>.
     * @param oid The OID to append.
     */
    public void append(SnmpOid oid) {
	enlargeIfNeeded(oid.componentCount) ;
	for (int i = 0 ; i < oid.componentCount ; i++) {
	    components[componentCount + i] = oid.components[i] ;
	}
	componentCount += oid.componentCount ;
    }
  
    /**
     * Appends the specified long to the end of this <CODE>SnmpOid</CODE>.
     * @param id The long to append.
     */
    public void append(long id) {
	enlargeIfNeeded(1) ;
	components[componentCount] = id ;
	componentCount++ ;
    }
  
    /**
     * Adds the specified dot-formatted OID <CODE>String</CODE> to the end of this <CODE>SnmpOid</CODE>.
     * The subidentifiers can be expressed as a dot-formatted <CODE>String</CODE> or a
     * MIB variable name.
     * @param s Variable name of the form .1.2.3 or  1.2.3 or
     * <CODE>ifInOctets</CODE>.
     * @exception SnmpStatusException An error occurred while accessing a MIB node.
     */
    public void addToOid(String s) throws SnmpStatusException {
	SnmpOid suffix= new SnmpOid(s);
	this.append(suffix);
    }

    /**
     * Adds the specified array of longs to the end of this <CODE>SnmpOid</CODE>.
     * @param oid An array of longs.
     * @exception SnmpStatusException An error occurred while accessing a MIB node.
     */
    public void addToOid(long []oid) throws SnmpStatusException {
	SnmpOid suffix= new SnmpOid(oid);
	this.append(suffix);
    }

    /**
     * Checks the validity of the OID.
     * @return <CODE>true</CODE> if the OID is valid, <CODE>false</CODE> otherwise.
     */
    public boolean isValid() {
	return ((componentCount >= 2) &&
		((0 <= components[0]) && (components[0] < 3)) &&
		((0 <= components[1]) && (components[1] < 40))) ;
    }

    /**
     * Checks whether the specified <CODE>Object</CODE> is equal to this <CODE>SnmpOid</CODE>.
     * @param o The <CODE>Object</CODE> to be compared.
     * @return <CODE>true</CODE> if <CODE>o</CODE> is an <CODE>SnmpOid</CODE> instance and equal to this, <CODE>false</CODE> otherwise.
     */
    public boolean equals(Object o) {
	boolean result = false ;
    
	if (o instanceof SnmpOid) {
	    SnmpOid oid = (SnmpOid)o ;
	    if (oid.componentCount == componentCount) {
		int i = 0 ;
		long[]  objoid = oid.components;
		while ((i < componentCount) && (components[i] == objoid[i]))
		    i++ ;
		result = (i == componentCount) ;
	    }
	}
	return result ;
    }
 
     /**
     * The hashCode is computed from the OID components.
     * @return a hashCode for this SnmpOid.
     **/
    public int hashCode() {
	long acc=0;
	for (int i=0;i<componentCount;i++) {
	    acc = acc*31+components[i];
	}
	return (int)acc;
    }

   /**
     * Compares two OIDs lexicographically.
     * @param other The OID to be compared.
     * @return 
     * The value 0 if the parameter <CODE>other</CODE> is equal to this <CODE>SnmpOid</CODE>.
     * A value smaller than 0 if this <CODE>SnmpOid</CODE> is lexicographically smaller than <CODE>other</CODE>.
     * A value larger than 0 if this <CODE>SnmpOid</CODE> is lexicographically larger than <CODE>other</CODE>. 
     */
    public int compareTo(SnmpOid other) {
	int result = 0 ;
	int i = 0 ;
	int cmplen = Math.min(componentCount, other.componentCount) ;
	long[] otheroid = other.components;

	for (i = 0; i < cmplen; i++) {
	    if (components[i] != otheroid[i]) {
		break ;
	    }
	}
	if ((i == componentCount) && (i == other.componentCount)) {
	    result = 0 ;
	}
	else if (i == componentCount) {
	    result = -1 ;
	}
	else if (i == other.componentCount) {
	    result = 1 ;
	}
	else {
	    result = (components[i] < otheroid[i]) ? -1 : 1 ;
	}
	return result ;
    }
      
    /**
     * Resolves a MIB variable <CODE>String</CODE> with the MIB database.
     * @param s The variable name to resolve.
     * @exception SnmpStatusException If the variable is not found in the MIB database.
     */
    public String resolveVarName(String s) throws SnmpStatusException {
	int index = s.indexOf('.') ;
    
	// First handle the case where oid is expressed as 1.2.3.4
	//
	try {
	    return handleLong(s, index);
	} catch(NumberFormatException e) {}
    
	// if we are here, it means we have something to resolve..
	//
	if (meta == null)
	  throw new SnmpStatusException(SnmpStatusException.noSuchName);
    
	// Ok assume there is a variable name to resolve ...
	//
	if (index <= 0) {          
	    SnmpOidRecord rec = meta.resolveVarName(s);
	    return rec.getOid();
	
	} else {
	    SnmpOidRecord rec = meta.resolveVarName(s.substring(0, index));
	    return (rec.getOid()+ s.substring(index));

	}
    }  

    /**
     * Returns a textual description of the type object.
     * @return ASN.1 textual description.
     */
    public String getTypeName() {
	return name ;
    }

    /**
     * Returns the MIB table used for resolving MIB variable names.
     * @return The MIB table.
     */
    public static SnmpOidTable getSnmpOidTable() {
	return meta;
    }
    
    /**
     * Sets the MIB table to use for resolving MIB variable names.
     * If no mib table is available, the class will not be able to resolve 
     * names contained in the Object Identifier.
     * @param db The MIB table to use.
     */
    public static void setSnmpOidTable(SnmpOidTable db) {
	meta = db;
    }

    /**
     * Converts an OID index converted string back to a DisplayString
     *
     **/
    public String toOctetString() { 
	return new String(tobyte()) ; 
    } 
 

    // PRIVATE METHODS
    //------------------

    /**
     * convert the components array into a byte array
     **/
    private byte[] tobyte() { 
	byte[] result = new byte[componentCount] ; 
	for (int i =0 ; i < componentCount ; i++) { 
	    if (components[0] > 255) { 
		throw new IllegalArgumentException() ; 
	    } 
	    result[i] = (byte)components[i] ; 
	} 
	return result ; 
    } 


    /**
     * Checks if there is enough space in the components
     * array to insert n new subids. If not, it increases the size of
     * the array.
     * In fact it reallocates a new array and copy the old one into the new one.
     * @param n The number of subids to insert.
     */
    private void enlargeIfNeeded(int n) {
	int neededSize = components.length ;
	while (componentCount + n > neededSize) {
	    neededSize = neededSize * 2 ;
	}
	if (neededSize > components.length) {
	    long[] newComponents = new long[neededSize] ;
	    for (int i = 0 ; i < components.length ; i++) {
		newComponents[i] = components[i] ;
	    }
	    components = newComponents ;
	}
    }
  
    // PRIVATE METHODS
    //----------------
    private String handleLong(String oid, int index) throws NumberFormatException, SnmpStatusException {
	String str;
	if (index >0) {
	    str= oid.substring(0, index);
	} else {
	    str= oid ;
	}
    
	// just parse the element.
	//
	Long.parseLong(str);
	return oid;
    }
  
    // VARIABLES
    //----------
    /**
     * The components' array.
     * @serial
     */
    protected long components[] = null ;

    /**
     * The length of the components' array.
     * @serial
     */
    protected int componentCount = 0 ;
  
    /**
     * The name of the type.
     */
    final static String  name = "Object Identifier";
  
    /**
     * Reference to a mib table. If no mib table is available,
     * the class will not be able to resolve names contained in the Object Identifier.
     */
     private static SnmpOidTable meta= null;

    /**
     * Ensure serialization compatibility with version 4.1 FCS
     *
     */
    static final long serialVersionUID = 8956237235607885096L;
}
