// $Id: Attribute.java,v 1.4 2002-05-29 18:31:32 steve Exp $
/*
* Copyright 1997-2000 Unidata Program Center/University Corporation for
* Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
* support@unidata.ucar.edu.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package ucar.netcdf;
import java.io.Serializable;
import java.lang.String;
import java.lang.reflect.Array;
/**
* Attributes are similar to "IndexedProperties" in the lingo
* of java beans. They have a name, type and an array of
* values. The array is often of length 1, degenerating into
* a simple property. The array should never be length 0.
* An Attribute object is used to contain netcdf "metadata",
* like units of a measurable quantity or its valid range.
* <p>
* These attributes have fixed values over their lifetime;
* no setValue() methods are provided.
* <p>
* Instances which have same name and same value elements are equal.
* We override hashCode() and equals() to be consistent with
* this semantic.
*
* @author $Author: steve $
* @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:32 $
*/
/*
* Implementation Notes:
* <p>
* We factor the value into two cases, a String value and
* an array of (numeric) primitive. Things would have been
* completely symmetric (eliminating this layer) if we just copied
* strings into array of char and copied out to string. Seems like a
* waste when given an immutable object to start with.
* <p>
* The members are immutable. The value is built by copying
* in. So, we can safely use the default clone() method.
*/
public class
Attribute
implements Named, Serializable, Cloneable
{
/**
* Construct simple numeric attribute.
*
* @param name String which is to be the name of this Attribute
* @param value A Number to be the Attribute value
*/
public
Attribute(String name, Number value)
{
final Class componentType = primitiveClass(value);
if(!ProtoVariable.checkComponentType(componentType))
throw new IllegalArgumentException("Invalid Type: "
+ componentType);
this.name = name;
this.value = new NumericAttrVal(value, componentType);
}
/**
* Construct simple numeric attribute.
*
* @param name String which is to be the name of this Attribute
* @param value A double to be the Attribute value
*/
public
Attribute(String name, double value)
{
this.name = name;
final double [] darray = {value};
this.value = new NumericAttrVal(darray);
}
/**
* Construct a string valued attribute.
* This will be seen to have component type Character.TYPE
*
* @param name String which is to be the name of this Attribute
* @param theValue The value
*/
public
Attribute(String name, String theValue)
{
this.name = name;
this.value = new StringAttrVal(theValue);
}
/**
* Construct an array valued Attribute.
* Not often used.
*
* @param name String which is to be the name of this Attribute
* @param theValue The value, an array of primitives. The primitive
* type must be netcdf encodeable.
*/
public
Attribute(String name, Object theValue)
{
/*
* check that arg is an array
*/
final Class aClass = theValue.getClass();
if(!aClass.isArray())
throw new IllegalArgumentException("Not an Array");
// else
/*
* Check that the array componentType is netcdf encodeable.
*/
final Class componentType = aClass.getComponentType();
if(!ProtoVariable.checkComponentType(componentType))
throw new IllegalArgumentException("Invalid Type: "
+ componentType);
// else
this.name = name;
if(componentType == Character.TYPE)
{
this.value = new StringAttrVal((char [])theValue);
}
else
{
/* make a private copy of the array */
this.value = new NumericAttrVal(arrayClone(theValue));
}
}
/* Begin Overrides */
/**
* Instances which have same name and same value elements are equal.
* Overrides Object.equals() to be consistent with
* this semantic.
*
* @return the hash code value for this Attribute
*/
public int
hashCode()
{
return (name.hashCode() ^ value.hashCode());
}
/**
* Instances which have same name and same value elements are equal.
* Overrides Object.equals() to be consistent with
* this semantic.
* TODO: test me.
*/
public boolean
equals(Object oo)
{
if(this == oo) return true;
if(oo instanceof Attribute)
{
final Attribute aa = (Attribute) oo;
if(name.equals(aa.getName()))
{
return value.equals(aa.value);
}
}
return false;
}
/**
* @return a string representation of this
*/
public String
toString() {
StringBuffer buf = new StringBuffer();
toCdl(buf);
return buf.toString();
}
/* End Overrides */
/**
* Returns the name of this Attribute.
* @return String which identifies this Attribute.
*/
public final
String getName()
{
return name;
}
/**
* Retrieve the value in its most general form.
* @return Object which is either a java.lang.String or
* a 1-dimensional array of primitives.
* @see Attribute#isString
*/
public Object
getValue()
{ return value.getValue(); }
/**
* Retrieve String value.
* @return String if this is a String valued attribute.
* @throws ClassCastException if this is not String valued.
* @see Attribute#isString
*/
public String
getStringValue()
{ return (String) value.getValue(); }
/**
* Retrieve indexed value.
* @param index int which is the index into the value array.
* @return Number <code>value[index]</code>
*/
public Object
get(int index)
{ return value.get(index); }
/**
* Retrieve indexed numeric value.
* @param index int which is the index into the value array.
* @return Number <code>value[index]</code>
*/
public Number
getNumericValue(int index)
{ return value.getNumericValue(index); }
/**
* Retrieve simple numeric value.
* Equivalent to <code>getNumericValue(0)</code>
* @return Number the first element of the value array
*/
public Number
getNumericValue()
{ return value.getNumericValue(); }
/**
* If the value is an instance of String, return <code>true</code>
* otherwise returns <code>false</code>
* @return boolean value instanceof String
*/
public boolean
isString()
{
return value instanceof StringAttrVal;
}
/**
* If the value represents an array type, returns the Class
* object representing the component type of the array; otherwise
* returns null.
* @return Class the component type
* @see java.lang.Class#getComponentType
*/
public Class
getComponentType()
{ return value.getComponentType(); }
/**
* If the value represents an array type, returns the length
* of the value array;
* otherwise return String.length of the String value.
* @return int length of the value
*/
public int
getLength()
{ return value.getLength(); }
/**
* Format as CDL.
* @param buf StringBuffer into which to write
*/
public void
toCdl(StringBuffer buf)
{
buf.append(":");
buf.append(this.getName());
buf.append(" = ");
value.toCdl(buf);
buf.append(" ;");
}
/**
* Why isn't this in java.lang.reflect.Array as native?
*/
static Object
arrayClone(Object src)
{
final int length = Array.getLength(src);
Object aa = Array.newInstance(src.getClass().getComponentType(),
length);
System.arraycopy(src, 0, aa, 0, length);
return aa;
}
/**
* Use reflection to find out the TYPE (primitive class)
* corresponding to a Number.
*/
static Class
primitiveClass(Number nn)
{
try {
return (Class) nn.getClass().getDeclaredField("TYPE").
get(nn);
}
catch (NoSuchFieldException ee)
{
// this shouldn't happen, since Numbers have TYPE
throw new Error();
}
catch (IllegalAccessException ee)
{
// this shouldn't happen, since TYPE is public
throw new Error();
}
}
/**
* @serial
*/
private final String name;
/**
* @serial
*/
private final AttrVal value;
}
abstract class
AttrVal
implements Serializable, Cloneable
{
abstract Object
getValue();
abstract Object
get(int index);
abstract Number
getNumericValue(int index);
abstract Number
getNumericValue();
abstract Class
getComponentType();
abstract int
getLength();
abstract void
toCdl(StringBuffer buf);
}
final class
NumericAttrVal
extends AttrVal
{
NumericAttrVal(Number nn, Class componentType)
{
data = Array.newInstance(componentType, 1);
Array.set(data, 0, nn);
}
NumericAttrVal(Object data)
{
/* sanity checking and copy in semantics done in caller */
this.data = data;
}
public int
hashCode()
{
int h = 0;
final int len = getLength();
for(int ii = 0; ii < len; ii++)
h = (h * 13) + get(ii).hashCode();
return h;
}
public boolean
equals(Object oo)
{
if(oo instanceof NumericAttrVal)
{
final NumericAttrVal aa = (NumericAttrVal) oo;
if(getComponentType() == aa.getComponentType())
{
// potentially equal
final int length = getLength();
if(length == aa.getLength())
{
for(int ii = 0; ii < length; ii++)
if(!get(ii).equals(aa.get(ii)))
return false;
return true;
}
}
}
return false;
}
Object
getValue()
{
return Attribute.arrayClone(data);
}
Object
get(int index)
{
return Array.get(data, index);
}
Number
getNumericValue(int index)
{
return (Number) Array.get(data, index);
}
Number
getNumericValue() {
return this.getNumericValue(0);
}
Class
getComponentType()
{
return data.getClass().getComponentType();
}
int
getLength()
{
return Array.getLength(data);
}
void
toCdl(StringBuffer buf)
{
final int last = Array.getLength(data) - 1;
for(int ii = 0; ii <= last; ii++) {
buf.append(Array.get(data, ii));
if(ii < last)
buf.append(", ");
}
}
/**
* An array of primitives
*/
private final Object data;
}
final class StringAttrVal extends AttrVal {
StringAttrVal(String str) {
if (str.length() == 0) { // empty string test added -dwd
data = "";
return;
}
if (str.charAt(str.length()-1) == 0) { // trailing null test - dwd
data = str.substring(0, str.length()-1);
} else
data = str;
}
StringAttrVal(char [] charArray) {
int len = charArray.length;
if (len == 0) { // empty string test added - dwd
data="";
return;
}
if (charArray[len-1] == 0) { // trailing null test - dwd
data = new String(charArray, 0, len-1);
} else
data = new String(charArray);
}
public int
hashCode()
{
return data.hashCode();
}
public boolean
equals(Object oo)
{
if(oo instanceof StringAttrVal)
{
final String other = (String)((StringAttrVal)oo).data;
if(data == other)
return true;
// else
return data.equals(other);
}
return false;
}
Object
getValue()
{
return data;
}
Object
get(int index)
{
return new Character(data.charAt(index));
}
Number
getNumericValue(int index)
{
return new Integer(data.charAt(index));
}
Number
getNumericValue() {
return this.getNumericValue(0);
}
Class
getComponentType()
{
return Character.TYPE;
}
int
getLength()
{
return data.length();
}
void
toCdl(StringBuffer buf)
{
buf.append("\"");
buf.append(data);
buf.append("\"");
}
private final String data;
}