// $Id: Attribute.java,v 1.1.1.1 2025/09/11 12:24:45 mhliu Exp $
/*
 * Copyright 1996-2014 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gsfc.nssdc.cdf;

import java.util.*;
import java.lang.*;
import java.io.File;

/** 
 * This class contains the methods that are associated with either global
 * or variable attributes.
 *
 * @version 1.0 
 * @version 2.0 03/18/05  Selection of current CDF and attribute are done as 
 *                        part of operations passed to JNI.
 *                        JNI call is synchronized so only one process is
 *                        allowed in a JVM, due to multi-thread safety.
 *                        The select method will never be called.
 * @version 3.3 01/31/11  Reset the vector size to match the maximum entry in
 *                        stead of its expanded capacity in getEntries method.
 * @author Phil Williams, QSS Group Inc/RITSS <BR>
 *         Mike Liu, RSTX 
 *
 * @see gsfc.nssdc.cdf.CDF
 * @see gsfc.nssdc.cdf.CDFException
 * @see gsfc.nssdc.cdf.Entry
 * @see gsfc.nssdc.cdf.Variable
 */
public class Attribute implements CDFConstants, CDFObject
{

    /**
     * The CDF this attribute belongs to
     */
    private CDF       myCDF;

    /**
     * The Attribute scope
     */
    private long scope;

    /**
     * The attribute id.<BR><BR>  
     *
     * <STRONG>Note</STRONG> that this only corresponds to the internal
     * CDF id upon opening.  If the any attributes have been deleted, then
     * the id here may be out of sync.  For this reason, all selection in 
     * the JNI code is done by name once the CDFObject has a valid name.
     */
    private long id;
    private long cdfID;

    /**
     * The attribute name
     */
    private String name;

    /**
     * The number of entries for this attribute.
     */
    private long    numEntries;

    /**
     * The maximum number of entries for all attributes (should be static)
     */
    private long maxEntryNumber;        // Not used now

    /**
     * The entries of this attribute.  The keys are the entry ids and the
     * values are the entries themselves. <P>
     *
     * Note that this must be a lookup table since the entry numbers
     * may not be contiguous, especially for variable attributes.
     */
    private Vector entries = null;
    

    /**
     * Create an attribute and its entry table. 
     *
     * @param myCDF the CDF object to which this attribute belongs
     */

    private Attribute(CDF myCDF) {
	this.myCDF = myCDF;
	entries = new Vector();
    }

    /**
     * Retrieve an existing attribute by id number from the given CDF.<BR><BR>
     *
     * Note:  The user should never have to call this directly as this is 
     * done when creating an instance of a CDF.
     *
     * @param myCDF CDF from which to retrieve the attribute
     * @param id Attribute id number to search for <P>
     * @return An attribute that corresponds to the passed-in attribute id  
     * @exception CDFException If an error has occurred in retrieving the
     *                         requested attribute.
     */
    protected final static Attribute retrieve(CDF myCDF, long id)
	throws CDFException
    {
synchronized(myCDF) {
	Attribute theAttr = new Attribute(myCDF);
	myCDF.addAttribute(theAttr);

	Vector cmds = new Vector();
	Vector item0 = new Vector();
	Vector item1 = new Vector();
        Vector itemA = new Vector();
	Vector itemB = new Vector();
        
        cmds.addElement(Long.valueOf(SELECT_));
           cmds.addElement(Long.valueOf(CDF_));
              itemA.addElement("cdfID");
              itemA.addElement("J");
           cmds.addElement(itemA);
           cmds.addElement(Long.valueOf(ATTR_));
              itemB.addElement("id");
              itemB.addElement("J");
           cmds.addElement(itemB);

	cmds.addElement(Long.valueOf(GET_));
	  cmds.addElement(Long.valueOf(ATTR_NAME_));
	    item0.addElement("name");
	    item0.addElement("Ljava/lang/String;");
	  cmds.addElement(item0);
	  cmds.addElement(Long.valueOf(ATTR_SCOPE_));
	    item1.addElement("scope");
	    item1.addElement("J");
	  cmds.addElement(item1);
	cmds.addElement(Long.valueOf(NULL_));

	theAttr.cdfID = myCDF.getID(); 
	theAttr.id = id;

	myCDF.executeCommand((CDFObject)theAttr, cmds);

	cmds.removeAllElements();
	item0.removeAllElements();
	item1.removeAllElements();
	itemA.removeAllElements();
	itemB.removeAllElements();

        cmds.addElement(Long.valueOf(SELECT_));
           cmds.addElement(Long.valueOf(CDF_));
              itemA.addElement("cdfID");
              itemA.addElement("J");
           cmds.addElement(itemA);
           cmds.addElement(Long.valueOf(ATTR_));
              itemB.addElement("id");
              itemB.addElement("J");
           cmds.addElement(itemB);

	cmds.addElement(Long.valueOf(GET_));
	  cmds.addElement(((theAttr.getScope() == GLOBAL_SCOPE) ? 
			   Long.valueOf(ATTR_NUMgENTRIES_) : 
			   Long.valueOf(ATTR_NUMzENTRIES_)));
	    item0.addElement("numEntries");
	    item0.addElement("J");
	  cmds.addElement(item0);
	  cmds.addElement(((theAttr.getScope() == GLOBAL_SCOPE) ?
			   Long.valueOf(ATTR_MAXgENTRY_) :
			   Long.valueOf(ATTR_MAXzENTRY_)));
	    item1.addElement("maxEntryNumber");
	    item1.addElement("J");
	  cmds.addElement(item1);
	cmds.addElement(Long.valueOf(NULL_));

	myCDF.executeCommand((CDFObject)theAttr, cmds);

        /*******************/
	/* get the entries */
        /*******************/
	long entryID = -1;
	long numRead = 0;
	Entry curEntry;
	if (theAttr.numEntries != 0) {
            if ((theAttr.getScope() == VARIABLE_SCOPE) && 
                (theAttr.numEntries > (theAttr.maxEntryNumber+1))) {
               /* A ivariable entry(ies) without a variable(s)... */
               theAttr.numEntries = theAttr.maxEntryNumber + 1;
            }
	    while (true) {
                entryID++;
		try {
		    curEntry = Entry.retrieve(theAttr, entryID);
		    //theAttr.entries.addElement(curEntry);
		    numRead++;
		    if (numRead == theAttr.numEntries) break;
		    if (numRead > theAttr.maxEntryNumber) 
			throw new CDFException(BAD_ENTRY_NUM);
		} catch (CDFException e) {
		    // Put a place holder in the entries vector
		    theAttr.entries.addElement(null);
		}
	    }
	}
	return theAttr;
    }
}

    /**
     * Creates a new attribute in the given CDF.  Attributes and attribute
     * entries are used to describe information about a CDF file and the
     * variables in the file.  Any number of attributes may be stored in
     * a CDF file. <P>
     *
     * The following example creates a global attribute called 'Project' 
     * and a variable attribute called 'VALIDMIN':
     * <PRE>
     *     Attribute project, validMin; 
     *
     *     project  = Attribute.create(cdf, "Project", GLOBAL_SCOPE);
     *     validMin = Attribute.create(cdf, "VALIDMIN", VARIABLE_SCOPE);
     * </PRE>
     *
     * @param myCDF the CDF object to which this attribute belongs <P>
     * @param name the name of the attribute to be created <P>
     * @param scope the attribute's scope - it should be either 
     *              GLOBAL_SCOPE or VARIABLE_SCOPE <P>
     *
     * @return Attribute an Attribute object in the CDF.
     * 
     * @exception CDFException if a problem occurred in creating an attribute
     */

    public static Attribute create(CDF myCDF, String name, long scope)
	throws CDFException {

synchronized(myCDF) {	
	if ((scope != GLOBAL_SCOPE) && (scope != VARIABLE_SCOPE)) {
	    throw new CDFException(BAD_SCOPE);
	}
        if (name == null || name.trim().length() == 0)
            throw new CDFException(BAD_ATTR_NAME);

	long ij = myCDF.getAttributeID(name);   

	if (ij != -1) {              // Attribute already exists
	    throw new CDFException(ATTR_EXISTS);
	}

	Vector cmds = new Vector();
	Vector item0 = new Vector();
	Vector item1 = new Vector();
	Vector item2 = new Vector();
	Vector itemA = new Vector();

	cmds.addElement(Long.valueOf(SELECT_));
          cmds.addElement(Long.valueOf(CDF_));
            itemA.addElement("cdfID");
            itemA.addElement("J");
          cmds.addElement(itemA);

	cmds.addElement(Long.valueOf(CREATE_));
	  cmds.addElement(Long.valueOf(ATTR_));             
	    item0.addElement("name");                   /* in */
	    item0.addElement("Ljava/lang/String;");
	  cmds.addElement(item0);
	    item1.addElement("scope");                  /* in */
	    item1.addElement("J");
	  cmds.addElement(item1);
	    item2.addElement("id");                     /* out */
	    item2.addElement("J");
	  cmds.addElement(item2);
	cmds.addElement(Long.valueOf(NULL_));

        Attribute theAttr = new Attribute(myCDF);
        theAttr.name = name;
        theAttr.scope = scope;
	theAttr.cdfID = myCDF.getID();

	theAttr.myCDF.executeCommand((CDFObject)theAttr, cmds);

        // Add the attribute just created to this CDF.
	myCDF.addAttribute(theAttr);      

        /******************************************************************/
	/* Put placeholders in the entry list for each var for variable   */ 
        /* attributes.  The number of entries MUST be equal to the number */
        /* of variables.                                                  */
        /******************************************************************/
	if (scope == VARIABLE_SCOPE)
	    for (int i=0; i < (int)theAttr.myCDF.getNumVars(); i++)
		theAttr.entries.addElement(null);
	return theAttr;
    }
}

    /**
     * Deletes this attribute.<BR><BR>
     *
     * <STRONG>Note:</STRONG> When an attribute is deleted all the
     * entries for attribute are deleted as well.  Also, all attributes
     * that follow the deleted attribute will be renumbered immediately
     * (their IDs will be decremented by one).  This can cause confusion
     * when using a loop to delete attributes.  The following is incorrect
     * and will result in every other attribute being deleted:
     *
     * <PRE>
     *     Vector attrs = cdf.getAttributes();
     *     int n = attrs.size();
     *     for (int i = 0 i &lt; n; i++)
     *         ((Attribute)attrs.getElementAt(i)).delete();
     * </PRE>
     *
     * <PRE>
     * Two possible workarounds are: <BR><BR>
     *     Vector attrs = cdf.getAttributes();
     *     int n = attrs.size();
     *     for (int i = n-1; i &gt;= 0; i--)
     *         ((Attribute)attrs.getElementAt(i)).delete();
     * 
     *     and
     *
     *     Vector attrs = cdf.getAttributes();
     *     int n = attrs.size();
     *     for (int i = 0 i &lt; n; i++)
     *         ((Attribute)attrs.getElementAt(0)).delete();
     * </PRE>
     *
     * @exception CDFException if there is a problem deleting the attribute
     */

    public synchronized void delete() throws CDFException {
	Vector cmds = new Vector();

	// Make sure that the Attribute id is sync'ed

        Vector itemA = new Vector();
	Vector itemB = new Vector();

        cmds.addElement(Long.valueOf(SELECT_));
          cmds.addElement(Long.valueOf(CDF_));
            itemA.addElement("cdfID");
            itemA.addElement("J");
          cmds.addElement(itemA);
          cmds.addElement(Long.valueOf(ATTR_));
            itemB.addElement("id");
            itemB.addElement("J");
          cmds.addElement(itemB);

	cmds.addElement(Long.valueOf(DELETE_));
	  cmds.addElement(Long.valueOf(ATTR_));
	cmds.addElement(Long.valueOf(NULL_));

        id = getID();
	cdfID = myCDF.getID();

	myCDF.executeCommand((CDFObject)this, cmds);

	// Make this is removed from the attribute list in the cdf
	myCDF.removeAttribute(this);
    }

    /////////////////////////////////////////////
    //                                         //
    //           Entry Handling                //
    //                                         //
    /////////////////////////////////////////////

    /**
     * Gets the attribute entry for the given entry number. <P> 
     *
     * The following example retrieves the first entry of the global 
     * attribute 'project'.  Please note that a global attribute can 
     * have multiple entries (whereas, a variable attribute has only 
     * one entry for a particular attribute), and attribute id starts 
     * at 0, not 1. 
     * <PRE>
     *     Entry  tEntry = project.getEntry(0L)
     * </PRE>
     *
     * @param entryID the entry number from which an attribute entry 
     *                is retrieved <P>
     * @return Entry the Entry object
     * @exception CDFException if an error occurred getting an entry (i.e. 
     *                         invalid entryID, no attribute entry for entryID)
     */

    public synchronized Entry getEntry(long entryID) throws CDFException {
	// This check is needed for blank global attributes
	if (entries.size() <= entryID) {
	    throw new CDFException(NO_SUCH_ENTRY);
	}

	// This check is needed for blank variable attributes which have 
	// a null object placeholder.
	if (entries.elementAt((int)entryID) == null) {
	    throw new CDFException(NO_SUCH_ENTRY);
	}

	return (Entry)entries.elementAt((int)entryID);

    }

    /**
     * Gets the attribute entry for the given variable.  <P>
     *
     * The following example retrieves the 'longitude' variable entry 
     * associate with the attribute 'validMin':
     * <PRE>
     *     vEntry = validMin.getEntry(longitude);
     * </PRE>
     *
     * @param var the variable from which an attribute entry is retrieved <P>
     * @return Entry the Entry object
     * @exception CDFException if an error occurred getting a variable
     *                         attribute entry (e.g. non-existent variable,
     *                         no attribute entry for this variable, etc.)
     */

    public synchronized Entry getEntry(Variable var) throws CDFException {

	if (scope != VARIABLE_SCOPE) {
	   throw new CDFException(BAD_SCOPE);
	}
	if (var.getID() < 0) {
	   throw new CDFException(NO_SUCH_VAR);
	}
	return this.getEntry(var.getID());

    }

    /**
     * Adds an attribute entry.  The Entry class uses this to establish 
     * the communication between the new entry and the attribute.
     *
     * @param newEntry the entry to be added <P>
     * @param id the entry id for the new entry being added 
     */
    protected synchronized final void addEntry(Entry newEntry, int id) {
	int eSize = entries.size();
	if (eSize > id) {

	    // Put the new one on the list
	    entries.insertElementAt(newEntry, id);

            // remove the placeholder.  Note that deleteEntry deletes
            // an entry and puts a null entry at the deleted location.
            
            entries.removeElementAt(id + 1);
	    
	} else {
	    if (scope == GLOBAL_SCOPE) {
                if (eSize < id) {
                    // must pad entries with null values
                    for (int i=0;i<(id - eSize);i++)
                        entries.addElement(null);
                }
	    }
	    entries.addElement(newEntry);
	}
    }

    /**
     * Removes the entry that is passed to this method.
     * The Entry class uses this to break the connection between
     * the Attribute and the Entry upon Entry deletion
     *
     * @param oldEntry the entry to be removed
     */
    protected synchronized final void removeEntry(Entry oldEntry) {
	int idx = entries.indexOf(oldEntry);

	// insert a placeholder if this is a variable attribute
	// or the if it is a global attribute and not the last one on the list
	if ((scope == VARIABLE_SCOPE) ||
	    ((scope == GLOBAL_SCOPE) && (idx != (entries.size() - 1)))) {
	    entries.insertElementAt(null, idx);
	    entries.removeElementAt(idx+1); // Remove the placeholder
	} else 
	    entries.removeElementAt(idx);  // Remove the placeholder

	
    }

    /**
     * The CDF class uses this method to remove entries for variables that 
     * have been deleted.
     *
     * @param id the id of the entry to be removed
     */
    protected synchronized final void removeEntry(int id) {
	entries.removeElementAt(id);
    }

    /**
     * The CDF class uses this method to put a placeholder on the list 
     * of entries when a new variable is added to the list of variables.  
     * For example, if there are 8 variables, then there must be 8 entries.
     */
    protected synchronized final void addNullEntry() {
	entries.addElement(null);
    }

    /**
     * Deletes an attribute entry for the given entry number.  <P>
     *
     * The following example deletes the first and second entries of 
     * the global attribute 'Project': 
     * <PRE>
     *     project.deleteEntry(0L);        
     *     project.deleteEntry(1L);        
     * </PRE> <P>
     *
     * The following example deletes the 'longitude' variable entry
     * associated with the attribute 'validMin': 
     * <PRE>
     *     validMin.deleteEntry(longitude.getID()); 
     * </PRE> 
     *
     * @param entryID the ID of the entry to be deleted <P>
     * @exception CDFException if there was a porblem deleting the entry
     */     

    public synchronized void deleteEntry(long entryID) throws CDFException {

	if (entryID < 0) {
	    throw new CDFException(BAD_ENTRY_NUM);
	}
	if (entries.elementAt((int)entryID) == null) {
	    throw new CDFException(NO_SUCH_ENTRY);
	}
	((Entry)entries.elementAt((int)entryID)).delete();

    }

    /**
     * Deletes the attribute entry for the given variable. <P>
     *
     * The following example deletes the 'longitude' variable entry
     * associated with the attribute 'validMin': 
     * <PRE>
     *     validMin.deleteEntry(longitude); 
     * </PRE>
     * 
     * @param var the variable from which the attribute entry is deleted <P>
     * @exception CDFException if there was a porblem deleting the entry
     */     

    public synchronized void deleteEntry(Variable var) throws CDFException {

	if (scope != VARIABLE_SCOPE) {
	   throw new CDFException(BAD_SCOPE);
	}
	if (var.getID() < 0) {
	   throw new CDFException(NO_SUCH_VAR);
	}
	this.deleteEntry(var.getID());

    }

    /**
     * Gets all the entries defined for this attribute.  A global attribute 
     * can have multiple entries.  Whereas, a variable attribute has
     * only one entry for a particular attribute.
     *
     * @return all the entries (one or more) defined for a global attribute
     *         or all variable entries for this attribute. The number of
     *         returned entries vector for a variable attribute may be less
     *         than the number of variables, as not all variables have an entry
     *         for the attribute. 
     *         
     * @exception CDFException if there was a porblem getting the entries
     */
    public synchronized Vector getEntries() throws CDFException {
        Vector cmds = new Vector();
        Vector item1 = new Vector();
        Vector itemA = new Vector();
        Vector itemB = new Vector();

        cmds.addElement(Long.valueOf(SELECT_));
           cmds.addElement(Long.valueOf(CDF_));
              itemA.addElement("cdfID");
              itemA.addElement("J");
           cmds.addElement(itemA);
           cmds.addElement(Long.valueOf(ATTR_));
              itemB.addElement("id");
              itemB.addElement("J");
           cmds.addElement(itemB);

        cmds.addElement(Long.valueOf(GET_));
          cmds.addElement(((this.getScope() == GLOBAL_SCOPE) ?
                           Long.valueOf(ATTR_MAXgENTRY_) :
                           Long.valueOf(ATTR_MAXzENTRY_)));
            item1.addElement("maxEntryNumber");
            item1.addElement("J");
          cmds.addElement(item1);
        cmds.addElement(Long.valueOf(NULL_));

        myCDF.executeCommand((CDFObject)this, cmds);

	entries.setSize((int)maxEntryNumber+1); 
	return entries;

    }
    
