1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package au.csiro.netcdf;
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.util.ArrayList;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.regex.PatternSyntaxException;
32
33 import org.apache.commons.cli.BasicParser;
34 import org.apache.commons.cli.CommandLine;
35 import org.apache.commons.cli.HelpFormatter;
36 import org.apache.commons.cli.Option;
37 import org.apache.commons.cli.OptionBuilder;
38 import org.apache.commons.cli.Options;
39 import org.apache.commons.cli.ParseException;
40 import org.apache.log4j.Logger;
41
42 import ucar.ma2.DataType;
43 import ucar.nc2.Attribute;
44 import ucar.nc2.NetcdfFileWriteable;
45 import ucar.nc2.Variable;
46 import au.csiro.netcdf.cli.Command;
47 import au.csiro.netcdf.cli.CommandLineOptionsComparator;
48 import au.csiro.netcdf.util.NetCDFUtils;
49 import au.csiro.netcdf.util.Util;
50
51
52
53
54
55
56
57
58
59
60
61 public class NcDefineVariable implements Command
62 {
63
64
65
66 public static final String NC_DEFINE_VAR_COMMAND_NAME = "ncdefineVar";
67
68
69
70
71 public static final String OUTPUT_FILE = "outputFileName";
72
73
74
75
76 public static final String INPUT_FILE = "inputFileName";
77
78
79
80
81 private static final String STANDARD_INPUT = "standardInput";
82
83
84
85
86 public static final String VARIABLE_NAME = "variableName";
87
88
89
90
91 public static final String VARIABLE_DATA_TYPE = "variableDataType";
92
93
94
95
96 public static final String VARIABLE_ATTRIBUTES = "variableAttributes";
97
98
99
100
101 public static final String DIMENSION_NAMES = "dimensionNames";
102
103
104
105
106
107 public static final String IS_LARGE_FILE = "largeFileSupport";
108
109
110
111
112
113 public static final String IS_FILL_VARIABLE = "fillVar";
114
115
116
117
118 private static final String DUMMY_VARIABLE = "dummyVariable";
119
120
121
122
123 private static final Logger LOG = Logger.getLogger(NcDefineVariable.class.getName());
124
125
126
127
128 private Options options = null;
129
130
131
132
133 private static Set<DataType> netCDF3DataTypes = new HashSet<DataType>();
134 {
135 netCDF3DataTypes.add(DataType.DOUBLE);
136 netCDF3DataTypes.add(DataType.FLOAT);
137 netCDF3DataTypes.add(DataType.INT);
138 netCDF3DataTypes.add(DataType.CHAR);
139 netCDF3DataTypes.add(DataType.SHORT);
140 netCDF3DataTypes.add(DataType.BYTE);
141 }
142
143
144
145
146 public NcDefineVariable()
147 {
148 this.options = createOptions();
149 }
150
151
152
153
154
155
156 @Override
157 public void execute(String[] args) throws IllegalArgumentException, IOException, ParseException
158 {
159
160 CommandLine parsedCommandLine = new BasicParser().parse(this.options, args);
161
162
163 String outputFilenameArg = (parsedCommandLine.hasOption(OUTPUT_FILE)) ? parsedCommandLine
164 .getOptionValue(OUTPUT_FILE) : "";
165 String inputFilenameArg = (parsedCommandLine.hasOption(INPUT_FILE)) ? parsedCommandLine
166 .getOptionValue(INPUT_FILE) : "";
167 boolean isStandardInput = parsedCommandLine.hasOption(STANDARD_INPUT);
168 String variableNameArg = (parsedCommandLine.hasOption(VARIABLE_NAME)) ? parsedCommandLine
169 .getOptionValue(VARIABLE_NAME) : "";
170 String variableDataTypeArg = (parsedCommandLine.hasOption(VARIABLE_DATA_TYPE)) ? parsedCommandLine
171 .getOptionValue(VARIABLE_DATA_TYPE) : "";
172 String variableAttributesArg = (parsedCommandLine.hasOption(VARIABLE_ATTRIBUTES)) ? parsedCommandLine
173 .getOptionValue(VARIABLE_ATTRIBUTES) : "";
174 String dimensionNamesArg = (parsedCommandLine.hasOption(DIMENSION_NAMES)) ? parsedCommandLine
175 .getOptionValue(DIMENSION_NAMES) : "";
176 boolean isLargeFileSupport = parsedCommandLine.hasOption(IS_LARGE_FILE);
177 boolean isFillVariable = parsedCommandLine.hasOption(IS_FILL_VARIABLE);
178
179
180 File outputFile = null;
181 try
182 {
183 outputFile = Util.getExistingFile(outputFilenameArg);
184 }
185 catch (IllegalArgumentException iae)
186 {
187 throw new IllegalArgumentException(NcDefineVariable.OUTPUT_FILE + " value refers to non-existent file: "
188 + outputFilenameArg);
189 }
190
191
192 if (Util.getExistingFile(outputFilenameArg).length() >= MAX_32BIT_OFFSET_FILE_SIZE && !isLargeFileSupport)
193 {
194 throw new IllegalArgumentException("The netCDF file will be too large, please use " + IS_LARGE_FILE
195 + " flag.");
196 }
197
198
199 if (!inputFilenameArg.isEmpty())
200 {
201 Util.getExistingFile(inputFilenameArg);
202 }
203
204
205 String dimensionNamesString = NcDefineVariable.mapStringToDimensionNamesString(dimensionNamesArg);
206
207
208 List<Attribute> variableAttributes = new ArrayList<Attribute>();
209
210 if (!variableAttributesArg.isEmpty())
211 {
212 try
213 {
214 variableAttributes = NetCDFUtils.mapStringToAttributeValueList(variableAttributesArg);
215 }
216 catch (IllegalArgumentException iae)
217 {
218 throw new IllegalArgumentException(NcDefineVariable.VARIABLE_ATTRIBUTES
219 + " value is not a comma separated String of attribute-value pairs: " + variableAttributesArg);
220 }
221 }
222
223
224 DataType variableDataType = NcDefineVariable.mapStringToDataType(variableDataTypeArg);
225
226
227 if (!inputFilenameArg.isEmpty())
228 {
229 variableAttributes.addAll(NetCDFUtils.readAttributesFromStream(new FileInputStream(inputFilenameArg)));
230 }
231 if(isStandardInput)
232 {
233 variableAttributes.addAll(NetCDFUtils.readAttributesFromStream(System.in));
234 }
235 this.execute(outputFile, variableNameArg, variableDataType, variableAttributes, dimensionNamesString,
236 isLargeFileSupport, isFillVariable);
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public void execute(File outputFilename, String variableName, DataType variableDataType,
262 List<Attribute> variableAttributes, String dimensionNamesString, boolean isLargeFileSupport,
263 boolean isFillVariable) throws IOException
264 {
265
266 NetcdfFileWriteable ncfile = null;
267
268 ncfile = NetcdfFileWriteable.openExisting(outputFilename.getPath(), isFillVariable
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 );
286 try
287 {
288
289 if (ncfile.getDimensions().isEmpty())
290 {
291 throw new IllegalStateException("No dimensions have been defined in the file: "
292 + outputFilename.getPath());
293 }
294
295
296
297
298
299
300
301
302
303
304
305 if (ncfile.getVariables().isEmpty())
306 {
307 ncfile.setRedefineMode(true);
308 ncfile.setLargeFile(true);
309 Variable variable = new Variable(ncfile, null
310 DUMMY_VARIABLE);
311 variable.setDataType(DataType.DOUBLE);
312 variable.setCaching(false);
313 variable.setDimensions(ncfile.getDimensions().get(0).getName());
314
315 ncfile.addVariable(null, variable);
316 ncfile.setRedefineMode(false);
317 ncfile.close();
318 ncfile = NetcdfFileWriteable.openExisting(outputFilename.getPath(), isFillVariable
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343 );
344 ncfile.setRedefineMode(true);
345 ncfile.removeVariable(null, DUMMY_VARIABLE);
346 }
347 else
348 {
349 ncfile.setRedefineMode(true);
350 }
351
352 ncfile.setLargeFile(isLargeFileSupport);
353
354 Variable variable = new Variable(ncfile, null
355 variableName);
356 variable.setDataType(variableDataType);
357 variable.setCaching(false);
358
359
360 variable.setDimensions(dimensionNamesString);
361
362
363 for (Attribute attribute : variableAttributes)
364 {
365 variable.addAttribute(attribute);
366 }
367
368 ncfile.addVariable(null, variable);
369
370
371 ncfile.setRedefineMode(false);
372 }
373 finally
374 {
375 if(ncfile!=null)
376 {
377 try
378 {
379 ncfile.close();
380 }
381 catch(Exception e)
382 {
383 LOG.error("Could not close file: " + outputFilename.getPath(), e);
384 }
385 }
386 }
387 }
388
389
390
391
392
393
394 @Override
395 public String getCommandName()
396 {
397 return NcDefineVariable.NC_DEFINE_VAR_COMMAND_NAME;
398 }
399
400
401
402
403
404
405 public String toString()
406 {
407
408 String header = "Define a variable in a netCDF file.";
409 String footer = "\nExample: ncdefinevar -outputFileName ABC.nc -variableName Lat "
410 + "-variableDataType float -dimensionNames Lat\n"
411 + "Will add a variable called Lat to the file ABC.nc. The variable is linked to the "
412 + "dimension lat and thus is a 'corresponding' variable. The file must already "
413 + "exist and the dimension must be already defined in the file.";
414 StringWriter sw = new StringWriter();
415 HelpFormatter formatter = new HelpFormatter();
416 formatter.setOptionComparator(new CommandLineOptionsComparator());
417 formatter.printHelp(new PrintWriter(sw), PRINT_WIDTH, NcDefineVariable.NC_DEFINE_VAR_COMMAND_NAME, header,
418 this.options, 0, 1, footer);
419 return sw.toString();
420 }
421
422
423
424
425
426
427 @SuppressWarnings("static-access")
428 @Override
429 public Options createOptions()
430 {
431 Option outputFileName = OptionBuilder.withArgName("file").hasArg().withDescription(
432 "1: the filename of the netCDF file to be modified.").isRequired(true).withLongOpt(OUTPUT_FILE).create(
433 "o");
434
435 Option inputFileName = OptionBuilder
436 .withArgName("file")
437 .hasArg()
438 .withDescription(
439 "2. the filename of a text file containing attributes to be loaded. "
440 + "OPTIONAL, ensure that text containing '=' or ',' characters are delimited by a backslash.")
441 .isRequired(false).withLongOpt(INPUT_FILE).create("i");
442
443 Option standardInput = OptionBuilder.withDescription(
444 "3: OPTIONAL, read attributes from Stdin, ensure that text containing '=' or ',' characters are delimited by a backslash.").isRequired(false)
445 .withLongOpt(STANDARD_INPUT).create("s");
446
447 Option variableName = OptionBuilder.withArgName("text").hasArg().withDescription(
448 "4: the name to be given to the variable.").isRequired(true).withLongOpt(VARIABLE_NAME).create("v");
449
450 Option variableType = OptionBuilder.withArgName("text").hasArg().withDescription(
451 "5: the data type of the variable, e.g. " + NcDefineVariable.netCDF3DataTypes).isRequired(true)
452 .withLongOpt(VARIABLE_DATA_TYPE).create("t");
453
454 Option variableAttributes = OptionBuilder
455 .withArgName("text")
456 .hasArg()
457 .withDescription(
458 "6: a comma separated list of attribute-value pairs, OPTIONAL, e.g. \"units=mm\", ensure that text containing '=' or ',' characters are delimited by a backslash.")
459 .isRequired(false).withLongOpt(VARIABLE_ATTRIBUTES).create("a");
460
461 Option dimensionNames = OptionBuilder.withArgName("text").hasArg().withDescription(
462 "7: OPTIONAL, a comma separated list of the variable's dimensions, e.g. date,latitude,longitude.")
463 .isRequired(false).withLongOpt(DIMENSION_NAMES).create("d");
464
465 Option largeFileSupport = OptionBuilder.withDescription(
466 "8: OPTIONAL, set if more than 2 GB of data will need to be stored in this file.").isRequired(false)
467 .withLongOpt(IS_LARGE_FILE).create("l");
468
469 Option fillVariable = OptionBuilder.withDescription(
470 "9: OPTIONAL, set if unwritten variable values should have the fill value.").isRequired(false)
471 .withLongOpt(IS_FILL_VARIABLE).create("f");
472
473 Options options = new Options();
474
475 options.addOption(outputFileName);
476 options.addOption(inputFileName);
477 options.addOption(standardInput);
478 options.addOption(variableName);
479 options.addOption(variableType);
480 options.addOption(variableAttributes);
481 options.addOption(dimensionNames);
482 options.addOption(largeFileSupport);
483 options.addOption(fillVariable);
484
485 return options;
486 }
487
488
489
490
491 public String getUsageString()
492 {
493 return this.toString();
494 }
495
496
497
498
499
500
501 public String validCommand(String[] commandLine)
502 {
503 String errorMsg = "";
504
505
506 try
507 {
508
509 CommandLine parsedCommandLine = new BasicParser().parse(this.options, commandLine);
510
511
512
513 String outputFilenameArg = (parsedCommandLine.hasOption(OUTPUT_FILE)) ? parsedCommandLine
514 .getOptionValue(OUTPUT_FILE) : "";
515 String inputFilenameArg = (parsedCommandLine.hasOption(INPUT_FILE)) ? parsedCommandLine
516 .getOptionValue(INPUT_FILE) : "";
517 String variableDataTypeArg = (parsedCommandLine.hasOption(VARIABLE_DATA_TYPE)) ? parsedCommandLine
518 .getOptionValue(VARIABLE_DATA_TYPE) : "";
519 String variableAttributesArg = (parsedCommandLine.hasOption(VARIABLE_ATTRIBUTES)) ? parsedCommandLine
520 .getOptionValue(VARIABLE_ATTRIBUTES) : "";
521 String dimensionNamesArg = (parsedCommandLine.hasOption(DIMENSION_NAMES)) ? parsedCommandLine
522 .getOptionValue(DIMENSION_NAMES) : "";
523 boolean isLargeFileSupport = parsedCommandLine.hasOption(IS_LARGE_FILE);
524
525
526 try
527 {
528 Util.getExistingFile(outputFilenameArg);
529 }
530 catch (IllegalArgumentException iae)
531 {
532 throw new IllegalArgumentException(NcDefineVariable.OUTPUT_FILE
533 + " value refers to non-existent file: " + outputFilenameArg);
534 }
535
536
537 if (Util.getExistingFile(outputFilenameArg).length() >= MAX_32BIT_OFFSET_FILE_SIZE && !isLargeFileSupport)
538 {
539 throw new IllegalArgumentException("The netCDF file will be too large, please use " + IS_LARGE_FILE
540 + " flag.");
541 }
542
543
544 if (!inputFilenameArg.isEmpty())
545 {
546 Util.getExistingFile(inputFilenameArg);
547 }
548
549
550 NcDefineVariable.mapStringToDimensionNamesString(dimensionNamesArg);
551
552
553 if (!variableAttributesArg.isEmpty())
554 {
555 try
556 {
557 NetCDFUtils.mapStringToAttributeValueList(variableAttributesArg);
558 }
559 catch (IllegalArgumentException iae)
560 {
561 throw new IllegalArgumentException(NcDefineVariable.VARIABLE_ATTRIBUTES
562 + " value is not a comma separated String of attribute-value pairs: "
563 + variableAttributesArg);
564 }
565 }
566
567
568 NcDefineVariable.mapStringToDataType(variableDataTypeArg);
569 }
570 catch (ParseException pe)
571 {
572 errorMsg = errorMsg + "\n" + pe.getMessage();
573 }
574 catch (IllegalArgumentException iae)
575 {
576 errorMsg = errorMsg + "\n" + iae.getMessage();
577 }
578
579 return errorMsg;
580 }
581
582
583
584
585
586
587
588
589
590
591
592
593 private static String mapStringToDimensionNamesString(String dimensionNamesArg) throws IllegalArgumentException
594 {
595 try
596 {
597 List<String> dimensionNames = Util.tokeniseCommaSeparatedString(dimensionNamesArg);
598 StringBuffer strBuf = new StringBuffer();
599 for (String dimensionName : dimensionNames)
600 {
601 strBuf.append(dimensionName + " ");
602 }
603 return strBuf.toString();
604 }
605 catch (PatternSyntaxException pse)
606 {
607 throw new IllegalArgumentException(NcDefineVariable.DIMENSION_NAMES
608 + " value is not a comma separated String: " + dimensionNamesArg);
609 }
610 }
611
612
613
614
615
616
617
618
619
620
621 private static DataType mapStringToDataType(String variableDataType) throws IllegalArgumentException
622 {
623 DataType dataType = DataType.getType(variableDataType);
624 if (dataType != null && NcDefineVariable.netCDF3DataTypes.contains(dataType))
625 {
626 return dataType;
627 }
628 throw new IllegalArgumentException(NcDefineVariable.VARIABLE_DATA_TYPE + " value is not a valid data type: "
629 + variableDataType + ". Allowed data types are: " + NcDefineVariable.netCDF3DataTypes);
630 }
631
632
633
634
635 public static Set<DataType> getNetCDF3DataTypes()
636 {
637 return netCDF3DataTypes;
638 }
639
640
641
642
643 public static void setNetCDF3DataTypes(Set<DataType> netCDF3DataTypes)
644 {
645 NcDefineVariable.netCDF3DataTypes = netCDF3DataTypes;
646 }
647 }