//
// QuantityDBImpl.java
//
/*
* Copyright 1998, University Corporation for Atmospheric Research
* See file LICENSE for copying and redistribution conditions.
*
* $Id: QuantityDBImpl.java,v 1.7 2006-02-13 22:30:07 curtis Exp $
*/
package visad.data.netcdf;
import java.io.Serializable;
import java.text.CollationKey;
import java.text.Collator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import visad.DerivedUnit;
import visad.PromiscuousUnit;
import visad.QuantityDimension;
import visad.RealType;
import visad.TypeException;
import visad.Unit;
import visad.UnitException;
import visad.VisADException;
import visad.data.units.ParseException;
import visad.data.units.Parser;
/**
* Provides support for a database of quantities.
*
* Instances are modifiable.
*
* @author Steven R. Emmerson
*/
public class
QuantityDBImpl
extends QuantityDB
implements Serializable
{
/**
* The set of quantities.
*/
private final SortedSet quantitySet = new TreeSet();
/**
* The (Name) -> Quantity map.
*/
private final TreeMap nameMap = new TreeMap();
/**
* The (Unit, Name) -> Quantity map (major key: unit).
*/
private final TreeMap unitMap = new TreeMap();
/**
* The minimum name value.
*/
private static final String minName = "";
/**
* The maximum name value.
*/
private static final String maxName = "zzz";
/**
* The minimum unit value.
*/
private static final Unit minUnit = new DerivedUnit();
/**
* The maximum unit value.
*/
private static final Unit maxUnit = new DerivedUnit();
/**
* The quantity database to search after this one.
*/
private /*final*/ QuantityDB nextDB;
/**
* Constructs with another quantity database as the successor database.
*
* @param nextDB The quantity database to search after this one.
* May be <code>null</code>.
*/
public
QuantityDBImpl(QuantityDB nextDB)
{
this.nextDB = nextDB;
}
/**
* Adds the given quantities and aliases to the database.
*
* @param definitions New quantities and their definitions.
* <code>definitions[2*i]</code> contains the
* name (e.g. "speed") of the quantity whose
* preferred unit specification (e.g. "m/s") is
* in <code>definitions[2*i+1]</code>.
* @param aliases Aliases for quantities. <code>aliases[2*i]
* </code> contains the alias for the quantity
* named in <code>aliases[2*i+1]</code>.
* @return The database resulting from the addition. May
* or may not be the original object.
* @throws ParseException A unit specification couldn't be parsed.
* @throws TypeException An incompatible version of the quantity already
* exists.
* @throws VisADException Couldn't create necessary VisAD object.
*/
public QuantityDB
add(String[] definitions, String[] aliases)
throws ParseException, TypeException, VisADException
{
for (int i = 0; i < definitions.length; i += 2)
add(definitions[i], definitions[i+1]);
for (int i = 0; i < aliases.length; i += 2)
add(aliases[i], get(aliases[i+1]));
return this;
}
/**
* Adds a quantity to the database under a given name.
*
* @param name The name of the quantity (e.g. "length").
* May be an alias for the quantity.
* @param quantity The quantity.
* @throws VisADException Couldn't create necessary VisAD object.
*/
public synchronized void
add(String name, Quantity quantity)
throws VisADException
{
if (name == null || quantity == null)
throw new VisADException("add(): null argument");
Unit unit = quantity.getDefaultUnit();
quantitySet.add(quantity);
nameMap.put(new NameKey(name), quantity);
unitMap.put(new UnitKey(unit, quantity.getName()), quantity);
}
/**
* Adds given Quantity-s to the database.
*
* @param quantities The quantities to be added. The quantity will
* be added under it own name.
* @return The database resulting from the addition. May
* or may not be the original object.
* @throws VisADException Couldn't create necessary VisAD object.
*/
public QuantityDB
add(Quantity[] quantities)
throws VisADException
{
for (int i = 0; i < quantities.length; i++)
{
Quantity quantity = quantities[i];
add(quantity);
}
return this;
}
/**
* Adds a quantity to the database given a name and a display unit
* specification.
*
* @param name The name of the quantity (e.g. "length").
* @param unitSpec The preferred display unit for the
* quantity (e.g. "feet").
* @throws ParseException Couldn't decode unit specification.
* @throws TypeException Incompatible ScalarType of same name already
* exists.
* @throws VisADException Couldn't create necessary VisAD object.
*/
protected synchronized void
add(String name, String unitSpec)
throws ParseException, TypeException, VisADException
{
try
{
add(name, new Quantity(name, unitSpec));
}
catch (VisADException e)
{
if (!(e instanceof TypeException))
throw e;
RealType realType = RealType.getRealTypeByName(name);
if (realType == null ||
!Unit.canConvert(
realType.getDefaultUnit(), Parser.parse(unitSpec)))
throw (TypeException)e;
}
}
/**
* Provides support for iterating over the database.
*/
protected abstract class
Iterator
implements java.util.Iterator
{
/**
* The private iterator.
*/
protected java.util.Iterator iterator;
/**
* Whether or not we can switch to the other database.
*/
private boolean canSwitch = nextDB != null;
/*
* Returns <code>true</code> if <code>next</code> will return an object.
* @return <code>true</code> if and only if <code>next()
* </code> will return a Quantity.
*/
public boolean
hasNext()
{
boolean have = iterator.hasNext();
if (!have && doSwitch())
have = hasNext();
return have;
}
protected abstract Object
nextObject();
/*
* Returns the next thing in the database.
* @return The next thing in the database if and only
* if a prior <code>hasNext()</code> did or would
* have returned <code>true</code>; otherwise
* throws an exception.
* @throws NoSuchElementException No more things in the database.
*/
public Object
next()
{
Object object;
try
{
object = nextObject();
}
catch (NoSuchElementException e)
{
if (!doSwitch())
throw e;
object = next();
}
return object;
}
/**
* Gets the iterator for the successor database.
*/
protected abstract java.util.Iterator
nextIterator();
/**
* Switchs to the other database.
* @return <code>true</code> if an only if the other
* database exists and this is the first switch
* to it.
*/
protected boolean
doSwitch()
{
boolean goodSwitch;
if (!canSwitch)
{
goodSwitch = false;
}
else
{
iterator = nextIterator();
canSwitch = false;
goodSwitch = true;
}
return goodSwitch;
}
/**
* Remove the element returned by the last <code>next()</code>.
* @throws UnsupportedOperationException
Operation not supported.
*/
public void
remove()
throws UnsupportedOperationException
{
throw new UnsupportedOperationException(
"remove(): Can't remove elements from quantity database");
}
}
/**
* Provides support for iterating over the quantities in the database.
*/
protected class
QuantityIterator
extends Iterator
{
/**
* Constructs.
*/
protected
QuantityIterator()
{
iterator = quantitySet.iterator();
}
/**
* Returns the next quantity of the iterator.
*/
protected Object
nextObject()
{
return iterator.next();
}
/**
* Returns the iterator for the successor database.
*/
protected java.util.Iterator
nextIterator()
{
return nextDB.quantityIterator();
}
}
/**
* Provides support for iterating over the names in the database.
* A name can only appear once in the database.
*/
protected class
NameIterator
extends Iterator
{
/**
* Constructs.
*/
protected
NameIterator()
{
iterator = nameMap.keySet().iterator();
}
/**
* Returns the next name in the iterator.
*/
protected Object
nextObject()
{
return ((NameKey)iterator.next()).getName();
}
/**
* Gets the iterator for the successor database.
*/
protected java.util.Iterator
nextIterator()
{
return nextDB.nameIterator();
}
}
/**
* Returns an iterator of the quantities in the database.
* @return An iterator of the quantities in the database.
*/
public java.util.Iterator
quantityIterator()
{
return new QuantityIterator();
}
/**
* Returns an iterator of the names in the database.
* @return An iterator of the names in the database.
*/
public java.util.Iterator
nameIterator()
{
return new NameIterator();
}
/**
* Returns the quantity in the database whose name matches a
* given name.
*
* @param name The name of the quantity.
* @return The quantity in the loal database with name
* <code>name</code>.
*/
public synchronized Quantity
get(String name)
{
Quantity quantity = (Quantity)nameMap.get(new NameKey(name));
return quantity != null
? quantity
: nextDB == null
? null
: nextDB.get(name);
}
/**
* Returns all quantities in the database whose default unit is
* convertible with a given unit.
*
* @param unit The unit of the quantity.
* @return The quantities in the database whose unit is
* convertible with <code>unit</code>.
*/
public synchronized Quantity[]
get(Unit unit)
{
Quantity[] myQuantities = (Quantity[])unitMap.subMap
(new UnitKey(unit, minName), new UnitKey(unit, maxName))
.values().toArray(new Quantity[0]);
Quantity[] nextQuantities = nextDB == null
? new Quantity[] {}
: nextDB.get(unit);
Quantity[] quantities =
new Quantity[myQuantities.length + nextQuantities.length];
System.arraycopy(myQuantities, 0, quantities, 0, myQuantities.length);
System.arraycopy(nextQuantities, 0, quantities, myQuantities.length,
nextQuantities.length);
myQuantities = null;
nextQuantities = null;
return quantities;
}
/**
* Provides support for keys to the name map.
*
* Immutable.
*/
protected static class
NameKey
implements Serializable, Comparable
{
/**
* The name of the quantity.
*/
private final String name;
/**
* The Collator for the name.
*/
private static final Collator collator;
/**
* The comparison value for the name of the quantity.
*/
private final CollationKey nameCookie;
static
{
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
/**
* Constructs from the name of a quantity.
*
* @param name The name of the quantity.
*/
protected NameKey(String name)
{
this.name = name;
nameCookie = collator.getCollationKey(name);
}
/**
* Compare this key to another.
*/
public int compareTo(Object obj)
throws ClassCastException
{
return nameCookie.compareTo(((NameKey)obj).nameCookie);
}
/**
* Returns the name of the quantity.
*/
public String
getName()
{
return name;
}
}
/**
* Provides support for keys to the unit map.
*
* Immutable.
*/
protected static final class
UnitKey
extends NameKey
{
/**
* The default unit of the quantity.
*/
protected final Unit unit;
/**
* Constructs from the unit of a quantity and its name.
*
* @param unit The default unit of the quantity.
* @param name The name of the quantity.
*/
protected UnitKey(Unit unit, String name)
{
super(name);
this.unit = unit;
}
/**
* Compare this key to another (unit first).
*/
public int compareTo(Object obj)
throws ClassCastException
{
UnitKey that = (UnitKey)obj;
int i = compare(this.unit, that.unit);
return i != 0
? i
: super.compareTo(that);
}
/**
* Compare one Unit to another.
*/
private int compare(Unit a, Unit b)
throws ClassCastException
{
int comparison;
if (a instanceof PromiscuousUnit || b instanceof PromiscuousUnit)
{
comparison = 0;
}
else
if (a == null || b == null)
{
comparison = a == null && b == null
? 0
: a == null
? -1
: 1;
}
else
{
try
{
comparison = (a == b)
? 0
: (a == minUnit || b == maxUnit)
? -1
: (a == maxUnit || b == minUnit)
? 1
: new QuantityDimension(a).compareTo
(new QuantityDimension(b));
}
catch (UnitException e)
{
throw new ClassCastException(e.getMessage());
}
}
return comparison;
}
}
}