    /**
     * Gets the entry id for the given entry.
     *
     * @param entry the entry from which an entry id is retrieved <P> 
     * @return the entry id for the given entry
     */    
    public synchronized long getEntryID(Entry entry) {

	return (long)entries.indexOf(entry);

    }


    /**
     * Renames the current attribute.
     *
     * @param newName the new attribute name <P>
     * @exception CDFException if there was a problem renaming the attribute 
     */
    public synchronized void rename(String newName) throws CDFException {

	Vector cmds = new Vector();
	Vector item0 = new Vector();
        Vector itemA = new Vector();
	Vector itemB = new Vector();

        cmds.addElement(Long.valueOf(SELECT_));
          cmds.addElement(Long.valueOf(CDF_));
            itemA.addElement("cdfID");
            itemA.addElement("J");
          cmds.addElement(itemA);
          cmds.addElement(Long.valueOf(ATTR_));
            itemB.addElement("id");
            itemB.addElement("J");
          cmds.addElement(itemB);

	cmds.addElement(Long.valueOf(PUT_));
	  cmds.addElement(Long.valueOf(ATTR_NAME_));
	    item0.addElement("name");
	    item0.addElement("Ljava/lang/String;");
	  cmds.addElement(item0);
	cmds.addElement(Long.valueOf(NULL_));

	name = newName;
	id = getID();
	cdfID = myCDF.getID();

//      select();
	myCDF.executeCommand((CDFObject)this, cmds);

    }

    /**
     * Selects this attribute.  There is no need to build the entire
     * select cmd vector since this is handled in the JNI native method
     * in cdfNativeLibrary.c.
     *
     * @exception CDFException if there was a problem in selecting the
     *            current attribute
     */
    protected final void select() throws CDFException {
	Vector cmds = new Vector();
	// Make sure that the id is up to date
	cmds.addElement(Long.valueOf(NULL_)); // Select this attribute

        id = getID();
	myCDF.executeCommand((CDFObject)this, cmds);

    }

    /**
     * Gets the number of entries in this attribute.
     *
     * @return the number of entries in this attribute
     */
    public synchronized long getNumEntries() {

	int count = 0;
	for (int i = 0;i<entries.size();i++)
	    if (entries.elementAt(i) != null)
		count++;

	return count;
    }
    
    /**
     * Gets the largest Entry number for this attribute.
     *
     * @return the largest Entry number for this attribute 
     */
    public synchronized long getMaxEntryNumber() {

	return entries.size();

    }

    /**
     * Gets the attribute ID of this attribute.
     *
     * @return the attribute id of this attribute
     */
    public synchronized long getID() {
	return id;
//	return myCDF.getAttributeID(name);
    }

    /**
     * Gets the CDF object to which this attribute belongs. 
     *
     * @return the CDF object to which this attribute belongs
     */
    public synchronized CDF getMyCDF(){
	return myCDF;
    }

    /**
     * Gets the name of this attribute. 
     *
     * @return the name of this attribute
     */
    public synchronized String getName() {      
	return this.name;
    }

    /**
     * Gets the name of this attribute.
     *
     * @return the name of this attribute
     */
    public String toString() {
	return this.name;
    }

    /**
     * Gets the scope of this attribute.  
     *
     * @return If the attribute is a global attribute, GLOBAL_SCOPE is 
     *         returned.  If the attribute is a variable attribute, 
     *         VARIABLE_SCOPE is returned.
     */
    public synchronized long getScope() {
	return scope;
    }

}
