ucar.nc2.internal.dataset.CoordSystemFactory Maven / Gradle / Ivy
The newest version!
package ucar.nc2.internal.dataset;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import ucar.nc2.Attribute;
import ucar.nc2.Group;
import ucar.nc2.NetcdfFile;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.spi.CoordSystemBuilderFactory;
import ucar.nc2.internal.dataset.conv.CF1Convention;
import ucar.nc2.internal.dataset.conv.DefaultConventions;
import ucar.nc2.internal.dataset.spi.CFSubConventionProvider;
import ucar.nc2.internal.ncml.NcmlReader;
import ucar.nc2.util.CancelTask;
import ucar.unidata.util.StringUtil2;
/** Static methods for managing CoordSystemBuilderFactory classes */
public class CoordSystemFactory {
protected static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CoordSystemFactory.class);
// Place where resources can be placed, eg NcML files used to wrap datasets.
public static final String resourcesDir = "resources/nj22/coords/";
private static List conventionList = new ArrayList<>();
private static Map ncmlHash = new HashMap<>();
private static boolean useMaximalCoordSys = true;
/**
* Allow plug-ins to determine if it owns a file based on the file's Convention attribute.
*/
public interface ConventionNameOk {
/**
* Do you own this file?
*
* @param convName name in the file
* @param wantName name passed into registry
* @return true if you own this file
*/
boolean isMatch(String convName, String wantName);
}
// These get precedence
static {
registerConvention(_Coordinate.Convention, new CoordSystemBuilder.Factory());
for (CFSubConventionProvider provider : ServiceLoader.load(CFSubConventionProvider.class)) {
registerConvention(provider.getConventionName(), provider);
}
registerConvention("CF-1.", new CF1Convention.Factory(), String::startsWith);
registerConvention("CDM-Extended-CF", new CF1Convention.Factory());
// this is to test DefaultConventions, not needed when we remove old convention builders.
registerConvention("MARS", new DefaultConventions.Factory());
}
/**
* Register an NcML file that implements a Convention by wrapping the dataset in the NcML.
* It is then processed by CoordSystemBuilder, using the _Coordinate attributes.
*
* @param conventionName name of Convention, must be in the "Conventions" global attribute.
* @param ncmlLocation location of NcML file, may be local file or URL.
*/
public static void registerNcml(String conventionName, String ncmlLocation) {
ncmlHash.put(conventionName, ncmlLocation);
}
/**
* Register a class that implements a Convention. Will match (ignoring case) the COnvention name.
*
* @param conventionName name of Convention.
* This name will be used to look in the "Conventions" global attribute.
* @param c implementation of CoordSystemBuilderFactory that parses those kinds of netcdf files.
*/
public static void registerConvention(String conventionName, CoordSystemBuilderFactory c) {
registerConvention(conventionName, c, null);
}
/**
* Register a class that implements a Convention.
*
* @param conventionName name of Convention.
* This name will be used to look in the "Conventions" global attribute.
* Otherwise, you must implement the isMine() static method.
* @param match pass in your own matcher. if null, equalsIgnoreCase() will be used.
* @param factory implementation of CoordSystemBuilderFactory that parses those kinds of netcdf files.
*/
public static void registerConvention(String conventionName, CoordSystemBuilderFactory factory,
ConventionNameOk match) {
conventionList.add(new Convention(conventionName, factory, match));
}
@Nullable
private static CoordSystemBuilderFactory matchConvention(String convName) {
for (Convention c : conventionList) {
if ((c.match == null) && c.convName.equalsIgnoreCase(convName))
return c.factory;
if ((c.match != null) && c.match.isMatch(convName, c.convName))
return c.factory;
}
return null;
}
/**
* If true, assign implicit CoordinateSystem objects to variables that dont have one yet.
* Default value is false.
*
* @param b true if if you want to guess at Coordinate Systems
*/
public static void setUseMaximalCoordSys(boolean b) {
useMaximalCoordSys = b;
}
/**
* Get whether to make records into Structures.
*
* @return whether to make records into Structures.
*/
public static boolean getUseMaximalCoordSys() {
return useMaximalCoordSys;
}
/**
* Breakup list of Convention names in the Convention attribute in CF compliant way.
*
* @param convAttValue original value of Convention attribute
* @return list of Convention names
*/
public static List breakupConventionNames(String convAttValue) {
ArrayList names = new ArrayList<>();
if ((convAttValue.indexOf(',') > 0) || (convAttValue.indexOf(';') > 0)) {
StringTokenizer stoke = new StringTokenizer(convAttValue, ",;");
while (stoke.hasMoreTokens()) {
String name = stoke.nextToken();
names.add(name.trim());
}
} else if ((convAttValue.indexOf('/') > 0)) {
StringTokenizer stoke = new StringTokenizer(convAttValue, "/");
while (stoke.hasMoreTokens()) {
String name = stoke.nextToken();
names.add(name.trim());
}
} else {
return ImmutableList.of(convAttValue);
}
return names;
}
/**
* Build a list of Conventions
*
* @param mainConv this is the main convention
* @param convAtts list of others, only use "extra" Conventions
* @return comma separated list of Conventions
*/
public static String buildConventionAttribute(String mainConv, String... convAtts) {
List result = new ArrayList<>();
result.add(mainConv);
for (String convs : convAtts) {
if (convs == null)
continue;
List ss = breakupConventionNames(convs); // may be a list
for (String s : ss) {
if (matchConvention(s) == null) // only add extra ones, not ones that compete with mainConv
result.add(s);
}
}
// now form comma separated result
boolean start = true;
Formatter f = new Formatter();
for (String s : result) {
if (start)
f.format("%s", s);
else
f.format(", %s", s);
start = false;
}
return f.toString();
}
/**
* Get a CoordSystemBuilder whose job it is to add Coordinate information to a NetcdfDataset.Builder.
*
* @param ds the NetcdfDataset.Builder to modify
* @param cancelTask allow user to bail out.
* @return the builder to be used
* @throws java.io.IOException on io error
*/
@Nonnull
public static Optional factory(NetcdfDataset.Builder ds, CancelTask cancelTask)
throws IOException {
// look for the Conventions attribute
Group.Builder root = ds.rootGroup;
String convName = root.getAttributeContainer().findAttributeString(CDM.CONVENTIONS, null);
if (convName == null) {
// common mistake Convention instead of Conventions
convName = root.getAttributeContainer().findAttributeString("Convention", null);
}
if (convName != null) {
convName = convName.trim();
}
// look for ncml first
if (convName != null) {
String convNcml = ncmlHash.get(convName);
if (convNcml != null) {
CoordSystemBuilder csb = new CoordSystemBuilder(ds);
NcmlReader.wrapNcml(ds, convNcml, cancelTask);
return Optional.of(csb);
}
}
CoordSystemBuilderFactory coordSysFactory = null;
// Try to match on convention name. Must be first in case NcML has set Convention name.
if (convName != null) {
coordSysFactory = findConventionByName(convName);
}
// if the match is on the main CF convention, we need to look at possible sub-conventions or incubating conventions
if (coordSysFactory != null && ds.orgFile != null && coordSysFactory instanceof CF1Convention.Factory) {
List convs = breakupConventionNames(convName);
if (convs.size() > 1) {
CoordSystemBuilderFactory potentialCoordSysFactory = findCfSubConvention(ds.orgFile, convs);
// only use the potentialCoordSysFactory if it is a CF sub-convention
if (potentialCoordSysFactory != null && potentialCoordSysFactory instanceof CFSubConventionProvider) {
coordSysFactory = potentialCoordSysFactory;
}
}
}
// Try to match on isMine() TODO: why use orgFile instead of ds?
if (coordSysFactory == null && ds.orgFile != null) {
coordSysFactory = findConventionByIsMine(ds.orgFile);
}
boolean isDefault = false;
// if no convention class found, use the default
if (coordSysFactory == null) {
coordSysFactory = new DefaultConventions.Factory();
isDefault = true;
}
// Now process it.
CoordSystemBuilder coordSystemBuilder = coordSysFactory.open(ds);
if (convName == null)
coordSystemBuilder.addUserAdvice("No 'Conventions' global attribute.");
else if (isDefault)
coordSystemBuilder.addUserAdvice("No CoordSystemBuilder is defined for Conventions= '" + convName + "'\n");
else {
coordSystemBuilder.setConventionUsed(coordSysFactory.getConventionName());
}
ds.rootGroup.addAttribute(new Attribute(_Coordinate._CoordSysBuilder, coordSystemBuilder.getClass().getName()));
return Optional.of(coordSystemBuilder);
}
@Nullable
private static CoordSystemBuilderFactory findConventionByIsMine(NetcdfFile orgFile) {
// Look for Convention using isMine()
for (Convention conv : conventionList) {
CoordSystemBuilderFactory candidate = conv.factory;
if (candidate.isMine(orgFile)) {
return candidate;
}
}
// Use service loader mechanism isMine()
for (CoordSystemBuilderFactory csb : ServiceLoader.load(CoordSystemBuilderFactory.class)) {
if (csb.isMine(orgFile)) {
return csb;
}
}
return null;
}
// same as findConventionByIsMine, but only checks CFSubConventionProvider instances of CoordSystemBuilderFactory
@Nullable
private static CoordSystemBuilderFactory findCfSubConvention(NetcdfFile orgFile, List convs) {
// Look for Convention using isMine()
for (Convention conv : conventionList) {
CoordSystemBuilderFactory candidate = conv.factory;
if (candidate instanceof CFSubConventionProvider && candidate.isMine(orgFile)) {
return candidate;
}
}
// Use service loader mechanism isMine()
for (CFSubConventionProvider cfSubCon : ServiceLoader.load(CFSubConventionProvider.class)) {
if (cfSubCon.isMine(orgFile) || cfSubCon.isMine(convs)) {
return cfSubCon;
}
}
return null;
}
@Nullable
private static CoordSystemBuilderFactory findConventionByName(String convName) {
// Try to match on convention name as is
CoordSystemBuilderFactory coordSysFactory = findRegisteredConventionByName(convName);
if (coordSysFactory != null)
return coordSysFactory;
coordSysFactory = findLoadedConventionByName(convName);
if (coordSysFactory != null)
return coordSysFactory;
// Try splitting up the Convention string.
List names = breakupConventionNames(convName);
for (String name : names) {
coordSysFactory = findRegisteredConventionByName(name);
if (coordSysFactory != null)
return coordSysFactory;
}
for (String name : names) {
coordSysFactory = findLoadedConventionByName(name);
if (coordSysFactory != null)
return coordSysFactory;
}
// last ditch desperate - split on white space
Iterable tokens = StringUtil2.split(convName);
for (String name : tokens) {
coordSysFactory = findRegisteredConventionByName(name);
if (coordSysFactory != null)
return coordSysFactory;
}
for (String name : tokens) {
coordSysFactory = findLoadedConventionByName(name);
if (coordSysFactory != null)
return coordSysFactory;
}
return null;
}
private static CoordSystemBuilderFactory findRegisteredConventionByName(String convName) {
// look for registered conventions using convention name
return matchConvention(convName);
}
@Nullable
private static CoordSystemBuilderFactory findLoadedConventionByName(String convName) {
// use service loader mechanism
for (CoordSystemBuilderFactory csb : ServiceLoader.load(CoordSystemBuilderFactory.class)) {
if (convName.equals(csb.getConventionName())) {
return csb;
}
}
return null;
}
private static class Convention {
String convName;
CoordSystemBuilderFactory factory;
ConventionNameOk match;
Convention(String convName, CoordSystemBuilderFactory factory, ConventionNameOk match) {
this.convName = convName;
this.factory = factory;
this.match = match;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy