ucar.nc2.Dimension Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.nc2;
import ucar.nc2.util.Indent;
import java.util.Formatter;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
/**
* A Dimension is used to define the array shape of a Variable.
* A Variable can be thought of as a sampled function with Domain its Dimensions.
* A Dimension may be shared among Variables, which provides a simple yet powerful way of associating Variables.
* When a Dimension is shared, it has a unique name within its Group.
* It may have a coordinate Variable, which gives each index a coordinate value.
* A private Dimension cannot have a coordinate Variable, so use shared dimensions with coordinates when possible.
* The Dimension length must be > 0, except for an unlimited dimension which may have length = 0, and a vlen
* Dimension which has a length known only when the variable is read.
*
*
* Immutable if setImmutable() was called, except for an Unlimited Dimension, whose size can change.
*
* Note: this class has a natural ordering that is inconsistent with equals.
*
* TODO Dimension will be immutable in 6.
* TODO Dimension will not extend CDMNode in 6.
* TODO Dimension will not have a reference to its owning Group in 6.
* TODO Dimension.getFullName() will not exist in 6.
*
* @author caron
*/
public class Dimension extends CDMNode implements Comparable {
/** A variable-length dimension: the length is not known until the data is read. */
public static final Dimension VLEN =
Dimension.builder().setName("*").setIsVariableLength(true).build().setImmutable();
/**
* Make a space-delineated String from a list of Dimension names.
* Inverse of makeDimensionsList().
*
* @return space-delineated String of Dimension names.
* @deprecated use Dimensions.makeDimensionsString()
*/
@Deprecated
public static String makeDimensionsString(List dimensions) {
if (dimensions == null)
return "";
Formatter buf = new Formatter();
for (int i = 0; i < dimensions.size(); i++) {
Dimension myd = dimensions.get(i);
String dimName = myd.getShortName();
if (i != 0)
buf.format(" ");
if (myd.isVariableLength()) {
buf.format("*");
} else if (myd.isShared()) {
buf.format("%s", dimName);
} else {
// if (dimName != null) // LOOK losing anon dim name
// buf.format("%s=", dimName);
buf.format("%d", myd.getLength());
}
}
return buf.toString();
}
/**
* Create a dimension list using the dimensions names. The dimension is searched for recursively in the parent groups,
* so it must already have been added.
* Inverse of makeDimensionsString().
*
* @param parentGroup containing group, may not be null
* @param dimString : whitespace separated list of dimension names, or '*' for Dimension.UNKNOWN, or number for anon
* dimension. null or empty String is a scalar.
* @return list of dimensions
* @throws IllegalArgumentException if cant find dimension or parse error.
* @deprecated use Group.makeDimensionsList()
*/
@Deprecated
public static List makeDimensionsList(Group parentGroup, String dimString)
throws IllegalArgumentException {
List newDimensions = new ArrayList<>();
if (dimString == null) // scalar
return newDimensions; // empty list
dimString = dimString.trim();
if (dimString.isEmpty()) // scalar
return newDimensions; // empty list
StringTokenizer stoke = new StringTokenizer(dimString);
while (stoke.hasMoreTokens()) {
String dimName = stoke.nextToken();
Dimension d;
if (dimName.equals("*"))
d = Dimension.VLEN;
else
d = parentGroup.findDimension(dimName);
if (d == null) {
// if numeric - then its anonymous dimension
try {
int len = Integer.parseInt(dimName);
d = Dimension.builder().setLength(len).setIsShared(false).build();
} catch (Exception e) {
throw new IllegalArgumentException("Dimension " + dimName + " does not exist");
}
}
newDimensions.add(d);
}
return newDimensions;
}
/**
* Create a dimension list using the dimensions names.
* Inverse of makeDimensionsString().
*
* @param dimensions list of possible dimensions, may not be null
* @param dimString : whitespace separated list of dimension names, or '*' for Dimension.UNKNOWN, or number for anon
* dimension. null or empty String is a scalar.
* @return list of dimensions
* @throws IllegalArgumentException if cant find dimension or parse error.
* @deprecated use Dimensions.makeDimensionsList()
*/
@Deprecated
public static List makeDimensionsList(List dimensions, String dimString) {
List newDimensions = new ArrayList<>();
if (dimString == null) // scalar
return newDimensions; // empty list
dimString = dimString.trim();
if (dimString.isEmpty()) // scalar
return newDimensions; // empty list
StringTokenizer stoke = new StringTokenizer(dimString);
while (stoke.hasMoreTokens()) {
String dimName = stoke.nextToken();
Dimension dim;
if (dimName.equals("*"))
dim = Dimension.VLEN;
else
dim = dimensions.stream().filter(d -> d.getShortName().equals(dimName)).findFirst().orElse(null);
if (dim == null) {
// if numeric - then its anonymous dimension
try {
int len = Integer.parseInt(dimName);
dim = Dimension.builder().setLength(len).setIsShared(false).build();
} catch (Exception e) {
throw new IllegalArgumentException("Dimension " + dimName + " does not exist");
}
}
newDimensions.add(dim);
}
return newDimensions;
}
/** @deprecated use Dimensions.makeDimensionsAnon() */
@Deprecated
public static List makeDimensionsAnon(int[] shape) {
List newDimensions = new ArrayList<>();
if ((shape == null) || (shape.length == 0)) { // scalar
return newDimensions;
}
for (int len : shape)
newDimensions.add(Dimension.builder().setLength(len).setIsShared(false).build());
return newDimensions;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TODO make these final in 6.
private boolean isUnlimited;
private boolean isVariableLength;
private boolean isShared; // shared means its in a group dimension list.
private int length;
private Dimension(Builder builder) {
super(builder.shortName);
setParentGroup(builder.parent);
this.isShared = builder.isShared;
this.isVariableLength = builder.isVariableLength;
this.isUnlimited = builder.isUnlimited;
this.length = builder.length;
}
/** Turn into a mutable Builder, use toBuilder().build() to make a copy. */
public Builder toBuilder() {
return builder().setName(this.shortName).setGroup(this.getGroup()).setIsUnlimited(this.isUnlimited)
.setIsVariableLength(this.isVariableLength).setIsShared(this.isShared).setLength(this.length);
}
/** Get the length of the Dimension. */
public int getLength() {
return length;
}
/** Get the name of the Dimension. Same as getShortName. Not deprecated. */
public String getName() {
return this.shortName;
}
/** Get the name of the Dimension. */
public String getShortName() {
return this.getName();
}
/**
* If this is a NetCDF unlimited dimension. The length might increase between invocations,
* but it remains fixed for the lifetime of the NetcdfFile.
* If you modify the file in a separate process, you must close and reopen the file.
*
* @return if its an "unlimited" Dimension
*/
public boolean isUnlimited() {
return isUnlimited;
}
/**
* If variable length, then the length is unknown until the data is read.
*
* @return if its a "variable length" Dimension.
*/
public boolean isVariableLength() {
return isVariableLength;
}
/**
* If this Dimension is shared, or is private to a Variable.
* All Dimensions in NetcdfFile.getDimensions() or Group.getDimensions() are shared.
* Dimensions in the Variable.getDimensions() may be shared or private.
*
* @return if its a "shared" Dimension.
*/
public boolean isShared() {
return isShared;
}
/**
* Get the Group that owns this Dimension.
*
* @return owning group or null if !isShared
* @deprecated Do not use.
*/
@Deprecated
public Group getGroup() {
return getParentGroup();
}
/** @deprecated Do not use. */
@Deprecated
public String makeFullName() {
return super.getFullName();
}
@Override
public boolean equals(Object oo) {
if (this == oo)
return true;
if (!(oo instanceof Dimension))
return false;
Dimension other = (Dimension) oo;
Group g = getGroup();
if ((g != null) && !g.equals(other.getGroup())) // TODO remove group in 6
return false;
if ((getShortName() == null) && (other.getShortName() != null))
return false;
if ((getShortName() != null) && !getShortName().equals(other.getShortName()))
return false;
return (getLength() == other.getLength()) && (isUnlimited() == other.isUnlimited())
&& (isVariableLength() == other.isVariableLength()) && (isShared() == other.isShared());
}
/**
* Override Object.hashCode() to implement equals.
*/
@Override
public int hashCode() {
int result = 17;
Group g = getGroup(); // TODO remove group in 6
if (g != null)
result += 37 * result + g.hashCode();
if (null != getShortName())
result += 37 * result + getShortName().hashCode();
result += 37 * result + getLength();
result += 37 * result + (isUnlimited() ? 0 : 1);
result += 37 * result + (isVariableLength() ? 0 : 1);
result += 37 * result + (isShared() ? 0 : 1);
return result;
}
@Override
public String toString() {
return writeCDL(false);
}
/**
* Dimensions with the same name are equal. This method is inconsistent with equals()!
*
* @param odim compare to this Dimension
* @return 0, 1, or -1
*/
public int compareTo(Dimension odim) {
String name = getShortName();
return name.compareTo(odim.getShortName());
}
/**
* CDL representation.
*
* @param strict if true, write in strict adherence to CDL definition.
* @return CDL representation.
* @deprecated use CDLWriter
*/
@Deprecated
public String writeCDL(boolean strict) {
Formatter f = new Formatter();
writeCDL(f, new Indent(2), strict);
return f.toString();
}
/** @deprecated use CDLWriter */
@Deprecated
void writeCDL(Formatter out, Indent indent, boolean strict) {
String name = strict ? NetcdfFiles.makeValidCDLName(getShortName()) : getShortName();
out.format("%s%s", indent, name);
if (isUnlimited())
out.format(" = UNLIMITED; // (%d currently)", getLength());
else if (isVariableLength())
out.format(" = UNKNOWN;");
else
out.format(" = %d;", getLength());
}
/**
* Constructor
*
* @param name name must be unique within group
* @param length length of Dimension
*/
public Dimension(String name, int length) {
this(name, length, true, false, false);
}
/**
* Constructor
*
* @param name name must be unique within group
* @param length length, or UNLIMITED.length or UNKNOWN.length
* @param isShared whether its shared or local to Variable.
* @deprecated Use Dimension.builder()
*/
@Deprecated
public Dimension(String name, int length, boolean isShared) {
this(name, length, isShared, false, false);
}
/**
* Constructor
*
* @param name name must be unique within group. Can be null only if not shared.
* @param length length, or UNLIMITED.length or UNKNOWN.length
* @param isShared whether its shared or local to Variable.
* @param isUnlimited whether the length can grow.
* @param isVariableLength whether the length is unknown until the data is read.
*/
public Dimension(String name, int length, boolean isShared, boolean isUnlimited, boolean isVariableLength) {
super(name);
this.isShared = isShared;
this.isUnlimited = isUnlimited;
this.isVariableLength = isVariableLength;
if (isVariableLength && (isUnlimited || isShared))
throw new IllegalArgumentException("variable length dimension cannot be shared or unlimited");
setLength(length);
assert (name != null) || !this.isShared;
}
/**
* Copy Constructor. used to make global dimensions
*
* @param name name must be unique within group. Can be null only if not shared.
* @param from copy all other fields from here.
* @deprecated Use Dimension.builder()
*/
@Deprecated
public Dimension(String name, Dimension from) {
super(name);
this.length = from.length;
this.isUnlimited = from.isUnlimited;
this.isVariableLength = from.isVariableLength;
this.isShared = from.isShared;
}
///////////////////////////////////////////////////////////
// the following make this mutable
/**
* Set whether this is unlimited, meaning length can increase.
*
* @param b true if unlimited
* @deprecated Use Dimension.builder()
*/
@Deprecated
public void setUnlimited(boolean b) {
if (immutable)
throw new IllegalStateException("Cant modify");
this.isUnlimited = b;
setLength(this.length); // check legal
}
/**
* Set whether the length is variable.
*
* @param b true if variable length
* @deprecated Use Dimension.builder()
*/
@Deprecated
public void setVariableLength(boolean b) {
if (immutable)
throw new IllegalStateException("Cant modify");
this.isVariableLength = b;
if (b) {
this.isShared = false;
this.isUnlimited = false;
}
setLength(this.length); // check legal
}
/**
* Set whether this is shared.
*
* @param b true if shared
* @deprecated Use Dimension.builder()
*/
@Deprecated
public void setShared(boolean b) {
if (immutable)
throw new IllegalStateException("Cant modify");
this.isShared = b;
}
/**
* Set the Dimension length.
*
* @param n length of Dimension
* @deprecated Use Dimension.builder()
*/
@Deprecated
public void setLength(int n) {
if (immutable && !isUnlimited)
throw new IllegalStateException("Cant modify");
if (isVariableLength) {
if (n != -1)
throw new IllegalArgumentException("VariableLength Dimension length =" + n + " must be -1");
} else if (isUnlimited) {
if (n < 0)
throw new IllegalArgumentException("Unlimited Dimension length =" + n + " must >= 0");
}
this.length = n;
}
/**
* Set the name, converting to valid CDM object name if needed.
*
* @param name set to this value
* @return valid CDM object name
* @deprecated Use Dimension.builder()
*/
@Deprecated
public String setName(String name) {
if (immutable)
throw new IllegalStateException("Cant modify");
if (name.isEmpty())
name = null;
setShortName(name);
return getShortName();
}
/**
* Set the group
*
* @param g parent group
* @deprecated Use Dimension.builder()
*/
@Deprecated
public void setGroup(Group g) {
if (immutable)
throw new IllegalStateException("Cant modify");
setParentGroup(g);
}
/**
* Make this immutable.
*
* @return this
* @deprecated Use Dimension.builder()
*/
@Deprecated
public Dimension setImmutable() {
super.setImmutable();
return this;
}
//////////////////////////////////////////////////////////////
public static Builder builder() {
return new Builder();
}
/** A builder with the Dimension name and length set */
public static Builder builder(String name, int length) {
return new Builder().setName(name).setLength(length);
}
/** A builder of Dimensions. */
public static class Builder {
private Group parent;
private String shortName;
private boolean isUnlimited;
private boolean isVariableLength;
private boolean isShared = true; // shared means its in a group dimension list.
private int length;
private boolean built;
private Builder() {}
/**
* Set is unlimited.
* Used by netcdf3 for variables whose outer dimension can change
*/
public Builder setIsUnlimited(boolean isUnlimited) {
this.isUnlimited = isUnlimited;
return this;
}
/**
* Set variable length is true.
* Implies that its not shared, nor unlimited.
* Used by sequences.
*/
public Builder setIsVariableLength(boolean isVariableLength) {
this.isVariableLength = isVariableLength;
if (isVariableLength) {
this.isShared = false;
this.isUnlimited = false;
this.length = -1;
}
return this;
}
/**
* Set whether this is shared.
* Default value is true.
* Implies it is owned by a Group.
*/
public Builder setIsShared(boolean isShared) {
this.isShared = isShared;
return this;
}
/**
* Set the Dimension length.
*
* @param n length of Dimension
*/
public Builder setLength(int n) {
if (isVariableLength) {
if (n != -1)
throw new IllegalArgumentException("VariableLength Dimension length =" + n + " must be -1");
} else if (isUnlimited) {
if (n < 0)
throw new IllegalArgumentException("Unlimited Dimension length =" + n + " must >= 0");
} else {
if (n < 1)
throw new IllegalArgumentException("Dimension length =" + n + " must be > 0");
}
this.length = n;
return this;
}
public Builder setName(String shortName) {
this.shortName = NetcdfFiles.makeValidCdmObjectName(shortName);
return this;
}
/**
* Set the parent group.
*
* @deprecated Will not use in 6.0
*/
@Deprecated
public Builder setGroup(Group parent) {
this.parent = parent;
return this;
}
public Dimension build() {
if (built)
throw new IllegalStateException("already built");
built = true;
return new Dimension(this);
}
}
}