All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ucar.nc2.Variable Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import ucar.ma2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.iosp.IospHelper;
import ucar.nc2.util.CancelTask;
import ucar.nc2.util.Indent;
import ucar.nc2.util.rc.RC;
import java.io.OutputStream;
import java.util.*;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;

/**
 * A Variable is a logical container for data. It has a dataType, a set of Dimensions that define its array shape,
 * and optionally a set of Attributes.
 * 

* The data is a multidimensional array of primitive types, Strings, or Structures. * Data access is done through the read() methods, which return a memory resident Array. *

* Immutable if setImmutable() was called. * TODO Variable will be immutable in 6. * TODO Variable will not implement AttributeContainer in 6, use Variable.attributes(). * TODO Variable will not extend CDMNode in 6. * * @author caron * @see ucar.ma2.Array * @see ucar.ma2.DataType */ public class Variable extends CDMNode implements VariableSimpleIF, ProxyReader, AttributeContainer { /** * Globally permit or prohibit caching. For use during testing and debugging. *

* A {@code true} value for this field does not indicate whether a Variable * {@link #isCaching() is caching}, only that it's permitted to cache. */ public static boolean permitCaching = true; public static int defaultSizeToCache = 4000; // bytes cache any variable whose size() < defaultSizeToCache public static int defaultCoordsSizeToCache = 40 * 1000; // bytes cache coordinate variable whose size() < // defaultSizeToCache protected static boolean debugCaching; private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Variable.class); /** @deprecated Will be moved to opendap package in 6. */ @Deprecated public static String getDAPName(String name, Variable context) { if (RC.getUseGroups()) { // leave off leading '/' for root entries Group xg = context.getParentGroupOrRoot(); if (!xg.isRoot()) { // Get the list of parent groups List path = Group.collectPath(xg); Formatter dapname = new Formatter(); for (int i = 1; i < path.size(); i++) { // start at 1 to skip root group Group g = path.get(i); dapname.format("/%s", g.getShortName()); } dapname.format("/%s", name); name = dapname.toString(); } } return name; } /** @deprecated Will be moved to opendap package in 6. */ @Deprecated public static String getDAPName(Variable v) { return Variable.getDAPName(v.getShortName(), v); } /** * Get the data type of the Variable. */ public DataType getDataType() { return dataType; } /** * Get the shape: length of Variable in each dimension. * A scalar (rank 0) will have an int[0] shape. * * @return int array whose length is the rank of this Variable * and whose values equal the length of that Dimension. */ public int[] getShape() { int[] result = new int[shape.length]; // optimization over clone() System.arraycopy(shape, 0, result, 0, shape.length); return result; } /** * Get the size of the ith dimension * * @param index which dimension * @return size of the ith dimension */ public int getShape(int index) { return shape[index]; } /** * Get the total number of elements in the Variable. * If this is an unlimited Variable, will use the current number of elements. * If this is a Sequence, will return 1. * If variable length, will skip vlen dimensions * * @return total number of elements in the Variable. */ public long getSize() { long size = 1; for (int aShape : shape) { if (aShape >= 0) size *= aShape; } return size; } /** * Get the number of bytes for one element of this Variable. * For Variables of primitive type, this is equal to getDataType().getSize(). * Variables of String type don't know their size, so what they return is undefined. * Variables of Structure type return the total number of bytes for all the members of * one Structure, plus possibly some extra padding, depending on the underlying format. * Variables of Sequence type return the number of bytes of one element. * * @return total number of bytes for the Variable */ public int getElementSize() { return elementSize; } /** Get the number of dimensions of the Variable, aka the rank. */ public int getRank() { return shape.length; } /** * Get the parent group, or if null, the root group. * * @deprecated Will go away in ver6, shouldn't be needed when builders are used. */ @Deprecated public Group getParentGroupOrRoot() { Group g = this.getParentGroup(); if (g == null) { g = ncfile.getRootGroup(); // super.setParentGroup(g); // TODO: WTF? } return g; } /** * Is this variable metadata?. True if its values need to be included explicitly in NcML output. * * @return true if Variable values need to be included in NcML */ public boolean isMetadata() { return cache != null && cache.isMetadata; } /** * Whether this is a scalar Variable (rank == 0). * * @return true if Variable has rank 0 */ public boolean isScalar() { return getRank() == 0; } /** * Does this variable have a variable length dimension? * If so, it has as one of its dimensions Dimension.VLEN. * * @return true if Variable has a variable length dimension? */ public boolean isVariableLength() { return isVariableLength; } /** * Can this variable's size grow?. * This is equivalent to saying at least one of its dimensions is unlimited. * * @return boolean true iff this variable can grow */ public boolean isUnlimited() { for (Dimension d : dimensions) { if (d.isUnlimited()) return true; } return false; } /** * Get the list of dimensions used by this variable. * The most slowly varying (leftmost for Java and C programmers) dimension is first. * For scalar variables, the list is empty. * * @return List, will be ImmutableList in ver 6. */ public ImmutableList getDimensions() { return ImmutableList.copyOf(dimensions); } /** * Get the ith dimension. * * @param i index of the dimension. * @return requested Dimension, or null if i is out of bounds. */ public Dimension getDimension(int i) { if ((i < 0) || (i >= getRank())) return null; return dimensions.get(i); } /** * Get the list of Dimension names, space delineated. * * @return Dimension names, space delineated */ public String getDimensionsString() { return Dimensions.makeDimensionsString(dimensions); } /** * Find the index of the named Dimension in this Variable. * * @param name the name of the dimension * @return the index of the named Dimension, or -1 if not found. */ public int findDimensionIndex(String name) { for (int i = 0; i < dimensions.size(); i++) { Dimension d = dimensions.get(i); if (name.equals(d.getShortName())) return i; } return -1; } /** * Get the description of the Variable. * Default is to use CDM.LONG_NAME attribute value. If not exist, look for "description", "title", or * "standard_name" attribute value (in that order). * * @return description, or null if not found. */ public String getDescription() { String desc = null; Attribute att = findAttributeIgnoreCase(CDM.LONG_NAME); if ((att != null) && att.isString()) desc = att.getStringValue(); if (desc == null) { att = findAttributeIgnoreCase("description"); if ((att != null) && att.isString()) desc = att.getStringValue(); } if (desc == null) { att = findAttributeIgnoreCase(CDM.TITLE); if ((att != null) && att.isString()) desc = att.getStringValue(); } if (desc == null) { att = findAttributeIgnoreCase(CF.STANDARD_NAME); if ((att != null) && att.isString()) desc = att.getStringValue(); } return desc; } /** * Get the Unit String for the Variable. * Looks for the CDM.UNITS attribute value * * @return unit string, or null if not found. */ public String getUnitsString() { String units = null; Attribute att = attributes().findAttributeIgnoreCase(CDM.UNITS); if ((att != null) && att.isString()) { units = att.getStringValue(); if (units != null) units = units.trim(); } return units; } /** * Get shape as a List of Range objects. * The List is immutable. * * @return List of Ranges, one for each Dimension. */ public ImmutableList getRanges() { // Ok to use Immutable as there are no nulls. return ImmutableList.copyOf(getShapeAsSection().getRanges()); } /** * Get shape as a Section object. * * @return Section containing List, one for each Dimension. */ public Section getShapeAsSection() { if (shapeAsSection == null) { shapeAsSection = Dimensions.makeSectionFromDimensions(this.dimensions).build(); } return shapeAsSection; } /** @deprecated Use Variable.builder() */ @Deprecated public ProxyReader getProxyReader() { return proxyReader; } /** @deprecated Use Variable.builder() */ @Deprecated public void setProxyReader(ProxyReader proxyReader) { this.proxyReader = proxyReader; } /** * Create a new Variable that is a logical subsection of this Variable. * No data is read until a read method is called on it. * * @param ranges List of type ucar.ma2.Range, with size equal to getRank(). * Each Range corresponds to a Dimension, and specifies the section of data to read in that Dimension. * A Range object may be null, which means use the entire dimension. * @return a new Variable which is a logical section of this Variable. * @throws InvalidRangeException if shape and range list don't match */ public Variable section(List ranges) throws InvalidRangeException { return section(new Section(ranges, shape).makeImmutable()); } /** * Create a new Variable that is a logical subsection of this Variable. * No data is read until a read method is called on it. * * @param subsection Section of this variable. * Each Range in the section corresponds to a Dimension, and specifies the section of data to read in that * Dimension. * A Range object may be null, which means use the entire dimension. * @return a new Variable which is a logical section of this Variable. * @throws InvalidRangeException if section not compatible with shape */ public Variable section(Section subsection) throws InvalidRangeException { subsection = Section.fill(subsection, shape); // create a copy of this variable with a proxy reader Variable sectionV = copy(); // subclasses must override sectionV.setProxyReader(new SectionReader(this, subsection)); sectionV.shape = subsection.getShape(); sectionV.createNewCache(); // dont share the cache sectionV.setCaching(false); // dont cache // replace dimensions if needed !! LOOK not shared int[] shape = subsection.getShape(); List dimensions = new ArrayList<>(); for (int i = 0; i < getRank(); i++) { Dimension oldD = getDimension(i); Dimension newD = (oldD.getLength() == shape[i]) ? oldD : Dimension.builder().setName(oldD.getShortName()).setIsUnlimited(oldD.isUnlimited()).setIsShared(false) .setLength(shape[i]).build(); dimensions.add(newD); } sectionV.dimensions = dimensions; return sectionV; } /** * Create a new Variable that is a logical slice of this Variable, by * fixing the specified dimension at the specified index value. This reduces rank by 1. * No data is read until a read method is called on it. * * @param dim which dimension to fix * @param value at what index value * @return a new Variable which is a logical slice of this Variable. * @throws InvalidRangeException if dimension or value is illegal */ public Variable slice(int dim, int value) throws InvalidRangeException { if ((dim < 0) || (dim >= shape.length)) throw new InvalidRangeException("Slice dim invalid= " + dim); // ok to make slice of record dimension with length 0 boolean recordSliceOk = false; if ((dim == 0) && (value == 0)) { Dimension d = getDimension(0); recordSliceOk = d.isUnlimited(); } // otherwise check slice in range if (!recordSliceOk) { if ((value < 0) || (value >= shape[dim])) throw new InvalidRangeException("Slice value invalid= " + value + " for dimension " + dim); } // create a copy of this variable with a proxy reader Variable.Builder sliceV = this.toBuilder(); // subclasses override toBuilder() Section.Builder slice = Section.builder().appendRanges(getShape()); slice.replaceRange(dim, new Range(value, value)); sliceV.setProxyReader(new SliceReader(this, dim, slice.build())); sliceV.resetCache(); // dont share the cache sliceV.setCaching(false); // dont cache // remove that dimension - reduce rank sliceV.dimensions.remove(dim); return sliceV.build(getParentGroupOrRoot()); } /** * Create a new Variable that is a logical view of this Variable, by * eliminating the specified dimension(s) of length 1. * No data is read until a read method is called on it. * * @param dims list of dimensions of length 1 to reduce * @return a new Variable which is a logical slice of this Variable. */ public Variable reduce(List dims) { List dimIdx = new ArrayList<>(dims.size()); for (Dimension d : dims) { assert dimensions.contains(d); assert d.getLength() == 1; dimIdx.add(dimensions.indexOf(d)); } // create a copy of this variable with a proxy reader Variable.Builder sliceV = this.toBuilder(); // subclasses override toBuilder() sliceV.setProxyReader(new ReduceReader(this, dimIdx)); sliceV.resetCache(); // dont share the cache sliceV.setCaching(false); // dont cache // remove dimension(s) - reduce rank for (Dimension d : dims) sliceV.dimensions.remove(d); return sliceV.build(getParentGroupOrRoot()); } /** @deprecated Use {@link #toBuilder()} */ @Deprecated protected Variable copy() { return new Variable(this); } /** Get the NetcdfFile that this variable is contained in. May be null. */ @Nullable public NetcdfFile getNetcdfFile() { return ncfile; } @Nullable public String getFileTypeId() { return ncfile == null ? null : ncfile.getFileTypeId(); } ////////////////////////////////////////////////////////////////////////////// /** * Lookup the enum string for this value. * Can only be called on enum types, where dataType.isEnum() is true. * * @param val the integer value of this enum * @return the String value */ @Nullable public String lookupEnumString(int val) { if (!dataType.isEnum()) throw new UnsupportedOperationException("Can only call Variable.lookupEnumVal() on enum types"); return enumTypedef.lookupEnumString(val); } /** * Public by accident. * * @param enumTypedef set the EnumTypedef, only use if getDataType.isEnum() * @deprecated Use Variable.builder() */ @Deprecated public void setEnumTypedef(EnumTypedef enumTypedef) { if (immutable) throw new IllegalStateException("Cant modify"); if (!dataType.isEnum()) throw new UnsupportedOperationException("Can only call Variable.setEnumTypedef() on enum types"); this.enumTypedef = enumTypedef; } /** * Get the EnumTypedef, only use if getDataType.isEnum() * * @return enumTypedef or null if !getDataType.isEnum() */ public EnumTypedef getEnumTypedef() { return enumTypedef; } ////////////////////////////////////////////////////////////////////////////// // IO // implementation notes to subclassers // all other calls use them, so override only these: // _read() // _read(Section section) // _readNestedData(Section section, boolean flatten) /** * Read a section of the data for this Variable and return a memory resident Array. * The Array has the same element type as the Variable, and the requested shape. * Note that this does not do rank reduction, so the returned Array has the same rank * as the Variable. Use Array.reduce() for rank reduction. *

* assert(origin[ii] + shape[ii]*stride[ii] <= Variable.shape[ii]); *

* * @param origin int array specifying the starting index. If null, assume all zeroes. * @param shape int array specifying the extents in each dimension. * This becomes the shape of the returned Array. * @return the requested data in a memory-resident Array */ public Array read(int[] origin, int[] shape) throws IOException, InvalidRangeException { if ((origin == null) && (shape == null)) return read(); if (origin == null) return read(new Section(shape)); if (shape == null) return read(new Section(origin, this.shape)); return read(new Section(origin, shape)); } /** * Read data section specified by a "section selector", and return a memory resident Array. Uses * Fortran 90 array section syntax. * * @param sectionSpec specification string, eg "1:2,10,:,1:100:10". May optionally have (). * @return the requested data in a memory-resident Array * @see ucar.ma2.Section for sectionSpec syntax */ public Array read(String sectionSpec) throws IOException, InvalidRangeException { return read(new Section(sectionSpec)); } /** * Read a section of the data for this Variable from the netcdf file and return a memory resident Array. * * @param ranges list of Range specifying the section of data to read. * @return the requested data in a memory-resident Array * @throws IOException if error * @throws InvalidRangeException if ranges is invalid * @see #read(Section) */ public Array read(List ranges) throws IOException, InvalidRangeException { if (null == ranges) return _read(); return read(new Section(ranges)); } /** * Read a section of the data for this Variable from the netcdf file and return a memory resident Array. * The Array has the same element type as the Variable, and the requested shape. * Note that this does not do rank reduction, so the returned Array has the same rank * as the Variable. Use Array.reduce() for rank reduction. *

* If the Variable is a member of an array of Structures, this returns only the variable's data * in the first Structure, so that the Array shape is the same as the Variable. * To read the data in all structures, use ncfile.readSectionSpec(). *

* Note this only allows you to specify a subset of this variable. * If the variable is nested in an array of structures and you want to subset that, use * NetcdfFile.read(String sectionSpec, boolean flatten); * * @param section list of Range specifying the section of data to read. * Must be null or same rank as variable. * If list is null, assume all data. * Each Range corresponds to a Dimension. If the Range object is null, it means use the entire dimension. * @return the requested data in a memory-resident Array * @throws IOException if error * @throws InvalidRangeException if section is invalid */ public Array read(ucar.ma2.Section section) throws java.io.IOException, ucar.ma2.InvalidRangeException { return (section == null) ? _read() : _read(Section.fill(section, shape)); } /** * Read all the data for this Variable and return a memory resident Array. * The Array has the same element type and shape as the Variable. *

* If the Variable is a member of an array of Structures, this returns only the variable's data * in the first Structure, so that the Array shape is the same as the Variable. * To read the data in all structures, use ncfile.readSection(). * * @return the requested data in a memory-resident Array. */ public Array read() throws IOException { return _read(); } ///// scalar reading /** * Get the value as a byte for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable or one-dimensional of length 1. * @throws ForbiddenConversionException if data type not convertible to byte */ public byte readScalarByte() throws IOException { Array data = _readScalarData(); return data.getByte(Index.scalarIndexImmutable); } /** * Get the value as a short for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable or one-dimensional of length 1. * @throws ForbiddenConversionException if data type not convertible to short */ public short readScalarShort() throws IOException { Array data = _readScalarData(); return data.getShort(Index.scalarIndexImmutable); } /** * Get the value as a int for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable or one-dimensional of length 1. * @throws ForbiddenConversionException if data type not convertible to int */ public int readScalarInt() throws IOException { Array data = _readScalarData(); return data.getInt(Index.scalarIndexImmutable); } /** * Get the value as a long for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable * @throws ForbiddenConversionException if data type not convertible to long */ public long readScalarLong() throws IOException { Array data = _readScalarData(); return data.getLong(Index.scalarIndexImmutable); } /** * Get the value as a float for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable or one-dimensional of length 1. * @throws ForbiddenConversionException if data type not convertible to float */ public float readScalarFloat() throws IOException { Array data = _readScalarData(); return data.getFloat(Index.scalarIndexImmutable); } /** * Get the value as a double for a scalar Variable. May also be one-dimensional of length 1. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar Variable or one-dimensional of length 1. * @throws ForbiddenConversionException if data type not convertible to double */ public double readScalarDouble() throws IOException { Array data = _readScalarData(); return data.getDouble(Index.scalarIndexImmutable); } /** * Get the value as a String for a scalar Variable. May also be one-dimensional of length 1. * May also be one-dimensional of type CHAR, which wil be turned into a scalar String. * * @throws IOException if theres an IO Error * @throws UnsupportedOperationException if not a scalar or one-dimensional. * @throws ClassCastException if data type not DataType.STRING or DataType.CHAR. */ public String readScalarString() throws IOException { Array data = _readScalarData(); if (dataType == DataType.STRING) return (String) data.getObject(Index.scalarIndexImmutable); else if (dataType == DataType.CHAR) { ArrayChar dataC = (ArrayChar) data; return dataC.getString(); } else throw new IllegalArgumentException("readScalarString not STRING or CHAR " + getFullName()); } /** @deprecated use readScalarXXXX */ @Deprecated protected Array getScalarData() throws IOException { Array scalarData = (cache.data != null) ? cache.data : read(); scalarData = scalarData.reduce(); if ((scalarData.getRank() == 0) || ((scalarData.getRank() == 1) && dataType == DataType.CHAR)) return scalarData; throw new java.lang.UnsupportedOperationException("not a scalar variable =" + this); } /////////////// // internal reads: all other calls go through these. // subclasses must override, so that NetcdfDataset wrapping will work. // non-structure-member Variables. protected Array _read() throws IOException { // caching overrides the proxyReader // check if already cached if (cache.data != null) { if (debugCaching) System.out.println("got data from cache " + getFullName()); return cache.data.copy(); } Array data = proxyReader.reallyRead(this, null); // optionally cache it if (isCaching()) { setCachedData(data); if (debugCaching) System.out.println("cache " + getFullName()); return cache.data.copy(); // dont let users get their nasty hands on cached data } else { return data; } } // section of non-structure-member Variable // assume filled, validated Section protected Array _read(Section section) throws IOException, InvalidRangeException { // check if its really a full read if ((null == section) || section.computeSize() == getSize()) return _read(); // full read was cached if (isCaching()) { if (cache.data == null) { setCachedData(_read()); // read and cache entire array if (debugCaching) System.out.println("cache " + getFullName()); } if (debugCaching) System.out.println("got data from cache " + getFullName()); return cache.data.sectionNoReduce(section.getRanges()).copy(); // subset it, return copy } return proxyReader.reallyRead(this, section, null); } protected Array _readScalarData() throws IOException { Array scalarData = read(); scalarData = scalarData.reduce(); if ((scalarData.getRank() == 0) || ((scalarData.getRank() == 1) && dataType == DataType.CHAR)) return scalarData; throw new java.lang.UnsupportedOperationException("not a scalar variable =" + this); } /** * public by accident, do not call directly. * * @return Array * @throws IOException on error */ @Override public Array reallyRead(Variable client, CancelTask cancelTask) throws IOException { if (isMemberOfStructure()) { // LOOK should be UnsupportedOperationException ?? List memList = new ArrayList<>(); memList.add(this.getShortName()); Structure s = getParentStructure().select(memList); ArrayStructure as = (ArrayStructure) s.read(); return as.extractMemberArray(as.findMember(getShortName())); } try { return ncfile.readData(this, getShapeAsSection()); } catch (InvalidRangeException e) { e.printStackTrace(); throw new IOException(e.getMessage()); // cant happen haha } } /** * public by accident, do not call directly. * * @return Array * @throws IOException on error */ @Override public Array reallyRead(Variable client, Section section, CancelTask cancelTask) throws IOException, InvalidRangeException { if (isMemberOfStructure()) { throw new UnsupportedOperationException("Cannot directly read section of Member Variable=" + getFullName()); } // read just this section return ncfile.readData(this, section); } /** @deprecated do not use */ @Deprecated public long readToByteChannel(Section section, WritableByteChannel wbc) throws IOException, InvalidRangeException { if ((ncfile == null) || hasCachedData()) return IospHelper.copyToByteChannel(read(section), wbc); return ncfile.readToByteChannel(this, section, wbc); } /** Read variable data to a stream. Support for NcStreamWriter. */ public long readToStream(Section section, OutputStream out) throws IOException, InvalidRangeException { if ((ncfile == null) || hasCachedData()) return IospHelper.copyToOutputStream(read(section), out); return ncfile.readToOutputStream(this, section, out); } /** * Get its containing Group. * Not deprecated. * LOOK if you relied on Group being set during construction, use getParentGroupOrRoot(). */ @SuppressWarnings("deprecated") public Group getParentGroup() { return this.group; } /** * Get its parent structure, or null if not in structure * Not deprecated. * * @return parent structure */ @SuppressWarnings("deprecated") @Nullable public Structure getParentStructure() { return this.parentstruct; } /** * Test for presence of parent Structure. * Not deprecated. */ @SuppressWarnings("deprecated") public boolean isMemberOfStructure() { return this.parentstruct != null; } /** * Get the full name of this Variable. * Certain characters are backslash escaped (see NetcdfFiles.getFullName(Variable)) * Not deprecated. * * @return full name with backslash escapes */ @SuppressWarnings("deprecated") public String getFullName() { return NetcdfFiles.makeFullName(this); } /** * Get the display name plus the dimensions, eg 'float name(dim1, dim2)' * * @return display name plus the dimensions */ public String getNameAndDimensions() { Formatter buf = new Formatter(); getNameAndDimensions(buf, true, false); return buf.toString(); } /** * Get the display name plus the dimensions, eg 'float name(dim1, dim2)' * * @param strict strictly comply with ncgen syntax, with name escaping. otherwise, get extra info, no escaping * @return display name plus the dimensions */ public String getNameAndDimensions(boolean strict) { Formatter buf = new Formatter(); getNameAndDimensions(buf, false, strict); return buf.toString(); } /** * Get the display name plus the dimensions, eg 'name(dim1, dim2)' * * @param buf add info to this StringBuilder * @deprecated use CDLWriter */ @Deprecated public void getNameAndDimensions(StringBuilder buf) { getNameAndDimensions(buf, true, false); } /** * Get the display name plus the dimensions, eg 'name(dim1, dim2)' * * @param buf add info to this StringBuffer * @deprecated use getNameAndDimensions(StringBuilder buf) */ @Deprecated public void getNameAndDimensions(StringBuffer buf) { Formatter proxy = new Formatter(); getNameAndDimensions(proxy, true, false); buf.append(proxy); } /** * Add display name plus the dimensions to the StringBuffer * * @param buf add info to this * @param useFullName use full name else short name. strict = true implies short name * @param strict strictly comply with ncgen syntax, with name escaping. otherwise, get extra info, no escaping * @deprecated use CDLWriter */ @Deprecated public void getNameAndDimensions(StringBuilder buf, boolean useFullName, boolean strict) { Formatter proxy = new Formatter(); getNameAndDimensions(proxy, useFullName, strict); buf.append(proxy); } /** * Add display name plus the dimensions to the Formatter * * @param buf add info to this * @param useFullName use full name else short name. strict = true implies short name * @param strict strictly comply with ncgen syntax, with name escaping. otherwise, get extra info, no escaping */ public void getNameAndDimensions(Formatter buf, boolean useFullName, boolean strict) { useFullName = useFullName && !strict; String name = useFullName ? getFullName() : getShortName(); if (strict) name = NetcdfFile.makeValidCDLName(getShortName()); buf.format("%s", name); if (shape != null) { if (getRank() > 0) buf.format("("); for (int i = 0; i < dimensions.size(); i++) { Dimension myd = dimensions.get(i); String dimName = myd.getShortName(); if ((dimName != null) && strict) dimName = NetcdfFile.makeValidCDLName(dimName); if (i != 0) buf.format(", "); if (myd.isVariableLength()) { buf.format("*"); } else if (myd.isShared()) { if (!strict) buf.format("%s=%d", dimName, myd.getLength()); else buf.format("%s", dimName); } else { if (dimName != null) { buf.format("%s=", dimName); } buf.format("%d", myd.getLength()); } } if (getRank() > 0) buf.format(")"); } } public String toString() { return writeCDL(false, false); } /** * CDL representation of a Variable. * * @param useFullName use full name, else use short name * @param strict strictly comply with ncgen syntax * @return CDL representation of the Variable. * @deprecated use CDLWriter */ @Deprecated public String writeCDL(boolean useFullName, boolean strict) { Formatter buf = new Formatter(); writeCDL(buf, new Indent(2), useFullName, strict); return buf.toString(); } /** @deprecated use CDLWriter */ @Deprecated protected void writeCDL(Formatter buf, Indent indent, boolean useFullName, boolean strict) { buf.format("%s", indent); if (dataType == null) buf.format("Unknown"); else if (dataType.isEnum()) { if (enumTypedef == null) buf.format("enum UNKNOWN"); else buf.format("enum %s", NetcdfFile.makeValidCDLName(enumTypedef.getShortName())); } else buf.format("%s", dataType.toString()); // if (isVariableLength) buf.append("(*)"); // LOOK buf.format(" "); getNameAndDimensions(buf, useFullName, strict); buf.format(";"); if (!strict) buf.format(extraInfo()); buf.format("%n"); indent.incr(); for (Attribute att : attributes()) { if (CDM.isspecial(att)) continue; buf.format("%s", indent); att.writeCDL(buf, strict, getShortName()); buf.format(";"); if (!strict && (att.getDataType() != DataType.STRING)) buf.format(" // %s", att.getDataType()); buf.format("%n"); } indent.decr(); } /** * String representation of Variable and its attributes. */ public String toStringDebug() { Formatter f = new Formatter(); f.format("Variable %s", getFullName()); if (ncfile != null) { f.format(" in file %s", getDatasetLocation()); String extra = ncfile.toStringDebug(this); if (extra != null) f.format(" %s", extra); } return f.toString(); } private static boolean showSize = false; protected String extraInfo() { return showSize ? " // " + getElementSize() + " " + getSize() : ""; } /** The location of the dataset this belongs to. Labeling purposes only. */ public String getDatasetLocation() { if (ncfile != null) return ncfile.getLocation(); return "N/A"; } /** * Instances which have same content are equal. */ public boolean equals(Object oo) { if (this == oo) return true; if (!(oo instanceof Variable)) return false; Variable o = (Variable) oo; if (!getShortName().equals(o.getShortName())) return false; if (isScalar() != o.isScalar()) return false; if (getDataType() != o.getDataType()) return false; if (!getParentGroup().equals(o.getParentGroup())) return false; if ((getParentStructure() != null) && !getParentStructure().equals(o.getParentStructure())) return false; if (isVariableLength() != o.isVariableLength()) return false; if (dimensions.size() != o.getDimensions().size()) return false; for (int i = 0; i < dimensions.size(); i++) if (!getDimension(i).equals(o.getDimension(i))) return false; return true; } /** * Override Object.hashCode() to implement equals. */ @Override public int hashCode() { if (hashCode == 0) { int result = 17; result = 37 * result + getShortName().hashCode(); if (isScalar()) result++; result = 37 * result + getDataType().hashCode(); result = 37 * result + getParentGroup().hashCode(); if (getParentStructure() != null) result = 37 * result + getParentStructure().hashCode(); if (isVariableLength) result++; result = 37 * result + dimensions.hashCode(); hashCode = result; } return hashCode; } protected int hashCode; /** * Sort by name */ public int compareTo(VariableSimpleIF o) { return getShortName().compareTo(o.getShortName()); } ///////////////////////////////////////////////////////////////////////////// /** @deprecated Use Variable.builder() */ @Deprecated protected Variable() {} /** * Create a Variable. Also must call setDataType() and setDimensions() * * @param ncfile the containing NetcdfFile. * @param group the containing group; if null, use rootGroup * @param parent parent Structure, may be null * @param shortName variable shortName, must be unique within the Group * @deprecated Use Variable.builder() */ @Deprecated public Variable(NetcdfFile ncfile, Group group, Structure parent, String shortName) { super(shortName); this.ncfile = ncfile; if (parent == null) setParentGroup((group == null) ? ncfile.getRootGroup() : group); else setParentStructure(parent); attributes = new AttributeContainerMutable(shortName); } /** * Create a Variable. Also must call setDataType() and setDimensions() * * @param ncfile the containing NetcdfFile. * @param group the containing group; if null, use rootGroup * @param parent parent Structure, may be null * @param shortName variable shortName, must be unique within the Group * @param dtype the Variable's DataType * @param dims space delimited list of dimension names. may be null or "" for scalars. * @deprecated Use Variable.builder() */ @Deprecated public Variable(NetcdfFile ncfile, Group group, Structure parent, String shortName, DataType dtype, String dims) { this(ncfile, group, parent, shortName, dtype, (List) null); if (group == null) group = ncfile.getRootGroup(); setDimensions(Dimensions.makeDimensionsList(group::findDimension, dims)); } /** * Create a Variable. Also must call setDataType() and setDimensions() * * @param ncfile the containing NetcdfFile. * @param group the containing group; if null, use rootGroup * @param parent parent Structure, may be null * @param shortName variable shortName, must be unique within the Group * @param dtype the Variable's DataType * @param dims dimension names. * @deprecated Use Variable.builder() */ @Deprecated public Variable(NetcdfFile ncfile, Group group, Structure parent, String shortName, DataType dtype, List dims) { this(ncfile, group, parent, shortName); setDataType(dtype); setDimensions(dims); } /** * Copy constructor. * The returned Variable is mutable. * It shares the cache object and the iosp Object, attributes and dimensions with the original. * Does not share the proxyReader. * Use for section, slice, "logical views" of original variable. * * @param from copy from this Variable. * @deprecated Use Variable.builder() */ @Deprecated public Variable(Variable from) { super(from.getShortName()); this.attributes = new AttributeContainerMutable(from.getShortName(), from.attributes()); this.cache = from.cache; // caller should do createNewCache() if dont want to share setDataType(from.getDataType()); this.dimensions = new ArrayList<>(from.dimensions); // dimensions are shared this.elementSize = from.getElementSize(); this.enumTypedef = from.enumTypedef; setParentGroup(from.group); setParentStructure(from.getParentStructure()); this.isVariableLength = from.isVariableLength; this.ncfile = from.ncfile; this.shape = from.getShape(); this.sizeToCache = from.sizeToCache; this.spiObject = from.spiObject; } /** * Set the data type * * @param dataType set to this value * @deprecated Use Variable.builder() */ @Deprecated public void setDataType(DataType dataType) { if (immutable) throw new IllegalStateException("Cant modify"); this.dataType = dataType; this.elementSize = getDataType().getSize(); } /** * Set the short name, converting to valid CDM object name if needed. * * @param shortName set to this value * @return valid CDM object name * @deprecated Use Variable.builder() */ @Deprecated public String setName(String shortName) { if (immutable) throw new IllegalStateException("Cant modify"); setShortName(shortName); return getShortName(); } /** * Set the parent group. * * @param group set to this value * @deprecated Use Variable.builder() */ @Deprecated public void setParentGroup(Group group) { if (immutable) throw new IllegalStateException("Cant modify"); super.setParentGroup(group); } /** * Set the element size. Usually elementSize is determined by the dataType, * use this only for exceptional cases. * * @param elementSize set to this value * @deprecated Use Variable.builder() */ @Deprecated public void setElementSize(int elementSize) { if (immutable) throw new IllegalStateException("Cant modify"); this.elementSize = elementSize; } ////////////////////////////////////////////////////////////////////////////////////////////////// // Attributes /** The attributes contained by this Variable. */ @Override public AttributeContainer attributes() { return attributes; } /** Find the attribute by name, return null if not exist */ @Override @Nullable public Attribute findAttribute(String name) { return attributes.findAttribute(name); } /** * Find a String-valued Attribute by name (ignore case), return the String value of the Attribute. * * @return the attribute value, or defaultValue if not found */ @Override public String findAttributeString(String attName, String defaultValue) { return attributes.findAttributeString(attName, defaultValue); } /** @deprecated Use attributes() */ public boolean isEmpty() { return attributes.isEmpty(); } /** @deprecated Use attributes() */ @Deprecated public java.util.List getAttributes() { return attributes.getAttributes(); } /** @deprecated Use attributes() */ @Deprecated public Attribute findAttributeIgnoreCase(String name) { return attributes.findAttributeIgnoreCase(name); } /** @deprecated Use attributes() */ @Deprecated public double findAttributeDouble(String attName, double defaultValue) { return attributes.findAttributeDouble(attName, defaultValue); } /** @deprecated Use attributes() */ @Deprecated public int findAttributeInteger(String attName, int defaultValue) { return attributes.findAttributeInteger(attName, defaultValue); } /** @deprecated Use Variable.builder() */ @Deprecated public Attribute addAttribute(Attribute att) { return attributes.addAttribute(att); } /** @deprecated Use Variable.builder() */ @Deprecated public void addAll(Iterable atts) { attributes.addAll(atts); } /** @deprecated Use Variable.builder() */ @Deprecated public boolean remove(Attribute a) { return attributes.remove(a); } /** @deprecated Use Variable.builder() */ @Deprecated public boolean removeAttribute(String attName) { return attributes.removeAttribute(attName); } /** @deprecated Use Variable.builder() */ @Deprecated public boolean removeAttributeIgnoreCase(String attName) { return attributes.removeAttributeIgnoreCase(attName); } //////////////////////////////////////////////////////////////////////// /** * Set the shape with a list of Dimensions. The Dimensions may be shared or not. * Dimensions are in order, slowest varying first. Send a null for a scalar. * Technically you can use Dimensions from any group; pragmatically you should only use * Dimensions contained in the Variable's parent groups. * * @param dims list of type ucar.nc2.Dimension * @deprecated Use Variable.builder() */ @Deprecated public void setDimensions(List dims) { if (immutable) throw new IllegalStateException("Cant modify"); this.dimensions = (dims == null) ? new ArrayList<>() : new ArrayList<>(dims); resetShape(); } /** * Use when dimensions have changed, to recalculate the shape. * * @deprecated Use Variable.builder() */ @Deprecated public void resetShape() { // if (immutable) throw new IllegalStateException("Cant modify"); LOOK allow this for unlimited dimension updating this.shape = new int[dimensions.size()]; for (int i = 0; i < dimensions.size(); i++) { Dimension dim = dimensions.get(i); shape[i] = dim.getLength(); // shape[i] = Math.max(dim.getLength(), 0); // LOOK // if (dim.isUnlimited() && (i != 0)) // LOOK only true for Netcdf-3 // throw new IllegalArgumentException("Unlimited dimension must be outermost"); if (dim.isVariableLength()) { // if (dimensions.size() != 1) // throw new IllegalArgumentException("Unknown dimension can only be used in 1 dim array"); // else isVariableLength = true; } } this.shapeAsSection = null; // recalc next time its asked for } /** * Set the dimensions using the dimensions names. The dimension is searched for recursively in the parent groups. * * @param dimString : whitespace separated list of dimension names, or '*' for Dimension.UNKNOWN, or number for anon * dimension. null or empty String is a scalar. * @deprecated Use Variable.builder() */ @Deprecated public void setDimensions(String dimString) { if (immutable) throw new IllegalStateException("Cant modify"); try { setDimensions(Dimensions.makeDimensionsList(getParentGroupOrRoot()::findDimension, dimString)); resetShape(); } catch (IllegalStateException e) { throw new IllegalArgumentException("Variable " + getFullName() + " setDimensions = '" + dimString + "' FAILED: " + e.getMessage() + " file = " + getDatasetLocation()); } } /** * Reset the dimension array. Anonymous dimensions are left alone. * Shared dimensions are searched for recursively in the parent groups. * * @deprecated Use Variable.builder() */ @Deprecated public void resetDimensions() { if (immutable) throw new IllegalStateException("Cant modify"); ArrayList newDimensions = new ArrayList<>(); for (Dimension dim : dimensions) { if (dim.isShared()) { Dimension newD = getParentGroupOrRoot().findDimension(dim.getShortName()); if (newD == null) throw new IllegalArgumentException( "Variable " + getFullName() + " resetDimensions FAILED, dim doesnt exist in parent group=" + dim); newDimensions.add(newD); } else { newDimensions.add(dim); } } this.dimensions = newDimensions; resetShape(); } /** * Set the dimensions using all anonymous (unshared) dimensions * * @param shape defines the dimension lengths. must be > 0, or -1 for VLEN * @throws ucar.ma2.InvalidRangeException if any shape < 1 * @deprecated Use Variable.builder() */ @Deprecated public void setDimensionsAnonymous(int[] shape) throws InvalidRangeException { if (immutable) throw new IllegalStateException("Cant modify"); this.dimensions = new ArrayList<>(); for (int i = 0; i < shape.length; i++) { if ((shape[i] < 1) && (shape[i] != -1)) throw new InvalidRangeException("shape[" + i + "]=" + shape[i] + " must be > 0"); Dimension anon; if (shape[i] == -1) { anon = Dimension.VLEN; isVariableLength = true; } else { anon = new Dimension(null, shape[i], false, false, false); } dimensions.add(anon); } resetShape(); } /** * Set this Variable to be a scalar * * @deprecated Use Variable.builder() */ @Deprecated public void setIsScalar() { if (immutable) throw new IllegalStateException("Cant modify"); this.dimensions = new ArrayList<>(); resetShape(); } /** * Replace a dimension with an equivalent one. * * @param idx index into dimension array * @param dim to set * @deprecated Use Variable.builder() */ @Deprecated public void setDimension(int idx, Dimension dim) { if (immutable) throw new IllegalStateException("Cant modify"); dimensions.set(idx, dim); resetShape(); } /** * Make this immutable. * * @return this * @deprecated Use Variable.builder() */ @Deprecated public Variable setImmutable() { super.setImmutable(); dimensions = Collections.unmodifiableList(dimensions); attributes.setImmutable(); return this; } /** * Is this Variable immutable * * @return if immutable * @deprecated Use Variable.builder() */ @Deprecated public boolean isImmutable() { return immutable; } /** Get immutable service provider opaque object. */ public Object getSPobject() { return spiObject; } /** @deprecated Do not use. */ @Deprecated public void setSPobject(Object spiObject) { this.spiObject = spiObject; } //////////////////////////////////////////////////////////////////////////////////// // caching /** * If total data size is less than SizeToCache in bytes, then cache. * * @return size at which caching happens */ public int getSizeToCache() { if (sizeToCache >= 0) return sizeToCache; // it was set return isCoordinateVariable() ? defaultCoordsSizeToCache : defaultSizeToCache; } /** * Set the sizeToCache. If not set, use defaults * * @param sizeToCache size at which caching happens. < 0 means use defaults * @deprecated Use Variable.builder() */ @Deprecated public void setSizeToCache(int sizeToCache) { this.sizeToCache = sizeToCache; } /** * Set whether to cache or not. Implies that the entire array will be stored, once read. * Normally this is set automatically based on size of data. * * @param caching set if caching. * @deprecated Use Variable.builder() */ @Deprecated public void setCaching(boolean caching) { this.cache.isCaching = caching; this.cache.cachingSet = true; } /** * Will this Variable be cached when read. * Set externally, or calculated based on total size < sizeToCache. *

* This will always return {@code false} if {@link #permitCaching caching isn't permitted}. * * @return true is caching */ public boolean isCaching() { if (!permitCaching) { return false; } if (!this.cache.cachingSet) { cache.isCaching = !isVariableLength && (getSize() * getElementSize() < getSizeToCache()); if (debugCaching) System.out.printf(" cache %s %s %d < %d%n", getFullName(), cache.isCaching, getSize() * getElementSize(), getSizeToCache()); this.cache.cachingSet = true; } return cache.isCaching; } /** * Note that standalone Ncml caches data values set in the Ncml. * So one cannot invalidate those caches. * * @deprecated Use Variable.builder() */ @Deprecated public void invalidateCache() { cache.data = null; } /** @deprecated Use Variable.builder() */ @Deprecated public void setCachedData(Array cacheData) { setCachedData(cacheData, false); } /** * Set the data cache * * @param cacheData cache this Array * @param isMetadata : synthesized data, set true if must be saved in NcML output (ie data not actually in the file). * @deprecated Use Variable.builder() */ @Deprecated public void setCachedData(Array cacheData, boolean isMetadata) { if ((cacheData != null) && (cacheData.getElementType() != getDataType().getPrimitiveClassType())) throw new IllegalArgumentException( "setCachedData type=" + cacheData.getElementType() + " incompatible with variable type=" + getDataType()); this.cache.data = cacheData; this.cache.isMetadata = isMetadata; this.cache.cachingSet = true; this.cache.isCaching = true; } /** * Create a new data cache, use this when you dont want to share the cache. */ public void createNewCache() { this.cache = new Cache(); } /** * Has data been read and cached. * Use only on a Variable, not a subclass. * * @return true if data is read and cached */ public boolean hasCachedData() { return (null != cache.data); } // this indirection allows us to share the cache among the variable's sections and copies protected static class Cache { private Array data; protected boolean isCaching; protected boolean cachingSet; private boolean isMetadata; private Cache() {} public void reset() { this.data = null; this.isCaching = false; this.cachingSet = false; this.isMetadata = false; } } /////////////////////////////////////////////////////////////////////// // setting variable data values /** * Generate the list of values from a starting value and an increment. * Will reshape to variable if needed. * * @param npts number of values, must = v.getSize() * @param start starting value * @param incr increment * @deprecated Use Variable.builder() */ @Deprecated public void setValues(int npts, double start, double incr) { if (npts != getSize()) throw new IllegalArgumentException("bad npts = " + npts + " should be " + getSize()); Array data = Array.makeArray(getDataType(), npts, start, incr); if (getRank() != 1) data = data.reshape(getShape()); setCachedData(data, true); } /** * Set the data values from a list of Strings. * * @param values list of Strings * @throws IllegalArgumentException if values array not correct size, or values wont parse to the correct type * @deprecated Use Variable.builder() */ @Deprecated public void setValues(List values) throws IllegalArgumentException { Array data = Array.makeArray(getDataType(), values); if (data.getSize() != getSize()) throw new IllegalArgumentException("Incorrect number of values specified for the Variable " + getFullName() + " needed= " + getSize() + " given=" + data.getSize()); if (getRank() != 1) // dont have to reshape for rank 1 data = data.reshape(getShape()); setCachedData(data, true); } //////////////////////////////////////////////////////////////////////// // StructureMember - could be a subclass, but that has problems /** * Get list of Dimensions, including parents if any. * * @return array of Dimension, rank of v plus all parents. * @deprecated use Dimensions.makeDimensionsAll(Variable); */ @Deprecated public List getDimensionsAll() { List dimsAll = new ArrayList<>(); addDimensionsAll(dimsAll, this); return dimsAll; } private void addDimensionsAll(List result, Variable v) { if (v.isMemberOfStructure()) addDimensionsAll(result, v.getParentStructure()); for (int i = 0; i < v.getRank(); i++) result.add(v.getDimension(i)); } /** @deprecated use Dimensions.makeDimensionsAll(Variable); */ @Deprecated public int[] getShapeAll() { if (getParentStructure() == null) return getShape(); List dimAll = getDimensionsAll(); int[] shapeAll = new int[dimAll.size()]; for (int i = 0; i < dimAll.size(); i++) shapeAll[i] = dimAll.get(i).getLength(); return shapeAll; } /** * Calculate if this is a classic coordinate variable: has same name as its first dimension. * If type char, must be 2D, else must be 1D. * * @return true if a coordinate variable. */ public boolean isCoordinateVariable() { if ((dataType == DataType.STRUCTURE) || isMemberOfStructure()) // Structures and StructureMembers cant be coordinate // variables return false; int n = getRank(); if (n == 1 && dimensions.size() == 1) { Dimension firstd = dimensions.get(0); if (getShortName().equals(firstd.getShortName())) { // : short names match return true; } } if (n == 2 && dimensions.size() == 2) { // two dimensional Dimension firstd = dimensions.get(0); // must be char valued (really a String) return shortName.equals(firstd.getShortName()) && // short names match (getDataType() == DataType.CHAR); } return false; } ///////////////////////////////////////////////////////////////////////////////////// // TODO make private final and Immutable in release 6. // Physical container for this Variable where the I/O happens. may be null if Variable is self contained. protected NetcdfFile ncfile; protected DataType dataType; private EnumTypedef enumTypedef; protected List dimensions = new ArrayList<>(5); // TODO immutable in ver 6 protected AttributeContainerMutable attributes; // TODO immutable in ver 6 protected ProxyReader proxyReader = this; protected Object spiObject; // computed private Section shapeAsSection; // derived from the shape, immutable; used for every read, deferred creation protected int[] shape = new int[0]; protected boolean isVariableLength; protected int elementSize; // TODO do we need these? breaks immutability // TODO maybe caching read data should be separate from "this is the source of the data". protected Cache cache = new Cache(); // cache cannot be null protected int sizeToCache = -1; // bytes protected Variable(Builder builder, Group parentGroup) { super(builder.shortName); if (parentGroup == null) { throw new IllegalStateException(String.format("Parent Group must be set for Variable %s", builder.shortName)); } if (builder.dataType == null) { throw new IllegalStateException(String.format("DataType must be set for Variable %s", builder.shortName)); } if (builder.shortName == null || builder.shortName.isEmpty()) { throw new IllegalStateException("Name must be set for Variable"); } this.group = parentGroup; this.ncfile = builder.ncfile; this.parentstruct = builder.parentStruct; this.dataType = builder.dataType; this.attributes = builder.attributes; this.proxyReader = builder.proxyReader == null ? this : builder.proxyReader; this.spiObject = builder.spiObject; this.cache = builder.cache; if (this.dataType.isEnum()) { this.enumTypedef = this.group.findEnumeration(builder.enumTypeName, true); if (this.enumTypedef == null) { throw new IllegalStateException( String.format("EnumTypedef '%s' does not exist in a parent Group", builder.enumTypeName)); } } // Convert dimension to shared dimensions that live in a parent group. // TODO: In 6.0 remove group field in dimensions, just use equals() to match. List dims = new ArrayList<>(); for (Dimension dim : builder.dimensions) { if (dim.isShared()) { Dimension sharedDim = this.group.findDimension(dim.getShortName()); if (sharedDim == null) { throw new IllegalStateException(String.format("Shared Dimension %s does not exist in a parent group", dim)); } else { dims.add(sharedDim); } } else { dims.add(dim); } } // possible slice of another variable if (builder.slicer != null) { int dim = builder.slicer.dim; int index = builder.slicer.index; Section slice = Dimensions.makeSectionFromDimensions(dims).replaceRange(dim, Range.make(index, index)).build(); Variable orgClient = parentGroup.findVariableLocal(builder.slicer.orgName); setProxyReader(new SliceReader(orgClient, dim, slice)); setCaching(false); // dont cache // remove that dimension - reduce rank dims.remove(dim); } this.dimensions = dims; if (builder.autoGen != null) { this.cache.data = builder.autoGen.makeDataArray(this.dataType, this.dimensions); this.cache.isMetadata = true; // So it gets copied } // calculated fields this.elementSize = builder.elementSize > 0 ? builder.elementSize : getDataType().getSize(); this.isVariableLength = this.dimensions.stream().anyMatch(Dimension::isVariableLength); try { List list = new ArrayList<>(); for (Dimension d : dimensions) { int len = d.getLength(); if (len > 0) list.add(new Range(d.getShortName(), 0, len - 1)); else if (len == 0) list.add(Range.EMPTY); // LOOK empty not named else { assert d.isVariableLength(); list.add(Range.VLEN); // LOOK vlen not named } } this.shapeAsSection = new Section(list).makeImmutable(); this.shape = shapeAsSection.getShape(); } catch (InvalidRangeException e) { log.error("Bad shape in variable " + getFullName(), e); throw new IllegalStateException(e.getMessage()); } } /** Turn into a mutable Builder. Can use toBuilder().build() to copy. */ public Builder toBuilder() { return addLocalFieldsToBuilder(builder()); } // Add local fields to the passed - in builder. // This makes an exact copy, including ncfile and parent and proxyReader. // build() replaces parent but respects ncfile and proxyReader. // Normally on a copy you want to set proxyReader to null; protected Builder addLocalFieldsToBuilder(Builder> builder) { builder.setName(this.shortName).setNcfile(this.ncfile).setParentStructure(this.getParentStructure()) .setDataType(this.dataType).setEnumTypeName(this.enumTypedef != null ? this.enumTypedef.getShortName() : null) .addDimensions(this.dimensions).addAttributes(this.attributes).setProxyReader(this.proxyReader) .setSPobject(this.spiObject); if (this.cache.isMetadata) { builder.setCachedData(this.cache.data, true); } return builder; } /** * Get Builder for this class that allows subclassing. * * @see "https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses" */ public static Builder builder() { return new Builder2(); } private static class Builder2 extends Builder { @Override protected Builder2 self() { return this; } } /** A builder for Variables. */ public static abstract class Builder> { public String shortName; public DataType dataType; private int elementSize; public NetcdfFile ncfile; // set in Group build() if null private Structure parentStruct; // set in Structure.build(), no not use otherwise protected Group.Builder parentBuilder; protected Structure.Builder parentStructureBuilder; private ArrayList dimensions = new ArrayList<>(); // The dimension's group is ignored; replaced when // build() public Object spiObject; public ProxyReader proxyReader; public Cache cache = new Cache(); // cache cannot be null private String enumTypeName; private AutoGen autoGen; private Slicer slicer; private AttributeContainerMutable attributes = new AttributeContainerMutable(""); private boolean built; protected abstract T self(); // add / replace previous public T addAttribute(Attribute att) { attributes.addAttribute(att); return self(); } public T addAttributes(Iterable atts) { attributes.addAll(atts); return self(); } public AttributeContainerMutable getAttributeContainer() { return attributes; } /* * TODO Dimensions are tricky during the transition to 6, because they have a pointer to their Group in 5.x, * but with Builders, the Group isnt created until build(). The Group is part of equals() and hash(). * The second issue is that we may not know their shape, so that may have to be made later. * Provisionally, we are going to try this strategy: during build, Dimensions are created without Groups, and * hash and equals are modified to allow that. During build, the Dimensions are recreated with the Group, and * Variables Dimensions are replaced with shared Dimensions. * For 6.0, Dimensions become value objects, without a reference to containing Group. * * A VariableBuilder does not know its Group.Builder, so searching for "parent dimensions", must be done with the * Group.Builder object, not the Variable.Builder. * * Havent dealt with structure yet, eg getDimensionsAll(), but should be ok because Variable.Builder does know its * parent structure. */ public T addDimension(Dimension dim) { dimensions.add(dim); return self(); } public T addDimensions(Collection dims) { dimensions.addAll(dims); return self(); } public T setDimensions(List dims) { this.dimensions = new ArrayList<>(dims); return self(); } /** Find the dimension with the same name as dim, and replace it with dim */ public boolean replaceDimensionByName(Dimension dim) { int idx = -1; for (int i = 0; i < dimensions.size(); i++) { if (dimensions.get(i).getShortName().equals(dim.getShortName())) { idx = i; } } if (idx >= 0) { dimensions.set(idx, dim); } return (idx >= 0); } /** Set dimensions by name. The parent group builder must be set. */ public T setDimensionsByName(String dimString) { Preconditions.checkNotNull(this.parentBuilder); this.dimensions = new ArrayList<>(this.parentBuilder.makeDimensionsList(dimString)); return self(); } @Nullable public String getFirstDimensionName() { return getDimensionName(0); } @Nullable public String getDimensionName(int index) { if (dimensions.size() > index) { return dimensions.get(index).getShortName(); } return null; } public Iterable getDimensionNames() { if (dimensions.size() > 0) { // TODO test if this always works return dimensions.stream().map(d -> d.getShortName()).filter(Objects::nonNull).collect(Collectors.toList()); } return ImmutableList.of(); } public String makeDimensionsString() { return Dimensions.makeDimensionsString(this.dimensions); } /** * Set the dimensions using all anonymous (unshared) dimensions * * @param shape defines the dimension lengths. must be > 0, or -1 for VLEN * @throws RuntimeException if any shape < 1 and not -1. */ public T setDimensionsAnonymous(int[] shape) { this.dimensions = new ArrayList<>(Dimensions.makeDimensionsAnon(shape)); return self(); } public ImmutableList getDimensions() { return ImmutableList.copyOf(this.dimensions); } // Get all dimension names, including parent structure public ImmutableSet getDimensionsAll() { ImmutableSet.Builder dimsAll = new ImmutableSet.Builder<>(); addDimensionsAll(dimsAll, this); return dimsAll.build(); } private void addDimensionsAll(ImmutableSet.Builder result, Variable.Builder v) { if (v.parentStructureBuilder != null) { v.parentStructureBuilder.getDimensionsAll().forEach(result::add); } getDimensionNames().forEach(result::add); } public T setIsScalar() { this.dimensions = new ArrayList<>(); return self(); } public int getRank() { return dimensions.size(); } public T setDataType(DataType dataType) { this.dataType = dataType; return self(); } public String getEnumTypeName() { return this.enumTypeName; } public int getElementSize() { return elementSize > 0 ? elementSize : dataType.getSize(); } // In some case we need to override standard element size. public T setElementSize(int elementSize) { this.elementSize = elementSize; return self(); } public T setEnumTypeName(String enumTypeName) { this.enumTypeName = enumTypeName; return self(); } public T setNcfile(NetcdfFile ncfile) { this.ncfile = ncfile; return self(); } public T setSPobject(Object spiObject) { this.spiObject = spiObject; return self(); } public T setName(String shortName) { this.shortName = NetcdfFiles.makeValidCdmObjectName(shortName); this.attributes.setName(shortName); return self(); } public String getFullName() { String full = ""; Group.Builder group = parentStructureBuilder != null ? parentStructureBuilder.parentBuilder : parentBuilder; if (group != null) { full = group.makeFullName(); } if (parentStructureBuilder != null) { full += parentStructureBuilder.shortName + "."; } return full + this.shortName; } public T setParentGroupBuilder(Group.Builder parent) { this.parentBuilder = parent; return self(); } public Group.Builder getParentGroupBuilder() { return parentBuilder; } T setParentStructureBuilder(Structure.Builder structureBuilder) { this.parentStructureBuilder = structureBuilder; return self(); } public Structure.Builder getParentStructureBuilder() { return parentStructureBuilder; } // Only the parent Structure should call this. T setParentStructure(Structure parent) { this.parentStruct = parent; return self(); } public T setProxyReader(ProxyReader proxy) { this.proxyReader = proxy; return self(); } public T setCachedData(Array cacheData, boolean isMetadata) { this.cache.data = cacheData; this.cache.isMetadata = isMetadata; this.cache.cachingSet = true; this.cache.isCaching = true; return self(); } public T setAutoGen(double start, double incr) { this.autoGen = new AutoGen(start, incr); return self(); } public T resetCache() { this.cache.data = null; return self(); } public T setCaching(boolean caching) { this.cache.isCaching = caching; this.cache.cachingSet = true; return self(); } /** * Create a new Variable.Builder that is a logical slice of this one, by * fixing the specified dimension at the specified index value. * * @param dim which dimension to fix * @param index at what index value */ public Variable.Builder makeSliceBuilder(int dim, int index) { System.out.printf(" slice of %s%n", this.shortName); // create a copy of this builder with a slicer Variable.Builder sliced = this.copy(); sliced.slicer = new Slicer(dim, index, this.shortName); return sliced; } public Builder copy() { return new Builder2().copyFrom(this); } /** TODO Copy metadata from orgVar. */ public T copyFrom(Variable orgVar) { setName(orgVar.getShortName()); setDataType(orgVar.getDataType()); if (orgVar.getEnumTypedef() != null) { setEnumTypeName(orgVar.getEnumTypedef().getShortName()); } setSPobject(orgVar.getSPobject()); addDimensions(orgVar.getDimensions()); addAttributes(orgVar); // copy return self(); } public T copyFrom(Variable.Builder builder) { addAttributes(builder.attributes); // copy this.autoGen = builder.autoGen; this.cache = builder.cache; setDataType(builder.dataType); addDimensions(builder.dimensions); this.elementSize = builder.elementSize; setEnumTypeName(builder.getEnumTypeName()); setNcfile(builder.ncfile); this.parentBuilder = builder.parentBuilder; setParentStructure(builder.parentStruct); setParentStructureBuilder(builder.parentStructureBuilder); setProxyReader(builder.proxyReader); setName(builder.shortName); setSPobject(builder.spiObject); return self(); } @Override public String toString() { return dataType + " " + shortName; } /** Normally this is called by Group.build() */ public Variable build(Group parentGroup) { if (built) throw new IllegalStateException("already built"); built = true; return new Variable(this, parentGroup); } } @Immutable private static class AutoGen { final double start; final double incr; private AutoGen(double start, double incr) { this.start = start; this.incr = incr; } private Array makeDataArray(DataType dtype, List dimensions) { Section section = Dimensions.makeSectionFromDimensions(dimensions).build(); return Array.makeArray(dtype, (int) section.getSize(), start, incr).reshape(section.getShape()); } } @Immutable private static class Slicer { final int dim; final int index; final String orgName; Slicer(int dim, int index, String orgName) { this.dim = dim; this.index = index; this.orgName = orgName; } } /////////////////////////////////////////////////////////////////////// // deprecated /** * @return isVariableLength() * @deprecated use isVariableLength() */ public boolean isUnknownLength() { return isVariableLength; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy