1 /**
2 * Copyright 2010, CSIRO Australia.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /**
18 *
19 */
20 package au.csiro.netcdf;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.util.ArrayList;
27 import java.util.List;
28
29 import org.apache.commons.cli.BasicParser;
30 import org.apache.commons.cli.CommandLine;
31 import org.apache.commons.cli.HelpFormatter;
32 import org.apache.commons.cli.Option;
33 import org.apache.commons.cli.OptionBuilder;
34 import org.apache.commons.cli.Options;
35 import org.apache.commons.cli.ParseException;
36 import org.apache.log4j.Logger;
37
38 import ucar.nc2.Dimension;
39 import ucar.nc2.NetcdfFileWriteable;
40 import au.csiro.netcdf.cli.Command;
41 import au.csiro.netcdf.cli.CommandLineOptionsComparator;
42 import au.csiro.netcdf.util.Util;
43
44 /**
45 * The <strong>ncdefineDim</strong> command defines a {@link Dimension} in a netCDF file.
46 * <p>
47 * Copyright 2010, CSIRO Australia
48 * All rights reserved.
49 *
50 * @author $Author: robertbridle $ on 17/03/2010
51 * @version $Revision: 84 $ $Date: 2010-08-25 15:56:46 +1000 (Wed, 25 Aug 2010) $ $Id: NcDefineDimension.java 6520
52 * 2010-03-17 06:17:50Z bri26e $
53 */
54 public class NcDefineDimension implements Command
55 {
56 /**
57 * The command name
58 */
59 public static final String NC_DEFINE_DIM_COMMAND_NAME = "ncdefineDim";
60
61 /**
62 * The name of the command line option used for specifying the output netCDF file name.
63 */
64 public static final String OUTPUT_FILE = "outputFileName";
65
66 /**
67 * The name of the command line option used for specifying the size of the dimension.
68 */
69 public static final String DIMENSION_SIZE = "dimensionSize";
70
71 /**
72 * The name of the command line option used for specifying the name of the dimension.
73 */
74 public static final String DIMENSION_NAME = "dimensionName";
75
76 /**
77 * Whether the netCDF file should be written with large file support, that is, 64-bit addressing for files greater
78 * than 2 GB.
79 */
80 public static final String IS_LARGE_FILE = "largeFileSupport";
81
82 /**
83 * Constant that defines the logger to be used.
84 */
85 private static final Logger LOG = Logger.getLogger(NcDefineDimension.class.getName());
86
87 /**
88 * Command options
89 */
90 private Options options = null;
91
92 /**
93 * Constructor
94 */
95 public NcDefineDimension()
96 {
97 this.options = createOptions();
98 }
99
100 /*
101 * (non-Javadoc)
102 *
103 * @see au.csiro.netcdf.Command#execute(java.lang.String[])
104 */
105 @Override
106 public void execute(String[] args) throws ParseException, IOException, IllegalArgumentException
107 {
108 // parse the command line arguments
109 CommandLine parsedCommandLine = new BasicParser().parse(this.options, args);
110
111 // get the command line argument values
112 String outputFilenameArg = (parsedCommandLine.hasOption(OUTPUT_FILE)) ? parsedCommandLine
113 .getOptionValue(OUTPUT_FILE) : "";
114 String dimensionSizeArg = (parsedCommandLine.hasOption(DIMENSION_SIZE)) ? parsedCommandLine
115 .getOptionValue(DIMENSION_SIZE) : "";
116 String dimensionNameArg = (parsedCommandLine.hasOption(DIMENSION_NAME)) ? parsedCommandLine
117 .getOptionValue(DIMENSION_NAME) : "";
118 boolean isUnlimited = dimensionSizeArg.isEmpty(); // true if empty or not specified, i.e. default value = false.
119 boolean isLargeFileSupport = parsedCommandLine.hasOption(IS_LARGE_FILE);
120
121 // check whether large file support is needed
122 if (Util.fileExists(outputFilenameArg)
123 && Util.getExistingFile(outputFilenameArg).length() >= MAX_32BIT_OFFSET_FILE_SIZE
124 && !isLargeFileSupport)
125 {
126 throw new IllegalArgumentException("The netCDF file will be too large, please use " + IS_LARGE_FILE
127 + " flag.");
128 }
129
130 // try getting dimension size, where IllegalArgumentException can be thrown from
131 int dimensionSize = 0; // this is non-mandatory option, its default value is 0.
132 if (!isUnlimited)
133 {
134 dimensionSize = NcDefineDimension.mapStringToDimensionSize(dimensionSizeArg);
135 }
136
137 this.execute(outputFilenameArg, dimensionNameArg, dimensionSize, isUnlimited, isLargeFileSupport);
138 }
139
140 /**
141 * Allows the command to be run programmatically, instead of from a command line.
142 *
143 * @param outputFilename
144 * the netCDF file in which to define a dimension.
145 * @param dimensionName
146 * the name of the dimension, this value will be used to reference the dimension.
147 * @param dimensionSize
148 * the size of the dimension, if the dimension is unlimited, then a size of 0 is used.
149 * @param isUnlimited
150 * whether the size of the dimension is unlimited. note: only the first dimension defined can be
151 * unlimited.
152 * @param isLargeFileSupport
153 * whether the netCDF file should be written with large file support, i.e. 64-bit addressing for files
154 * greater than 2 GB.
155 * @throws IOException
156 * thrown if netCDF can to be written to or read from.
157 * @throws SecurityException
158 * thrown if a security manager exists and it prevents the netCDF file from being created.
159 * @throws IllegalStateException
160 * thrown if the {@link Dimension} can not be added to the netCDF-3 file.
161 */
162 public void execute(String outputFilename, String dimensionName, int dimensionSize, boolean isUnlimited,
163 boolean isLargeFileSupport) throws IOException, SecurityException, IllegalStateException
164 {
165 // the netcdf file to be written.
166 NetcdfFileWriteable ncfile = null;
167
168 // does output file exist
169 File outputFile = new File(outputFilename);
170 if (outputFile.exists())
171 {
172 ncfile = NetcdfFileWriteable.openExisting(outputFilename, false /*
173 * Setting fill = true causes everything to
174 * be written twice: first with the fill
175 * value, then with the data values. If you
176 * know you will write all the data, you dont
177 * need to use fill. If you don't know if all
178 * the data will be written, turning fill on
179 * ensures that any values not written will
180 * have the fill value. Otherwise those
181 * values will be undefined: possibly zero,
182 * or possibly garbage.
183 */);
184 try
185 {
186 // allow editing header info
187 ncfile.setRedefineMode(true);
188
189 Dimension dimension = new Dimension(dimensionName, dimensionSize/*
190 * number of data points
191 */, true /* isShared */,
192 isUnlimited /* isUnlimited */, false /* isVariableLength */);
193
194 // check the dimension to be added
195 this.checkDimensionLimitiations(ncfile, dimension);
196
197 // add dimension
198 ncfile.addDimension(null, dimension);
199
200 // close editing header info
201 ncfile.setRedefineMode(false);
202 }
203 finally
204 {
205 if(ncfile!=null)
206 {
207 try
208 {
209 ncfile.close();
210 }
211 catch(Exception e)
212 {
213 LOG.error("Could not close file: " + outputFilename, e);
214 }
215 }
216 }
217 }
218 else
219 {
220 File parentFile = outputFile.getParentFile();
221 if (parentFile != null)
222 {
223 parentFile.mkdirs(); // create the file's parent folder structure
224 }
225 ncfile = NetcdfFileWriteable.createNew(outputFilename, false /*
226 * Setting fill = true causes everything to be
227 * written twice: first with the fill value,
228 * then with the data values. If you know you
229 * will write all the data, you dont need to use
230 * fill. If you don't know if all the data will
231 * be written, turning fill on ensures that any
232 * values not written will have the fill value.
233 * Otherwise those values will be undefined:
234 * possibly zero, or possibly garbage.
235 */);
236 try
237 {
238 Dimension dimension = new Dimension(dimensionName, Integer.valueOf(dimensionSize).intValue()/*
239 * number of
240 * data
241 * points
242 */,
243 true /* isShared */, isUnlimited /* isUnlimited */, false /* isVariableLength */);
244
245 // check the dimension to be added
246 this.checkDimensionLimitiations(ncfile, dimension);
247
248 // add dimension
249 ncfile.addDimension(null, dimension);
250
251 // when all objects are added, call create(). You cannot read or write data until create() is called.
252 ncfile.create();
253 }
254 finally
255 {
256 if(ncfile!=null)
257 {
258 try
259 {
260 ncfile.close();
261 }
262 catch(Exception e)
263 {
264 LOG.error("Could not close file: " + outputFilename, e);
265 }
266 }
267 }
268 }
269 }
270
271 /*
272 * (non-Javadoc)
273 *
274 * @see au.csiro.netcdf.Command#getCommandName()
275 */
276 @Override
277 public String getCommandName()
278 {
279 return NcDefineDimension.NC_DEFINE_DIM_COMMAND_NAME;
280 }
281
282 /*
283 * (non-Javadoc)
284 *
285 * @see java.lang.Object#toString()
286 */
287 public String toString()
288 {
289 // generate the help/usage statement
290 String header = "Define a dimension in a netCDF file.";
291 String footer = "\nExample: ncdefinedim -outputFileName ABC.nc -dimensionName Lat -dimensionSize 560\n"
292 + "Will add a dimension called Lat allowing a maximum of 560 values to the file ABC.nc. "
293 + "The file will be created if it doesn't already exist. ";
294 StringWriter sw = new StringWriter();
295 HelpFormatter formatter = new HelpFormatter();
296 formatter.setOptionComparator(new CommandLineOptionsComparator());
297 formatter.printHelp(new PrintWriter(sw), PRINT_WIDTH, NcDefineDimension.NC_DEFINE_DIM_COMMAND_NAME, header,
298 this.options, 0, 1, footer);
299 return sw.toString();
300 }
301
302 /*
303 * (non-Javadoc)
304 *
305 * @see au.csiro.netcdf.Command#createOptions()
306 */
307 @SuppressWarnings("static-access")
308 @Override
309 public Options createOptions()
310 {
311 Option outputFileName = OptionBuilder.withArgName("file").hasArg().withDescription(
312 "1: the filename of the netCDF file to be created.").isRequired(true).withLongOpt(OUTPUT_FILE).create(
313 "o");
314
315 // if a dimensionSize is not specified, then the dimension is "unlimited" by default.
316 Option dimensionSize = OptionBuilder.withArgName("integer").hasArg().withDescription(
317 "2: the size of the dimension, OPTIONAL, if not specified dimension is considered to be \"unlimited\".")
318 .isRequired(false).withLongOpt(DIMENSION_SIZE).create("s");
319
320 Option dimensionName = OptionBuilder.withArgName("text").hasArg().withDescription(
321 "3: the name to be given to the dimension.").isRequired(true).withLongOpt(DIMENSION_NAME).create("d");
322
323 Option largeFileSupport = OptionBuilder.withDescription(
324 "4: OPTIONAL, set if more than 2 GB of data will need to be stored in this file.").isRequired(false)
325 .withLongOpt(IS_LARGE_FILE).create("l");
326
327 Options options = new Options();
328
329 options.addOption(outputFileName);
330 options.addOption(dimensionSize);
331 options.addOption(dimensionName);
332 options.addOption(largeFileSupport);
333
334 return options;
335 }
336
337 /**
338 * @return the usage for this command.
339 */
340 public String getUsageString()
341 {
342 return this.toString();
343 }
344
345 /**
346 * Determine if this command is valid, check its syntax.
347 *
348 * @param commandLine
349 * command line arguments.
350 * @return error if found.
351 */
352 public String validCommand(String[] commandLine)
353 {
354 String errorMsg = "";
355
356 // validate the command line parameters
357 try
358 {
359 // try parsing the command line, where ParseException can be thrown from
360 CommandLine parsedCommandLine = new BasicParser().parse(this.options, commandLine);
361
362 // get the command line argument values
363 String outputFilenameArg = (parsedCommandLine.hasOption(OUTPUT_FILE)) ? parsedCommandLine
364 .getOptionValue(OUTPUT_FILE) : "";
365 String dimensionSizeArg = (parsedCommandLine.hasOption(DIMENSION_SIZE)) ? parsedCommandLine
366 .getOptionValue(DIMENSION_SIZE) : "";
367 boolean isLargeFileSupport = parsedCommandLine.hasOption(IS_LARGE_FILE);
368
369 // check whether large file support is needed
370 if (Util.fileExists(outputFilenameArg)
371 && Util.getExistingFile(outputFilenameArg).length() >= MAX_32BIT_OFFSET_FILE_SIZE
372 && !isLargeFileSupport)
373 {
374 throw new IllegalArgumentException("The netCDF file will be too large, please use " + IS_LARGE_FILE
375 + " flag.");
376 }
377
378 // try getting dimension size, where IllegalArgumentException can be thrown from
379 if (!dimensionSizeArg.isEmpty())
380 {
381 NcDefineDimension.mapStringToDimensionSize(dimensionSizeArg);
382 }
383 }
384 catch (ParseException pe)
385 {
386 errorMsg = errorMsg + "\n" + pe.getMessage();
387 }
388 catch (IllegalArgumentException iae)
389 {
390 errorMsg = errorMsg + "\n" + iae.getMessage();
391 }
392
393 return errorMsg;
394 }
395
396 /**
397 * Maps a <code>String</code> into an <code>integer</code>.
398 *
399 * @param dimensionSizeArg
400 * a dimensions size.
401 * @return the dimension size as an <code>integer</code>.
402 * @throws IllegalArgumentException
403 * thrown if the <code>String</code> can not be mapped to a <code>integer</code>.
404 */
405 private static int mapStringToDimensionSize(String dimensionSizeArg)
406 {
407 if (Util.validateNumber(dimensionSizeArg))
408 {
409 return Integer.valueOf(dimensionSizeArg).intValue();
410 }
411 else
412 {
413 throw new IllegalArgumentException(NcDefineDimension.DIMENSION_SIZE + " value is not an integer: "
414 + dimensionSizeArg);
415 }
416 }
417
418 /**
419 * Check that a {@link Dimension} can be added to a netCDF-3 file.
420 *
421 * @param ncfile
422 * the netCDF-3 file that the {@link Dimension} will be added too.
423 * @param dimension
424 * the {@link Dimension} to be added.
425 * @throws IllegalStateException
426 * thrown if the {@link Dimension} can not be added to the netCDF-3 file.
427 */
428 private void checkDimensionLimitiations(NetcdfFileWriteable ncfile, Dimension dimension)
429 throws IllegalStateException
430 {
431 List<Dimension> dimensions = new ArrayList<Dimension>();
432 try
433 {
434 dimensions = ncfile.getDimensions();
435 }
436 catch (NullPointerException npe)
437 {
438 // the netcdf-java-4.0.41.jar will throw a NullPointerException if getDimensions() is called on a file that
439 // has no dimensions defined.
440 LOG.info("NetCDF file has no dimensions defined.");
441 }
442
443 // throw an exception if the user is attempting to add an unlimited dimension to a netCDF-3
444 // file that already contains one. (this a limitation of the netCDF-3 file format).
445 if (!dimensions.isEmpty() && ncfile.getUnlimitedDimension() != null && dimension.isUnlimited())
446 {
447 throw new IllegalStateException("Can not add an unlimited dimension to a netCDF-3 file "
448 + "that already contains an unlimited dimension: " + ncfile.getUnlimitedDimension());
449 }
450 }
451 }